From fdb1ba144ca61185e6457f092f38f59dd9bbe6a0 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Tue, 12 Apr 2016 09:19:11 -0700 Subject: Support OFFSET and (OFFSET ABBR) time zone rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This simplifies Gnus and VC time zone support, by letting them feed the output of ‘current-time-zone’ and ‘decode time’ to primitives that accept time zone arguments. * doc/lispref/os.texi (Time Zone Rules, Time Conversion): * etc/NEWS: * lisp/gnus/message.el (message-insert-formatted-citation-line): * lisp/org/org.el (org-timestamp-format): * src/editfns.c (Fformat_time_string, Fdecode_time): (Fcurrent_time_string, Fcurrent_time_zone, Fset_time_zone_rule): Document new behavior. * lisp/gnus/gmm-utils.el (gmm-format-time-string): * lisp/vc/add-log.el (add-log-iso8601-time-zone): Mark as obsolete, as it is now just an alias or narrow wrapper around format-time-string. * src/editfns.c (tzlookup): Also support integer OFFSET and list (OFFSET ABBR) as time zone rules. (Fencode_time): No longer need a special case for a cons ZONE. (Fcurrent_time_zone): If the time zone string is missing, compute it the same way the other new code does. --- doc/lispref/os.texi | 11 +++--- etc/NEWS | 8 ++++ lisp/gnus/gmm-utils.el | 33 +--------------- lisp/gnus/message.el | 9 ++++- lisp/org/org.el | 6 ++- lisp/vc/add-log.el | 19 ++------- src/editfns.c | 104 ++++++++++++++++++++++++++++++++++++------------- 7 files changed, 108 insertions(+), 82 deletions(-) diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index 6e0edec294..becb691581 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -1325,7 +1325,12 @@ omitted or @code{nil}, the conversion uses Emacs's default time zone. If it is @code{t}, the conversion uses Universal Time. If it is @code{wall}, the conversion uses the system wall clock time. If it is a string, the conversion uses the time zone rule equivalent to setting -@env{TZ} to that string. +@env{TZ} to that string. If it is an integer @var{offset}, the +conversion uses a fixed time zone with the given offset and a numeric +abbreviation. If it is a list (@var{offset} @var{abbr}), where +@var{offset} is an integer number of seconds east of Universal Time +and @var{abbr} is a string, the conversion uses a fixed time zone with +the given offset and abbreviation. @defun current-time-zone &optional time zone @cindex time zone, current @@ -1423,10 +1428,6 @@ yourself before you call @code{encode-time}. The optional argument @var{zone} defaults to the current time zone rule. @xref{Time Zone Rules}. -In addition to the usual time zone rule values, it can also be a list -(as you would get from @code{current-time-zone}) or an integer (as -from @code{decode-time}), applied without any further alteration for -daylight saving time. If you pass more than seven arguments to @code{encode-time}, the first six are used as @var{seconds} through @var{year}, the last argument is diff --git a/etc/NEWS b/etc/NEWS index 00f5aadd85..5ebff6267f 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -228,6 +228,14 @@ two objects are 'eq' ('eql'), then the result of 'sxhash-eq' consistency with the new functions. For compatibility, 'sxhash' remains as an alias to 'sxhash-equal'. ++++ +** Time conversion functions that accept a time zone rule argument now +allow it to be OFFSET or a list (OFFSET ABBR), where the integer +OFFSET is a count of seconds east of Universal Time, and the string +ABBR is a time zone abbreviation. The affected functions are +'current-time-string', 'current-time-zone', 'decode-time', +'format-time-string', and 'set-time-zone-rule'. + * Changes in Emacs 25.2 on Non-Free Operating Systems diff --git a/lisp/gnus/gmm-utils.el b/lisp/gnus/gmm-utils.el index f6455cf9f1..7aa52794e4 100644 --- a/lisp/gnus/gmm-utils.el +++ b/lisp/gnus/gmm-utils.el @@ -256,37 +256,8 @@ If mode is nil, use `major-mode' of the current buffer." (string-match "^\\(.+\\)-mode$" mode) (match-string 1 mode)))))) -(defun gmm-format-time-string (format-string &optional time tz) - "Use FORMAT-STRING to format the time TIME, or now if omitted. -The optional TZ specifies the time zone in a number of seconds; any -other non-nil value will be treated as 0. Note that both the format -specifiers `%Z' and `%z' will be replaced with a numeric form. " -;; FIXME: is there a smart way to replace %Z with a time zone name? - (if (and (numberp tz) (not (zerop tz))) - (let ((st 0) - (case-fold-search t) - ls nd rest) - (setq time (if time - (copy-sequence time) - (current-time))) - (if (>= (setq ls (- (cadr time) (car (current-time-zone)) (- tz))) 0) - (setcar (cdr time) ls) - (setcar (cdr time) (+ ls 65536)) - (setcar time (1- (car time)))) - (setq tz (format "%s%02d%02d" - (if (>= tz 0) "+" "-") - (/ (abs tz) 3600) - (/ (% (abs tz) 3600) 60))) - (while (string-match "%+z" format-string st) - (if (zerop (% (- (setq nd (match-end 0)) (match-beginning 0)) 2)) - (progn - (push (substring format-string st (- nd 2)) rest) - (push tz rest)) - (push (substring format-string st nd) rest)) - (setq st nd)) - (push (substring format-string st) rest) - (format-time-string (apply 'concat (nreverse rest)) time)) - (format-time-string format-string time t))) +(define-obsolete-function-alias 'gmm-format-time-string 'format-time-string + "25.2") (provide 'gmm-utils) diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index 14d8d30f8a..32d740b019 100644 --- a/lisp/gnus/message.el +++ b/lisp/gnus/message.el @@ -3879,8 +3879,13 @@ This function uses `mail-citation-hook' if that is non-nil." (defun message-insert-formatted-citation-line (&optional from date tz) "Function that inserts a formatted citation line. The optional FROM, and DATE are strings containing the contents of -the From header and the Date header respectively. The optional TZ -is a number of seconds, overrides the time zone of DATE. +the From header and the Date header respectively. + +The optional TZ is omitted or nil for Emacs local time, t for +Universal Time, `wall' for system wall clock time, or a string as +in the TZ environment variable. It can also be a list (as from +`current-time-zone') or an integer (as from `decode-time') +applied without consideration for daylight saving time. See `message-citation-line-format'." ;; The optional args are for testing/debugging. They will disappear later. diff --git a/lisp/org/org.el b/lisp/org/org.el index 231daa9a6a..3abf62704b 100644 --- a/lisp/org/org.el +++ b/lisp/org/org.el @@ -22673,8 +22673,10 @@ When optional argument END is non-nil, use end of date-range or time-range, if possible. The optional ZONE is omitted or nil for Emacs local time, t for -Universal Time, `wall' for system wall clock time, or a string as in -the TZ environment variable." +Universal Time, `wall' for system wall clock time, or a string as +in the TZ environment variable. It can also be a list (as from +`current-time-zone') or an integer (as from `decode-time') +applied without consideration for daylight saving time." (format-time-string format (apply 'encode-time diff --git a/lisp/vc/add-log.el b/lisp/vc/add-log.el index 58a4e77a60..9076d834c7 100644 --- a/lisp/vc/add-log.el +++ b/lisp/vc/add-log.el @@ -590,25 +590,14 @@ If a string, interpret as the ZONE argument of `format-time-string'.") (lambda (x) (or (booleanp x) (stringp x)))) (defun add-log-iso8601-time-zone (&optional time zone) - (let* ((utc-offset (or (car (current-time-zone time zone)) 0)) - (sign (if (< utc-offset 0) ?- ?+)) - (sec (abs utc-offset)) - (ss (% sec 60)) - (min (/ sec 60)) - (mm (% min 60)) - (hh (/ min 60))) - (format (cond ((not (zerop ss)) "%c%02d:%02d:%02d") - ((not (zerop mm)) "%c%02d:%02d") - (t "%c%02d")) - sign hh mm ss))) + (declare (obsolete nil "25.2")) + (format-time-string "%:::z" time zone)) (defvar add-log-iso8601-with-time-zone nil) (defun add-log-iso8601-time-string (&optional time zone) - (let ((date (format-time-string "%Y-%m-%d" time zone))) - (if add-log-iso8601-with-time-zone - (concat date " " (add-log-iso8601-time-zone time zone)) - date))) + (format-time-string + (if add-log-iso8601-with-time-zone "%Y-%m-%d %:::z" "%Y-%m-%d") time zone)) (defun change-log-name () "Return (system-dependent) default name for a change log file." diff --git a/src/editfns.c b/src/editfns.c index 70285e6d5d..48f2a8de12 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -146,8 +146,6 @@ xtzfree (timezone_t tz) static timezone_t tzlookup (Lisp_Object zone, bool settz) { - static char const tzbuf_format[] = "XXX%s%"pI"d:%02d:%02d"; - char tzbuf[sizeof tzbuf_format + INT_STRLEN_BOUND (EMACS_INT)]; char const *zone_string; timezone_t new_tz; @@ -160,16 +158,53 @@ tzlookup (Lisp_Object zone, bool settz) } else { + static char const tzbuf_format[] = "<%+.*"pI"d>%s%"pI"d:%02d:%02d"; + char const *trailing_tzbuf_format = tzbuf_format + sizeof "<%+.*"pI"d" - 1; + char tzbuf[sizeof tzbuf_format + 2 * INT_STRLEN_BOUND (EMACS_INT)]; + bool plain_integer = INTEGERP (zone); + if (EQ (zone, Qwall)) zone_string = 0; else if (STRINGP (zone)) - zone_string = SSDATA (zone); - else if (INTEGERP (zone)) + zone_string = SSDATA (ENCODE_SYSTEM (zone)); + else if (plain_integer || (CONSP (zone) && INTEGERP (XCAR (zone)) + && CONSP (XCDR (zone)))) { + Lisp_Object abbr; + if (!plain_integer) + { + abbr = XCAR (XCDR (zone)); + zone = XCAR (zone); + } + EMACS_INT abszone = eabs (XINT (zone)), hour = abszone / (60 * 60); - int min = (abszone / 60) % 60, sec = abszone % 60; - sprintf (tzbuf, tzbuf_format, &"-"[XINT (zone) < 0], hour, min, sec); - zone_string = tzbuf; + int hour_remainder = abszone % (60 * 60); + int min = hour_remainder / 60, sec = hour_remainder % 60; + + if (plain_integer) + { + int prec = 2; + EMACS_INT numzone = hour; + if (hour_remainder != 0) + { + prec += 2, numzone = 100 * numzone + min; + if (sec != 0) + prec += 2, numzone = 100 * numzone + sec; + } + sprintf (tzbuf, tzbuf_format, prec, numzone, + &"-"[XINT (zone) < 0], hour, min, sec); + zone_string = tzbuf; + } + else + { + AUTO_STRING (leading, "<"); + AUTO_STRING_WITH_LEN (trailing, tzbuf, + sprintf (tzbuf, trailing_tzbuf_format, + &"-"[XINT (zone) < 0], + hour, min, sec)); + zone_string = SSDATA (concat3 (leading, ENCODE_SYSTEM (abbr), + trailing)); + } } else xsignal2 (Qerror, build_string ("Invalid time zone specification"), @@ -1969,9 +2004,13 @@ DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0, doc: /* Use FORMAT-STRING to format the time TIME, or now if omitted. TIME is specified as (HIGH LOW USEC PSEC), as returned by `current-time' or `file-attributes'. The obsolete form (HIGH . LOW) -is also still accepted. The optional ZONE is omitted or nil for Emacs -local time, t for Universal Time, `wall' for system wall clock time, -or a string as in the TZ environment variable. +is also still accepted. + +The optional ZONE is omitted or nil for Emacs local time, t for +Universal Time, `wall' for system wall clock time, or a string as in +the TZ environment variable. It can also be a list (as from +`current-time-zone') or an integer (as from `decode-time') applied +without consideration for daylight saving time. The value is a copy of FORMAT-STRING, but with certain constructs replaced by text that describes the specified date and time in TIME: @@ -2085,9 +2124,12 @@ DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 2, 0, The optional SPECIFIED-TIME should be a list of (HIGH LOW . IGNORED), as from `current-time' and `file-attributes', or nil to use the current time. The obsolete form (HIGH . LOW) is also still accepted. + The optional ZONE is omitted or nil for Emacs local time, t for Universal Time, `wall' for system wall clock time, or a string as in -the TZ environment variable. +the TZ environment variable. It can also be a list (as from +`current-time-zone') or an integer (as from `decode-time') applied +without consideration for daylight saving time. The list has the following nine members: SEC is an integer between 0 and 60; SEC is 60 for a leap second, which only some operating systems @@ -2150,6 +2192,7 @@ check_tm_member (Lisp_Object obj, int offset) DEFUN ("encode-time", Fencode_time, Sencode_time, 6, MANY, 0, doc: /* Convert SECOND, MINUTE, HOUR, DAY, MONTH, YEAR and ZONE to internal time. This is the reverse operation of `decode-time', which see. + The optional ZONE is omitted or nil for Emacs local time, t for Universal Time, `wall' for system wall clock time, or a string as in the TZ environment variable. It can also be a list (as from @@ -2184,8 +2227,6 @@ usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE) */) tm.tm_year = check_tm_member (args[5], TM_YEAR_BASE); tm.tm_isdst = -1; - if (CONSP (zone)) - zone = XCAR (zone); timezone_t tz = tzlookup (zone, false); value = emacs_mktime_z (tz, &tm); xtzfree (tz); @@ -2214,7 +2255,9 @@ but this is considered obsolete. The optional ZONE is omitted or nil for Emacs local time, t for Universal Time, `wall' for system wall clock time, or a string as in -the TZ environment variable. */) +the TZ environment variable. It can also be a list (as from +`current-time-zone') or an integer (as from `decode-time') applied +without consideration for daylight saving time. */) (Lisp_Object specified_time, Lisp_Object zone) { time_t value = lisp_seconds_argument (specified_time); @@ -2290,8 +2333,12 @@ instead of using the current time. The argument should have the form \(HIGH LOW . IGNORED). Thus, you can use times obtained from `current-time' and from `file-attributes'. SPECIFIED-TIME can also have the form (HIGH . LOW), but this is considered obsolete. -Optional second arg ZONE is omitted or nil for the local time zone, or -a string as in the TZ environment variable. + +The optional ZONE is omitted or nil for Emacs local time, t for +Universal Time, `wall' for system wall clock time, or a string as in +the TZ environment variable. It can also be a list (as from +`current-time-zone') or an integer (as from `decode-time') applied +without consideration for daylight saving time. Some operating systems cannot provide all this information to Emacs; in this case, `current-time-zone' returns a list containing nil for @@ -2315,15 +2362,18 @@ the data it can't find. */) zone_offset = make_number (offset); if (SCHARS (zone_name) == 0) { - /* No local time zone name is available; use "+-NNNN" instead. */ - long int m = offset / 60; - long int am = offset < 0 ? - m : m; - long int hour = am / 60; - int min = am % 60; - char buf[sizeof "+00" + INT_STRLEN_BOUND (long int)]; - zone_name = make_formatted_string (buf, "%c%02ld%02d", + /* No local time zone name is available; use numeric zone instead. */ + long int hour = offset / 3600; + int min_sec = offset % 3600; + int amin_sec = min_sec < 0 ? - min_sec : min_sec; + int min = amin_sec / 60; + int sec = amin_sec % 60; + int min_prec = min_sec ? 2 : 0; + int sec_prec = sec ? 2 : 0; + char buf[sizeof "+0000" + INT_STRLEN_BOUND (long int)]; + zone_name = make_formatted_string (buf, "%c%.2ld%.*d%.*d", (offset < 0 ? '-' : '+'), - hour, min); + hour, min_prec, min, sec_prec, sec); } } @@ -2332,11 +2382,11 @@ the data it can't find. */) DEFUN ("set-time-zone-rule", Fset_time_zone_rule, Sset_time_zone_rule, 1, 1, 0, doc: /* Set the Emacs local time zone using TZ, a string specifying a time zone rule. - If TZ is nil or `wall', use system wall clock time; this differs from the usual Emacs convention where nil means current local time. If TZ -is t, use Universal Time. If TZ is an integer, treat it as in -`encode-time'. +is t, use Universal Time. If TZ is a list (as from +`current-time-zone') or an integer (as from `decode-time'), use the +specified time zone without consideration for daylight saving time. Instead of calling this function, you typically want something else. To temporarily use a different time zone rule for just one invocation -- cgit v1.2.3