aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/lispref/os.texi64
-rw-r--r--etc/NEWS15
-rw-r--r--lisp/calendar/time-date.el149
-rw-r--r--lisp/simple.el76
-rw-r--r--src/timefns.c6
-rw-r--r--test/lisp/calendar/time-date-tests.el109
6 files changed, 419 insertions, 0 deletions
diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index fef954eb7a3..d397a125738 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -1466,6 +1466,60 @@ seconds east of Greenwich.
1466 1466
1467@strong{Common Lisp Note:} Common Lisp has different meanings for 1467@strong{Common Lisp Note:} Common Lisp has different meanings for
1468@var{dow} and @var{utcoff}. 1468@var{dow} and @var{utcoff}.
1469
1470To access (or alter) the elements in the time value, the
1471@code{decoded-time-second}, @code{decoded-time-minute},
1472@code{decoded-time-hour}, @code{decoded-time-day},
1473@code{decoded-time-month}, @code{decoded-time-year},
1474@code{decoded-time-weekday}, @code{decoded-time-dst} and
1475@code{decoded-time-zone} accessors can be used.
1476
1477For instance, to increase the year in a decoded time, you could say:
1478
1479@lisp
1480(setf (decoded-time-year decoded-time)
1481 (+ (decoded-time-year decoded-time) 4))
1482@end lisp
1483
1484Also see the following function.
1485
1486@end defun
1487
1488@defun decoded-time-add time delta
1489This function takes a decoded time structure and adds @var{delta}
1490(also a decoded time structure) to it. Elements in @var{delta} that
1491are @code{nil} are ignored.
1492
1493For instance, if you want ``same time next month'', you
1494could say:
1495
1496@lisp
1497(let ((time (decode-time))
1498 (delta (make-decoded-time :month 2)))
1499 (encode-time (decoded-time-add time delta)))
1500@end lisp
1501
1502If this date doesn't exist (if you're running this on January 31st,
1503for instance), then the date will be shifted back until you get a
1504valid date (which will be February 28th or 29th, depending).
1505
1506Fields are added in a most to least significant order, so if the
1507adjustment described above happens, it happens before adding days,
1508hours, minutes or seconds.
1509
1510The values in @var{delta} can be negative to subtract values instead.
1511
1512The return value is a decoded time structure.
1513@end defun
1514
1515@defun make-decoded-time &key second minute hour day month year dst zone
1516Return a decoded time structure with only the given keywords filled
1517out, leaving the rest @code{nil}. For instance, to get a structure
1518that represents ``two months'', you could say:
1519
1520@lisp
1521(make-decoded-time :month 2)
1522@end lisp
1469@end defun 1523@end defun
1470 1524
1471@defun encode-time &optional time form &rest obsolescent-arguments 1525@defun encode-time &optional time form &rest obsolescent-arguments
@@ -1867,6 +1921,16 @@ This returns the day number within the year corresponding to @var{time-value}.
1867This function returns @code{t} if @var{year} is a leap year. 1921This function returns @code{t} if @var{year} is a leap year.
1868@end defun 1922@end defun
1869 1923
1924@defun date-days-in-month year month
1925Return the number of days in @var{month} in @var{year}. For instance,
1926there's 29 days in February 2004.
1927@end defun
1928
1929@defun date-ordinal-to-time year ordinal
1930Return the date of @var{ordinal} in @var{year} as a decoded time
1931structure. For instance, the 120th day in 2004 is April 29th.
1932@end defun
1933
1870@node Timers 1934@node Timers
1871@section Timers for Delayed Execution 1935@section Timers for Delayed Execution
1872@cindex timers 1936@cindex timers
diff --git a/etc/NEWS b/etc/NEWS
index c654b9ba34a..2bdbfcb8d08 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2069,6 +2069,21 @@ that acts like the '0' flag but also puts a '+' before nonnegative
2069years containing more than four digits. This is for compatibility 2069years containing more than four digits. This is for compatibility
2070with POSIX.1-2017. 2070with POSIX.1-2017.
2071 2071
2072+++
2073*** To access (or alter) the elements a decoded time value, the
2074'decoded-time-second', 'decoded-time-minute', 'decoded-time-hour',
2075'decoded-time-day', 'decoded-time-month', 'decoded-time-year',
2076'decoded-time-weekday', 'decoded-time-dst' and 'decoded-time-zone'
2077accessors can be used.
2078
2079+++
2080*** The new functions `date-days-in-month' (which will say how many
2081days there are in a month in a specific year), `date-ordinal-to-time'
2082(that computes the date of an ordinal day), `decoded-time-add' for
2083doing computations on a decoded time structure), and
2084`make-decoded-time' (for making a decoded time structure with only the
2085given keywords filled out) have been added.
2086
2072** 'define-minor-mode' automatically documents the meaning of ARG. 2087** 'define-minor-mode' automatically documents the meaning of ARG.
2073 2088
2074+++ 2089+++
diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index 2c0280ccf3b..d299dc5e7d1 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -36,6 +36,9 @@
36 36
37;;; Code: 37;;; Code:
38 38
39(require 'cl-lib)
40(require 'subr-x)
41
39(defmacro with-decoded-time-value (varlist &rest body) 42(defmacro with-decoded-time-value (varlist &rest body)
40 "Decode a time value and bind it according to VARLIST, then eval BODY. 43 "Decode a time value and bind it according to VARLIST, then eval BODY.
41 44
@@ -349,6 +352,152 @@ is output until the first non-zero unit is encountered."
349 (<= (car here) delay))) 352 (<= (car here) delay)))
350 (concat (format "%.2f" (/ delay (car (cddr here)))) (cadr here)))))) 353 (concat (format "%.2f" (/ delay (car (cddr here)))) (cadr here))))))
351 354
355(defun date-days-in-month (year month)
356 "The number of days in MONTH in YEAR."
357 (if (= month 2)
358 (if (date-leap-year-p year)
359 29
360 28)
361 (if (memq month '(1 3 5 7 8 10 12))
362 31
363 30)))
364
365(defun date-ordinal-to-time (year ordinal)
366 "Convert a YEAR/ORDINAL to the equivalent `decoded-time' structure.
367ORDINAL is the number of days since the start of the year, with
368January 1st being 1."
369 (let ((month 1))
370 (while (> ordinal (date-days-in-month year month))
371 (setq ordinal (- ordinal (date-days-in-month year month))
372 month (1+ month)))
373 (list nil nil nil ordinal month year nil nil nil)))
374
375(defun decoded-time-add (time delta)
376 "Add DELTA to TIME, both of which are `decoded-time' structures.
377TIME should represent a time, while DELTA should only have
378non-nil integers for the values that should be altered.
379
380For instance, if you want to \"add two months\" to TIME, then
381leave all other fields but the month field in DELTA nil, and make
382the month field 2. The values in DELTA can be negative.
383
384If applying a month/year delta leaves the time spec invalid, it
385is decreased to be valid (\"add one month\" to January 31st 2019
386will yield a result of February 28th 2019 and \"add one year\" to
387February 29th 2020 will result in February 28th 2021).
388
389Fields are added in a most to least significant order, so if the
390adjustment described above happens, it happens before adding
391days, hours, minutes or seconds.
392
393When changing the time bits in TIME (i.e., second/minute/hour),
394changes in daylight saving time are not taken into account."
395 (let ((time (copy-sequence time))
396 seconds)
397 ;; Years are simple.
398 (when (decoded-time-year delta)
399 (cl-incf (decoded-time-year time) (decoded-time-year delta)))
400
401 ;; Months are pretty simple.
402 (when (decoded-time-month delta)
403 (let ((new (+ (decoded-time-month time) (decoded-time-month delta))))
404 (setf (decoded-time-month time) (mod new 12))
405 (cl-incf (decoded-time-year time) (/ new 12))))
406
407 ;; Adjust for month length (as described in the doc string).
408 (setf (decoded-time-day time)
409 (min (date-days-in-month (decoded-time-year time)
410 (decoded-time-month time))
411 (decoded-time-day time)))
412
413 ;; Days are iterative.
414 (when-let* ((days (decoded-time-day delta)))
415 (let ((increase (> days 0))
416 (days (abs days)))
417 (while (> days 0)
418 (decoded-time--alter-day time increase)
419 (cl-decf days))))
420
421 ;; Do the time part, which is pretty simple (except for leap
422 ;; seconds, I guess).
423 (setq seconds (+ (* (or (decoded-time-hour delta) 0) 3600)
424 (* (or (decoded-time-minute delta) 0) 60)
425 (or (decoded-time-second delta) 0)))
426
427 ;; Time zone adjustments are basically the same as time adjustments.
428 (setq seconds (+ seconds (or (decoded-time-zone delta) 0)))
429
430 (cond
431 ((> seconds 0)
432 (decoded-time--alter-second time seconds t))
433 ((< seconds 0)
434 (decoded-time--alter-second time (abs seconds) nil)))
435
436 time))
437
438(defun decoded-time--alter-month (time increase)
439 "Increase or decrease the month in TIME by 1."
440 (if increase
441 (progn
442 (cl-incf (decoded-time-month time))
443 (when (> (decoded-time-month time) 12)
444 (setf (decoded-time-month time) 1)
445 (cl-incf (decoded-time-year time))))
446 (cl-decf (decoded-time-month time))
447 (when (zerop (decoded-time-month time))
448 (setf (decoded-time-month time) 12)
449 (cl-decf (decoded-time-year time)))))
450
451(defun decoded-time--alter-day (time increase)
452 "Increase or decrease the day in TIME by 1."
453 (if increase
454 (progn
455 (cl-incf (decoded-time-day time))
456 (when (> (decoded-time-day time)
457 (date-days-in-month (decoded-time-year time)
458 (decoded-time-month time)))
459 (setf (decoded-time-day time) 1)
460 (decoded-time--alter-month time t)))
461 (cl-decf (decoded-time-day time))
462 (when (zerop (decoded-time-day time))
463 (decoded-time--alter-month time nil)
464 (setf (decoded-time-day time)
465 (date-days-in-month (decoded-time-year time)
466 (decoded-time-month time))))))
467
468(defun decoded-time--alter-second (time seconds increase)
469 "Increase or decrease the time in TIME by SECONDS."
470 (let ((old (+ (* (or (decoded-time-hour time) 0) 3600)
471 (* (or (decoded-time-minute time) 0) 60)
472 (or (decoded-time-second time) 0))))
473
474 (if increase
475 (progn
476 (setq old (+ old seconds))
477 (setf (decoded-time-second time) (% old 60)
478 (decoded-time-minute time) (% (/ old 60) 60)
479 (decoded-time-hour time) (% (/ old 3600) 24))
480 ;; Hm... DST...
481 (let ((days (/ old (* 60 60 24))))
482 (while (> days 0)
483 (decoded-time--alter-day time t)
484 (cl-decf days))))
485 (setq old (abs (- old seconds)))
486 (setf (decoded-time-second time) (% old 60)
487 (decoded-time-minute time) (% (/ old 60) 60)
488 (decoded-time-hour time) (% (/ old 3600) 24))
489 ;; Hm... DST...
490 (let ((days (/ old (* 60 60 24))))
491 (while (> days 0)
492 (decoded-time--alter-day time nil)
493 (cl-decf days))))))
494
495(cl-defun make-decoded-time (&key second minute hour
496 day month year
497 dst zone)
498 "Return a `decoded-time' structure with only the keywords given filled out."
499 (list second minute hour day month year nil dst zone))
500
352(provide 'time-date) 501(provide 'time-date)
353 502
354;;; time-date.el ends here 503;;; time-date.el ends here
diff --git a/lisp/simple.el b/lisp/simple.el
index 75be4fe7cb5..8855045123f 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -9063,6 +9063,82 @@ to capitalize ARG words."
9063 (capitalize-region (region-beginning) (region-end)) 9063 (capitalize-region (region-beginning) (region-end))
9064 (capitalize-word arg))) 9064 (capitalize-word arg)))
9065 9065
9066;;; Accessors for `decode-time' values.
9067
9068(defsubst decoded-time-second (time)
9069 "The seconds in TIME, which is a value returned by `decode-time'.
9070This is an integer between 0 and 60 (inclusive). (60 is a leap
9071second, which only some operating systems support.)"
9072 (nth 0 time))
9073
9074(defsubst decoded-time-minute (time)
9075 "The minutes in TIME, which is a value returned by `decode-time'.
9076This is an integer between 0 and 59 (inclusive)."
9077 (nth 1 time))
9078
9079(defsubst decoded-time-hour (time)
9080 "The hours in TIME, which is a value returned by `decode-time'.
9081This is an integer between 0 and 23 (inclusive)."
9082 (nth 2 time))
9083
9084(defsubst decoded-time-day (time)
9085 "The day-of-the-month in TIME, which is a value returned by `decode-time'.
9086This is an integer between 1 and 31 (inclusive)."
9087 (nth 3 time))
9088
9089(defsubst decoded-time-month (time)
9090 "The month in TIME, which is a value returned by `decode-time'.
9091This is an integer between 1 and 12 (inclusive). January is 1."
9092 (nth 4 time))
9093
9094(defsubst decoded-time-year (time)
9095 "The year in TIME, which is a value returned by `decode-time'.
9096This is a four digit integer."
9097 (nth 5 time))
9098
9099(defsubst decoded-time-weekday (time)
9100 "The day-of-the-week in TIME, which is a value returned by `decode-time'.
9101This is a number between 0 and 6, and 0 is Sunday."
9102 (nth 6 time))
9103
9104(defsubst decoded-time-dst (time)
9105 "The daylight saving time in TIME, which is a value returned by `decode-time'.
9106This is t if daylight saving time is in effect, and nil if not."
9107 (nth 7 time))
9108
9109(defsubst decoded-time-zone (time)
9110 "The time zone in TIME, which is a value returned by `decode-time'.
9111This is an integer indicating the UTC offset in seconds, i.e.,
9112the number of seconds east of Greenwich."
9113 (nth 8 time))
9114
9115(gv-define-setter decoded-time-second (second time)
9116 `(setf (nth 0 ,time) ,second))
9117
9118(gv-define-setter decoded-time-minute (minute time)
9119 `(setf (nth 1 ,time) ,minute))
9120
9121(gv-define-setter decoded-time-hour (hour time)
9122 `(setf (nth 2 ,time) ,hour))
9123
9124(gv-define-setter decoded-time-day (day time)
9125 `(setf (nth 3 ,time) ,day))
9126
9127(gv-define-setter decoded-time-month (month time)
9128 `(setf (nth 4 ,time) ,month))
9129
9130(gv-define-setter decoded-time-year (year time)
9131 `(setf (nth 5 ,time) ,year))
9132
9133;; No setter for weekday, which is the 6th element.
9134
9135(gv-define-setter decoded-time-dst (dst time)
9136 `(setf (nth 7 ,time) ,dst))
9137
9138(gv-define-setter decoded-time-zone (zone time)
9139 `(setf (nth 8 ,time) ,zone))
9140
9141
9066 9142
9067 9143
9068(provide 'simple) 9144(provide 'simple)
diff --git a/src/timefns.c b/src/timefns.c
index 3b7ed460222..cce9dd51ba9 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -1326,6 +1326,12 @@ the TZ environment variable. It can also be a list (as from
1326`current-time-zone') or an integer (the UTC offset in seconds) applied 1326`current-time-zone') or an integer (the UTC offset in seconds) applied
1327without consideration for daylight saving time. 1327without consideration for daylight saving time.
1328 1328
1329To access (or alter) the elements in the time value, the
1330`decoded-time-second', `decoded-time-minute', `decoded-time-hour',
1331`decoded-time-day', `decoded-time-month', `decoded-time-year',
1332`decoded-time-weekday', `decoded-time-dst' and `decoded-time-zone'
1333accessors can be used.
1334
1329The list has the following nine members: SEC is an integer between 0 1335The list has the following nine members: SEC is an integer between 0
1330and 60; SEC is 60 for a leap second, which only some operating systems 1336and 60; SEC is 60 for a leap second, which only some operating systems
1331support. MINUTE is an integer between 0 and 59. HOUR is an integer 1337support. MINUTE is an integer between 0 and 59. HOUR is an integer
diff --git a/test/lisp/calendar/time-date-tests.el b/test/lisp/calendar/time-date-tests.el
new file mode 100644
index 00000000000..d6cf742bc53
--- /dev/null
+++ b/test/lisp/calendar/time-date-tests.el
@@ -0,0 +1,109 @@
1;;; time-date-tests.el --- tests for calendar/time-date.el -*- lexical-binding:t -*-
2
3;; Copyright (C) 2019 Free Software Foundation, Inc.
4
5;; This file is part of GNU Emacs.
6
7;; GNU Emacs is free software: you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Code:
21
22(require 'ert)
23(require 'time-date)
24
25(ert-deftest test-leap-year ()
26 (should-not (date-leap-year-p 1999))
27 (should-not (date-leap-year-p 1900))
28 (should (date-leap-year-p 2000))
29 (should (date-leap-year-p 2004)))
30
31(ert-deftest test-days-in-month ()
32 (should (= (date-days-in-month 2004 2) 29))
33 (should (= (date-days-in-month 2004 3) 31))
34 (should-not (= (date-days-in-month 1900 3) 28)))
35
36(ert-deftest test-ordinal ()
37 (should (equal (date-ordinal-to-time 2008 271)
38 '(0 0 0 27 9 2008 nil nil nil)))
39 (should (equal (date-ordinal-to-time 2008 1)
40 '(0 0 0 1 1 2008 nil nil nil)))
41 (should (equal (date-ordinal-to-time 2008 32)
42 '(0 0 0 1 2 2008 nil nil nil)))
43 (should (equal (date-ordinal-to-time 1981 095)
44 '(0 0 0 5 4 1981 nil nil nil))))
45
46(cl-defmethod mdec (&key second minute hour
47 day month year
48 dst zone)
49 (list second minute hour day month year nil dst zone))
50
51(ert-deftest test-decoded-add ()
52 (let ((time '(12 15 16 8 7 2019 1 t 7200)))
53 (should (equal (decoded-time-add time (mdec :year 1))
54 '(12 15 16 8 7 2020 1 t 7200)))
55
56 (should (equal (decoded-time-add time (mdec :year -2))
57 '(12 15 16 8 7 2017 1 t 7200)))
58
59 (should (equal (decoded-time-add time (mdec :month 1))
60 '(12 15 16 8 8 2019 1 t 7200)))
61
62 (should (equal (decoded-time-add time (mdec :month 10))
63 '(12 15 16 8 5 2020 1 t 7200)))
64
65 (should (equal (decoded-time-add time (mdec :day 1))
66 '(12 15 16 9 7 2019 1 t 7200)))
67
68 (should (equal (decoded-time-add time (mdec :day -1))
69 '(12 15 16 7 7 2019 1 t 7200)))
70
71 (should (equal (decoded-time-add time (mdec :day 30))
72 '(12 15 16 7 8 2019 1 t 7200)))
73
74 (should (equal (decoded-time-add time (mdec :day -365))
75 '(12 15 16 8 7 2018 1 t 7200)))
76
77 (should (equal (decoded-time-add time (mdec :day 365))
78 '(12 15 16 7 7 2020 1 t 7200)))
79
80 ;; 2020 is a leap year.
81 (should (equal (decoded-time-add time (mdec :day 366))
82 '(12 15 16 8 7 2020 1 t 7200)))
83
84 (should (equal (decoded-time-add time (mdec :second 1))
85 '(13 15 16 8 7 2019 1 t 7200)))
86
87 (should (equal (decoded-time-add time (mdec :second -1))
88 '(11 15 16 8 7 2019 1 t 7200)))
89
90 (should (equal (decoded-time-add time (mdec :second 61))
91 '(13 16 16 8 7 2019 1 t 7200)))
92
93 (should (equal (decoded-time-add time (mdec :hour 1 :minute 2 :second 3))
94 '(15 17 17 8 7 2019 1 t 7200)))
95
96 (should (equal (decoded-time-add time (mdec :hour 24))
97 '(12 15 16 9 7 2019 1 t 7200)))
98 ))
99
100(ert-deftest test-decoded-add-zone ()
101 (let ((time '(12 15 16 8 7 2019 1 t 7200)))
102 (should (equal (decoded-time-add time (mdec :zone -3600))
103 '(12 15 15 8 7 2019 1 t 7200)))
104 (should (equal (decoded-time-add time (mdec :zone -7200))
105 '(12 15 14 8 7 2019 1 t 7200)))))
106
107(require 'ert)
108
109;;; time-date-tests.el ends here