Commit fdb1ba14 authored by Paul Eggert's avatar Paul Eggert

Support OFFSET and (OFFSET ABBR) time zone rules

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.
parent 7c2c2196
......@@ -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
......
......@@ -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
......
......@@ -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)
......
......@@ -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.
......
......@@ -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
......
......@@ -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."
......
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment