diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2015-07-26 00:01:34 -0700 |
---|---|---|
committer | Paul Eggert <eggert@cs.ucla.edu> | 2015-07-26 12:44:54 -0700 |
commit | af32fa956267af40db61051c248597144d41521c (patch) | |
tree | afd650b9f9805474df149081e51cc8abae3bdb87 | |
parent | 4c55786d9b2a5d571f3e543cc261ce0702c7341e (diff) | |
download | emacs-af32fa956267af40db61051c248597144d41521c.tar.gz |
New optional ZONE arg for format-time-string etc.
This simplifies time conversions in other time zones.
It also prevents display-time-world tampering with TZ (Bug#21020).
* admin/admin.el (add-release-logs):
Use improved add-log-time-format API.
* admin/merge-gnulib (GNULIB_MODULES): Add time_rz, timegm.
(GNULIB_TOOL_FLAGS): Avoid flexmember, setenv, unsetenv.
* configure.ac (tzalloc): Remove test for this, since
Emacs no longer uses HAVE_TZALLOC directly.
* doc/lispref/os.texi (Time of Day, Time Conversion)
(Time Parsing):
* etc/NEWS: Document the new behavior.
Merge from gnulib, incorporating:
2015-07-25 strftime: fix newly-introduced bug on Solaris
2015-07-23 fprintftime, strftime: use timezone_t args
* lib/gnulib.mk, m4/gnulib-comp.m4: Regenerate.
* lib/strftime.c, lib/strftime.h, lib/time.in.h, m4/sys_time_h.m4:
* m4/time_h.m4:
Update from gnulib.
* lib/time_rz.c, lib/timegm.c, m4/time_rz.m4, m4/timegm.m4:
New files from gnulib.
* lisp/time-stamp.el (time-stamp-string):
* lisp/time.el (display-time-world-list)
(display-time-world-display):
Use new API, with time zone arg.
* lisp/time.el (display-time-world-display):
Fix race when current-time advances while we're running.
* lisp/vc/add-log.el (add-log-iso8601-time-zone)
(add-log-iso8601-time-string): Accept optional time zone arg.
* lisp/vc/add-log.el (add-change-log-entry):
* lisp/vc/log-edit.el (log-edit-changelog-ours-p): Use new arg.
* nt/gnulib.mk: Propagate lib/gnulib.mk changes here.
Add rules for the time module, since they're now needed
for tzalloc etc.
* src/conf_post.h (getenv_TZ, setenv_TZ): New macros.
(emacs_getenv_TZ, emacs_setenv_TZ): New decls.
* src/editfns.c: Include errno.h.
(set_time_zone_rule): Omit unnecessary forward decl.
(initial_tz): Remove, replacing with ...
(local_tz, wall_clock_tz, utc_tz): New static vars and constants.
(tzeqlen): New constant; prefer it to (sizeof "TZ=" - 1).
(emacs_localtime_rz, emacs_mktime_z, xtzalloc, xtzfree)
(tzlookup): New static functions.
(init_editfns): New arg DUMPING. All uses changed.
(init_editfns): Omit most initialization if dumping, not if
!initialized. Initialize wall_clock_tz and local_tz.
(emacs_nmemftime, format_time_string): Time zone argument can now
be any time zone, not just a boolean for UTC or local time. All
callers changed.
(Fformat_time_string, Fencode_time, Fcurrent_time_string)
(Fcurrent_time_zone): New optional arg ZONE.
(Fdecode_time, Fset_time_zone_rule): ZONE arg can now also take
the same form as with the other new additions.
(decode_time_zone): Remove; no longer needed.
(tzvalbuf): Now file-scope.
(emacs_getenv_TZ, emacs_setenv_TZ): New functions.
(syms_of_editfns): Define Qwall.
* src/editfns.c (mktime_z) [!HAVE_TZALLOC]:
* src/systime.h (mktime_z, timezone_t, tzalloc, tzfree)
[!HAVE_TZALLOC]:
Remove; now supplied by gnulib.
* src/emacs.c (main):
* src/lisp.h (init_editfns): Adjust to init_editfns API change.
-rw-r--r-- | admin/admin.el | 6 | ||||
-rwxr-xr-x | admin/merge-gnulib | 8 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | doc/lispref/os.texi | 80 | ||||
-rw-r--r-- | etc/NEWS | 9 | ||||
-rw-r--r-- | lib/gnulib.mk | 42 | ||||
-rw-r--r-- | lib/strftime.c | 64 | ||||
-rw-r--r-- | lib/strftime.h | 7 | ||||
-rw-r--r-- | lib/time.in.h | 19 | ||||
-rw-r--r-- | lib/time_rz.c | 374 | ||||
-rw-r--r-- | lib/timegm.c | 38 | ||||
-rw-r--r-- | lisp/time-stamp.el | 12 | ||||
-rw-r--r-- | lisp/time.el | 33 | ||||
-rw-r--r-- | lisp/vc/add-log.el | 23 | ||||
-rw-r--r-- | lisp/vc/log-edit.el | 3 | ||||
-rw-r--r-- | m4/gnulib-comp.m4 | 43 | ||||
-rw-r--r-- | m4/sys_time_h.m4 | 1 | ||||
-rw-r--r-- | m4/time_h.m4 | 1 | ||||
-rw-r--r-- | m4/time_rz.m4 | 21 | ||||
-rw-r--r-- | m4/timegm.m4 | 26 | ||||
-rw-r--r-- | nt/gnulib.mk | 88 | ||||
-rw-r--r-- | src/conf_post.h | 7 | ||||
-rw-r--r-- | src/editfns.c | 324 | ||||
-rw-r--r-- | src/emacs.c | 2 | ||||
-rw-r--r-- | src/lisp.h | 2 | ||||
-rw-r--r-- | src/systime.h | 14 |
26 files changed, 941 insertions, 308 deletions
diff --git a/admin/admin.el b/admin/admin.el index 93e9124ce8d..267f2c4afea 100644 --- a/admin/admin.el +++ b/admin/admin.el @@ -38,14 +38,12 @@ Optional argument DATE is the release date, default today." emacs-minor-version)) (read-string "Release date: " (progn (require 'add-log) - (let ((add-log-time-zone-rule t)) - (funcall add-log-time-format)))))) + (funcall add-log-time-format nil t))))) (setq root (expand-file-name root)) (unless (file-exists-p (expand-file-name "src/emacs.c" root)) (user-error "%s doesn't seem to be the root of an Emacs source tree" root)) (require 'add-log) - (or date (setq date (let ((add-log-time-zone-rule t)) - (funcall add-log-time-format)))) + (or date (setq date (funcall add-log-time-format nil t))) (let* ((logs (process-lines "find" root "-name" "ChangeLog")) (entry (format "%s %s <%s>\n\n\t* Version %s released.\n\n" date diff --git a/admin/merge-gnulib b/admin/merge-gnulib index e7910a642c7..963c3a06e1a 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -37,20 +37,20 @@ GNULIB_MODULES=' pipe2 pselect pthread_sigmask putenv qcopy-acl readlink readlinkat sig2str socklen stat-time stdalign stddef stdio stpcpy strftime strtoimax strtoumax symlink sys_stat - sys_time time time_r timer-time timespec-add timespec-sub + sys_time time time_r time_rz timegm timer-time timespec-add timespec-sub unsetenv update-copyright utimens vla warnings ' GNULIB_TOOL_FLAGS=' --avoid=close --avoid=dup - --avoid=fchdir --avoid=fstat + --avoid=fchdir --avoid=flexmember --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=open --avoid=openat-die --avoid=opendir --avoid=raise - --avoid=save-cwd --avoid=select --avoid=sigprocmask + --avoid=save-cwd --avoid=select --avoid=setenv --avoid=sigprocmask --avoid=stdarg --avoid=stdbool - --avoid=threadlib + --avoid=threadlib --avoid=unsetenv --conditional-dependencies --import --no-changelog --no-vc-files --makefile-name=gnulib.mk ' diff --git a/configure.ac b/configure.ac index b58c7de67a7..19b8b9d69ae 100644 --- a/configure.ac +++ b/configure.ac @@ -4000,7 +4000,7 @@ AC_SUBST(KRB4LIB) AC_CHECK_HEADERS(valgrind/valgrind.h) -AC_CHECK_FUNCS_ONCE(tzalloc tzset) +AC_CHECK_FUNCS_ONCE(tzset) ok_so_far=yes AC_CHECK_FUNC(socket, , ok_so_far=no) diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi index 0c39be9ad20..4b5a1b4a6ff 100644 --- a/doc/lispref/os.texi +++ b/doc/lispref/os.texi @@ -1234,7 +1234,7 @@ three-element lists, with omitted @var{microsec} and @var{picosec} components defaulting to zero. @cindex time value - Function arguments, e.g., the @var{time-value} argument to + Function arguments, e.g., the @var{time} argument to @code{current-time-string}, accept a more-general @dfn{time value} format, which can be a list of integers as above, or a single number for seconds since the epoch, or @code{nil} for the current time. You @@ -1244,7 +1244,7 @@ of integers using @code{seconds-to-time}, and into other forms using @code{decode-time} and @code{float-time}. These functions are described in the following sections. -@defun current-time-string &optional time-value +@defun current-time-string &optional time zone This function returns the current time and date as a human-readable string. The format does not vary for the initial part of the string, which contains the day of week, month, day of month, and time of day @@ -1255,8 +1255,9 @@ characters from the beginning of the string rather than from the end, as the year might not have exactly four digits, and additional information may some day be added at the end. -The argument @var{time-value}, if given, specifies a time to format, -instead of the current time. +The argument @var{time}, if given, specifies a time to format, +instead of the current time. The optional argument @var{zone} +defaults to the current time zone rule. @example @group @@ -1275,9 +1276,9 @@ multiple of 1000, but this may change as higher-resolution clocks become available. @end defun -@defun float-time &optional time-value +@defun float-time &optional time This function returns the current time as a floating-point number of -seconds since the epoch. The optional argument @var{time-value}, if +seconds since the epoch. The optional argument @var{time}, if given, specifies a time to convert instead of the current time. @emph{Warning}: Since the result is floating point, it may not be @@ -1286,14 +1287,14 @@ exact. Do not use this function if precise time stamps are required. @code{time-to-seconds} is an alias for this function. @end defun -@defun seconds-to-time time-value +@defun seconds-to-time time This function converts a time value to list-of-integer form. -For example, if @var{time-value} is a number, @code{(time-to-seconds -(seconds-to-time @var{time-value}))} equals the number unless overflow +For example, if @var{time} is a number, @code{(time-to-seconds +(seconds-to-time @var{time}))} equals the number unless overflow or rounding errors occur. @end defun -@defun current-time-zone &optional time-value +@defun current-time-zone &optional time zone @cindex time zone, current This function returns a list describing the time zone that the user is in. @@ -1309,15 +1310,27 @@ adjustment, then the value is constant through time. If the operating system doesn't supply all the information necessary to compute the value, the unknown elements of the list are @code{nil}. -The argument @var{time-value}, if given, specifies a time value to -analyze instead of the current time. +The argument @var{time}, if given, specifies a time value to +analyze instead of the current time. The optional argument @var{zone} +defaults to the current time zone rule. @end defun -The current time zone is determined by the @env{TZ} environment +@vindex TZ, environment variable +The default time zone is determined by the @env{TZ} environment variable. @xref{System Environment}. For example, you can tell Emacs -to use universal time with @code{(setenv "TZ" "UTC0")}. If @env{TZ} -is not in the environment, Emacs uses a platform-dependent default -time zone. +to default to universal time with @code{(setenv "TZ" "UTC0")}. If +@env{TZ} is not in the environment, Emacs uses system wall clock time, +which is a platform-dependent default time zone. + +@cindex time zone rule +Functions that convert to and from local time accept an optional +@dfn{time zone rule} argument, which specifies the conversion's time +zone and daylight saving time history. If the time zone rule is +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. @node Time Conversion @section Time Conversion @@ -1340,13 +1353,14 @@ count the number of years since the year 1 B.C., and do not skip zero as traditional Gregorian years do; for example, the year number @minus{}37 represents the Gregorian year 38 B.C@. -@defun decode-time &optional time-value +@defun decode-time &optional time zone This function converts a time value into calendrical information. If -you don't specify @var{time-value}, it decodes the current time. The return +you don't specify @var{time}, it decodes the current time, and similarly +@var{zone} defaults to the current time zone rule. The return value is a list of nine elements, as follows: @example -(@var{seconds} @var{minutes} @var{hour} @var{day} @var{month} @var{year} @var{dow} @var{dst} @var{zone}) +(@var{seconds} @var{minutes} @var{hour} @var{day} @var{month} @var{year} @var{dow} @var{dst} @var{utcoff}) @end example Here is what the elements mean: @@ -1370,13 +1384,13 @@ The day of week, as an integer between 0 and 6, where 0 stands for Sunday. @item dst @code{t} if daylight saving time is effect, otherwise @code{nil}. -@item zone -An integer indicating the time zone, as the number of seconds east of -Greenwich. +@item utcoff +An integer indicating the UTC offset in seconds, i.e., the number of +seconds east of Greenwich. @end table @strong{Common Lisp Note:} Common Lisp has different meanings for -@var{dow} and @var{zone}. +@var{dow} and @var{utcoff}. @end defun @defun encode-time seconds minutes hour day month year &optional zone @@ -1389,12 +1403,11 @@ Year numbers less than 100 are not treated specially. If you want them to stand for years above 1900, or years above 2000, you must alter them yourself before you call @code{encode-time}. -The optional argument @var{zone} defaults to the current time zone and -its daylight saving time rules. If specified, it can be either a list -(as you would get from @code{current-time-zone}), a string as in the -@env{TZ} environment variable, @code{t} for Universal Time, or an -integer (as you would get from @code{decode-time}). The specified -zone is used without any further alteration for daylight saving time. +The optional argument @var{zone} defaults to the current time zone rule. +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 @@ -1430,11 +1443,12 @@ This function parses the time-string @var{string} and returns the corresponding time value. @end defun -@defun format-time-string format-string &optional time-value universal +@defun format-time-string format-string &optional time zone -This function converts @var{time-value} (or the current time, if -@var{time-value} is omitted) to a string according to -@var{format-string}. The argument +This function converts @var{time} (or the current time, if +@var{time} is omitted) to a string according to +@var{format-string}. The conversion uses the time zone rule @var{zone} +(or the current time zone rule, if omitted). The argument @var{format-string} may contain @samp{%}-sequences which say to substitute parts of the time. Here is a table of what the @samp{%}-sequences mean: @@ -1014,6 +1014,15 @@ key works) by typing ‘A-[’ and ‘A-]’. +++ ** Time-related changes: +*** Time conversion functions now accept an optional ZONE argument +that specifies the time zone rules for conversion. ZONE is omitted or +nil for Emacs local time, t for Universal Time, ‘wall’ for system wall +clock time, or a string as in ‘set-time-zone-rule’ for a time zone +rule. The affected functions are ‘current-time-string’, +‘current-time-zone’, ‘decode-time’, and ‘format-time-string’. The +function ‘encode-time’, which already accepted a simple time zone rule +argument, has been extended to accept all the new forms. + *** Time-related functions now consistently accept numbers (representing seconds since the epoch) and nil (representing the current time) as well as the usual list-of-integer representation. diff --git a/lib/gnulib.mk b/lib/gnulib.mk index 2dd0ef82c3c..1ca12a28beb 100644 --- a/lib/gnulib.mk +++ b/lib/gnulib.mk @@ -21,7 +21,7 @@ # the same distribution terms as the rest of that program. # # Generated by gnulib-tool. -# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=close --avoid=dup --avoid=fchdir --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=open --avoid=openat-die --avoid=opendir --avoid=raise --avoid=save-cwd --avoid=select --avoid=sigprocmask --avoid=stdarg --avoid=stdbool --avoid=threadlib --makefile-name=gnulib.mk --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt binary-io byteswap c-ctype c-strcase careadlinkat close-stream count-one-bits count-trailing-zeros crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 dtoastr dtotimespec dup2 environ execinfo faccessat fcntl fcntl-h fdatasync fdopendir filemode fstatat fsync getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog intprops largefile lstat manywarnings memrchr mkostemp mktime pipe2 pselect pthread_sigmask putenv qcopy-acl readlink readlinkat sig2str socklen stat-time stdalign stddef stdio stpcpy strftime strtoimax strtoumax symlink sys_stat sys_time time time_r timer-time timespec-add timespec-sub unsetenv update-copyright utimens vla warnings +# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=close --avoid=dup --avoid=fchdir --avoid=flexmember --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=open --avoid=openat-die --avoid=opendir --avoid=raise --avoid=save-cwd --avoid=select --avoid=setenv --avoid=sigprocmask --avoid=stdarg --avoid=stdbool --avoid=threadlib --avoid=unsetenv --makefile-name=gnulib.mk --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt binary-io byteswap c-ctype c-strcase careadlinkat close-stream count-one-bits count-trailing-zeros crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 dtoastr dtotimespec dup2 environ execinfo faccessat fcntl fcntl-h fdatasync fdopendir filemode fstatat fsync getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog intprops largefile lstat manywarnings memrchr mkostemp mktime pipe2 pselect pthread_sigmask putenv qcopy-acl readlink readlinkat sig2str socklen stat-time stdalign stddef stdio stpcpy strftime strtoimax strtoumax symlink sys_stat sys_time time time_r time_rz timegm timer-time timespec-add timespec-sub unsetenv update-copyright utimens vla warnings MOSTLYCLEANFILES += core *.stackdump @@ -655,6 +655,17 @@ EXTRA_libgnu_a_SOURCES += mktime.c ## end gnulib module mktime +## begin gnulib module mktime-internal + +if gl_GNULIB_ENABLED_5264294aa0a5557541b53c8c741f7f31 + +endif +EXTRA_DIST += mktime-internal.h mktime.c + +EXTRA_libgnu_a_SOURCES += mktime.c + +## end gnulib module mktime-internal + ## begin gnulib module openat-h if gl_GNULIB_ENABLED_03e0aaad4cb89ca757653bd367a6ccb7 @@ -1589,10 +1600,12 @@ time.h: time.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $( -e 's/@''GNULIB_STRPTIME''@/$(GNULIB_STRPTIME)/g' \ -e 's/@''GNULIB_TIMEGM''@/$(GNULIB_TIMEGM)/g' \ -e 's/@''GNULIB_TIME_R''@/$(GNULIB_TIME_R)/g' \ + -e 's/@''GNULIB_TIME_RZ''@/$(GNULIB_TIME_RZ)/g' \ -e 's|@''HAVE_DECL_LOCALTIME_R''@|$(HAVE_DECL_LOCALTIME_R)|g' \ -e 's|@''HAVE_NANOSLEEP''@|$(HAVE_NANOSLEEP)|g' \ -e 's|@''HAVE_STRPTIME''@|$(HAVE_STRPTIME)|g' \ -e 's|@''HAVE_TIMEGM''@|$(HAVE_TIMEGM)|g' \ + -e 's|@''HAVE_TIMEZONE_T''@|$(HAVE_TIMEZONE_T)|g' \ -e 's|@''REPLACE_GMTIME''@|$(REPLACE_GMTIME)|g' \ -e 's|@''REPLACE_LOCALTIME''@|$(REPLACE_LOCALTIME)|g' \ -e 's|@''REPLACE_LOCALTIME_R''@|$(REPLACE_LOCALTIME_R)|g' \ @@ -1624,6 +1637,24 @@ EXTRA_libgnu_a_SOURCES += time_r.c ## end gnulib module time_r +## begin gnulib module time_rz + + +EXTRA_DIST += time_rz.c + +EXTRA_libgnu_a_SOURCES += time_rz.c + +## end gnulib module time_rz + +## begin gnulib module timegm + + +EXTRA_DIST += mktime-internal.h timegm.c + +EXTRA_libgnu_a_SOURCES += timegm.c + +## end gnulib module timegm + ## begin gnulib module timespec libgnu_a_SOURCES += timespec.c @@ -1806,15 +1837,6 @@ EXTRA_DIST += unistd.in.h ## end gnulib module unistd -## begin gnulib module unsetenv - - -EXTRA_DIST += unsetenv.c - -EXTRA_libgnu_a_SOURCES += unsetenv.c - -## end gnulib module unsetenv - ## begin gnulib module update-copyright diff --git a/lib/strftime.c b/lib/strftime.c index 2426aae7052..c7cec2621c9 100644 --- a/lib/strftime.c +++ b/lib/strftime.c @@ -121,22 +121,11 @@ extern char *tzname[]; #ifdef _LIBC +# define mktime_z(tz, tm) mktime (tm) # define tzname __tzname # define tzset __tzset #endif -#if !HAVE_TM_GMTOFF -/* Portable standalone applications should supply a "time.h" that - declares a POSIX-compliant localtime_r, for the benefit of older - implementations that lack localtime_r or have a nonstandard one. - See the gnulib time_r module for one way to implement this. */ -# undef __gmtime_r -# undef __localtime_r -# define __gmtime_r gmtime_r -# define __localtime_r localtime_r -#endif - - #ifndef FPRINTFTIME # define FPRINTFTIME 0 #endif @@ -385,12 +374,7 @@ iso_week_days (int yday, int wday) /* When compiling this file, GNU applications can #define my_strftime to a symbol (typically nstrftime) to get an extended strftime with - extra arguments UT and NS. Emacs is a special case for now, but - this Emacs-specific code can be removed once Emacs's config.h - defines my_strftime. */ -#if defined emacs && !defined my_strftime -# define my_strftime nstrftime -#endif + extra arguments TZ and NS. */ #if FPRINTFTIME # undef my_strftime @@ -398,8 +382,9 @@ iso_week_days (int yday, int wday) #endif #ifdef my_strftime -# define extra_args , ut, ns -# define extra_args_spec , int ut, int ns +# undef HAVE_TZSET +# define extra_args , tz, ns +# define extra_args_spec , timezone_t tz, int ns #else # if defined COMPILE_WIDE # define my_strftime wcsftime @@ -411,7 +396,7 @@ iso_week_days (int yday, int wday) # define extra_args # define extra_args_spec /* We don't have this information in general. */ -# define ut 0 +# define tz 1 # define ns 0 #endif @@ -483,7 +468,7 @@ strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, zone = (const char *) tp->tm_zone; #endif #if HAVE_TZNAME - if (ut) + if (!tz) { if (! (zone && *zone)) zone = "GMT"; @@ -496,7 +481,12 @@ strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, tzset (); # endif } + /* The tzset() call might have changed the value. */ + if (!(zone && *zone) && tp->tm_isdst >= 0) + zone = tzname[tp->tm_isdst != 0]; #endif + if (! zone) + zone = ""; if (hour12 > 12) hour12 -= 12; @@ -1144,7 +1134,7 @@ strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, time_t t; ltm = *tp; - t = mktime (<m); + t = mktime_z (tz, <m); /* Generate string value for T using time_t arithmetic; this works even if sizeof (long) < sizeof (time_t). */ @@ -1319,14 +1309,6 @@ strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, to_lowcase = true; } -#if HAVE_TZNAME - /* The tzset() call might have changed the value. */ - if (!(zone && *zone) && tp->tm_isdst >= 0) - zone = tzname[tp->tm_isdst != 0]; -#endif - if (! zone) - zone = ""; - #ifdef COMPILE_WIDE { /* The zone string is always given in multibyte form. We have @@ -1366,7 +1348,7 @@ strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, #if HAVE_TM_GMTOFF diff = tp->tm_gmtoff; #else - if (ut) + if (!tz) diff = 0; else { @@ -1375,7 +1357,7 @@ strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, time_t lt; ltm = *tp; - lt = mktime (<m); + lt = mktime_z (tz, <m); if (lt == (time_t) -1) { @@ -1384,7 +1366,7 @@ strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, occurred. */ struct tm tm; - if (! __localtime_r (<, &tm) + if (! localtime_rz (tz, <, &tm) || ((ltm.tm_sec ^ tm.tm_sec) | (ltm.tm_min ^ tm.tm_min) | (ltm.tm_hour ^ tm.tm_hour) @@ -1394,7 +1376,7 @@ strftime_case_ (bool upcase, STREAM_OR_CHAR_T *s, break; } - if (! __gmtime_r (<, >m)) + if (! localtime_rz (0, <, >m)) break; diff = tm_diff (<m, >m); @@ -1473,15 +1455,3 @@ my_strftime (STREAM_OR_CHAR_T *s, STRFTIME_ARG (size_t maxsize) #if defined _LIBC && ! FPRINTFTIME libc_hidden_def (my_strftime) #endif - - -#if defined emacs && ! FPRINTFTIME -/* For Emacs we have a separate interface which corresponds to the normal - strftime function plus the ut argument, but without the ns argument. */ -size_t -emacs_strftimeu (char *s, size_t maxsize, const char *format, - const struct tm *tp, int ut) -{ - return my_strftime (s, maxsize, format, tp, ut, 0); -} -#endif diff --git a/lib/strftime.h b/lib/strftime.h index 3967afc4941..2ce6cc57687 100644 --- a/lib/strftime.h +++ b/lib/strftime.h @@ -23,11 +23,10 @@ extern "C" { /* Just like strftime, but with two more arguments: POSIX requires that strftime use the local timezone information. - When __UTC is nonzero and tm->tm_zone is NULL or the empty string, - use UTC instead. Use __NS as the number of nanoseconds in the - %N directive. */ + Use the timezone __TZ instead. Use __NS as the number of + nanoseconds in the %N directive. */ size_t nstrftime (char *, size_t, char const *, struct tm const *, - int __utc, int __ns); + timezone_t __tz, int __ns); #ifdef __cplusplus } diff --git a/lib/time.in.h b/lib/time.in.h index 1a6b746ccab..a983f498e51 100644 --- a/lib/time.in.h +++ b/lib/time.in.h @@ -231,6 +231,25 @@ _GL_CXXALIAS_SYS (strptime, char *, (char const *restrict __buf, _GL_CXXALIASWARN (strptime); # endif +# if defined _GNU_SOURCE && @GNULIB_TIME_RZ@ && ! @HAVE_TIMEZONE_T@ +typedef struct tm_zone *timezone_t; +_GL_FUNCDECL_SYS (tzalloc, timezone_t, (char const *__name)); +_GL_CXXALIAS_SYS (tzalloc, timezone_t, (char const *__name)); +_GL_FUNCDECL_SYS (tzfree, void, (timezone_t __tz)); +_GL_CXXALIAS_SYS (tzfree, void, (timezone_t __tz)); +_GL_FUNCDECL_SYS (localtime_rz, struct tm *, + (timezone_t __tz, time_t const *restrict __timer, + struct tm *restrict __result) _GL_ARG_NONNULL ((2, 3))); +_GL_CXXALIAS_SYS (localtime_rz, struct tm *, + (timezone_t __tz, time_t const *restrict __timer, + struct tm *restrict __result)); +_GL_FUNCDECL_SYS (mktime_z, time_t, + (timezone_t __tz, struct tm *restrict __result) + _GL_ARG_NONNULL ((2))); +_GL_CXXALIAS_SYS (mktime_z, time_t, + (timezone_t __tz, struct tm *restrict __result)); +# endif + /* Convert TM to a time_t value, assuming UTC. */ # if @GNULIB_TIMEGM@ # if @REPLACE_TIMEGM@ diff --git a/lib/time_rz.c b/lib/time_rz.c new file mode 100644 index 00000000000..8a4d7d13a2c --- /dev/null +++ b/lib/time_rz.c @@ -0,0 +1,374 @@ +/* Time zone functions such as tzalloc and localtime_rz + + Copyright 2015 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, see <http://www.gnu.org/licenses/>. */ + +/* Written by Paul Eggert. */ + +/* Although this module is not thread-safe, any races should be fairly + rare and reasonably benign. For complete thread-safety, use a C + library with a working timezone_t type, so that this module is not + needed. */ + +#include <config.h> + +#include <time.h> + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#if !HAVE_TZSET +static void tzset (void) { } +#endif + +/* A time zone rule. */ +struct tm_zone +{ + /* More abbreviations, should they be needed. Their TZ_IS_SET + members are zero. */ + timezone_t next; + + /* If nonzero, the rule represents the TZ environment variable set + to the first "abbreviation" (this may be the empty string). + Otherwise, it represents an unset TZ. */ + char tz_is_set; + + /* A sequence of null-terminated strings packed next to each other. + The strings are followed by an extra null byte. If TZ_IS_SET, + there must be at least one string and the first string (which is + actually a TZ environment value value) may be empty. Otherwise + all strings must be nonempty. + + Abbreviations are stored here because otherwise the values of + tm_zone and/or tzname would be dead after changing TZ and calling + tzset. Abbreviations never move once allocated, and are live + until tzfree is called. */ + char abbrs[FLEXIBLE_ARRAY_MEMBER]; +}; + +/* The approximate size to use for small allocation requests. This is + the largest "small" request for the GNU C library malloc. */ +enum { DEFAULT_MXFAST = 64 * sizeof (size_t) / 4 }; + +/* Minimum size of the ABBRS member of struct abbr. ABBRS is larger + only in the unlikely case where an abbreviation longer than this is + used. */ +enum { ABBR_SIZE_MIN = DEFAULT_MXFAST - offsetof (struct tm_zone, abbrs) }; + +static char const TZ[] = "TZ"; + +/* Magic cookie timezone_t value, for local time. It differs from + NULL and from all other timezone_t values. Only the address + matters; the pointer is never dereferenced. */ +static timezone_t const local_tz = (timezone_t) 1; + +#if HAVE_TM_ZONE || HAVE_TZNAME + +/* Return true if the values A and B differ according to the rules for + tm_isdst: A and B differ if one is zero and the other positive. */ +static bool +isdst_differ (int a, int b) +{ + return !a != !b && 0 <= a && 0 <= b; +} + +/* Return true if A and B are equal. */ +static int +equal_tm (const struct tm *a, const struct tm *b) +{ + return ! ((a->tm_sec ^ b->tm_sec) + | (a->tm_min ^ b->tm_min) + | (a->tm_hour ^ b->tm_hour) + | (a->tm_mday ^ b->tm_mday) + | (a->tm_mon ^ b->tm_mon) + | (a->tm_year ^ b->tm_year) + | isdst_differ (a->tm_isdst, b->tm_isdst)); +} + +#endif + +/* Copy to ABBRS the abbreviation at ABBR with size ABBR_SIZE (this + includes its trailing null byte). Append an extra null byte to + mark the end of ABBRS. */ +static void +extend_abbrs (char *abbrs, char const *abbr, size_t abbr_size) +{ + memcpy (abbrs, abbr, abbr_size); + abbrs[abbr_size] = '\0'; +} + +/* Return a newly allocated time zone for NAME, or NULL on failure. + As a special case, return a nonzero constant for wall clock time, a + constant that survives freeing. */ +timezone_t +tzalloc (char const *name) +{ + size_t name_size = name ? strlen (name) + 1 : 0; + size_t abbr_size = name_size < ABBR_SIZE_MIN ? ABBR_SIZE_MIN : name_size + 1; + timezone_t tz = malloc (offsetof (struct tm_zone, abbrs) + abbr_size); + if (tz) + { + tz->next = NULL; + tz->tz_is_set = !!name; + extend_abbrs (tz->abbrs, name, name_size); + } + return tz; +} + +#if HAVE_TZNAME +/* If TZNAME_ADDRESS is nonnull, an assignment of a saved abbreviation. + TZNAME_ADDRESS should be either null, or &tzname[0], or &tzname[1]. + *TZNAME_ADDRESS = TZNAME_VALUE should be done after revert_tz + (indirectly) calls tzset, so that revert_tz can overwrite tzset's + assignment to tzname. Also, it should be done at the start of + the next localtime_tz or mktime_z, to undo the overwrite. */ +static char **tzname_address; +static char *tzname_value; +#endif + +/* Save into TZ any nontrivial time zone abbreviation used by TM, + and update *TM (or prepare to update tzname) if they use the abbreviation. + Return true if successful, false (setting errno) otherwise. */ +static bool +save_abbr (timezone_t tz, struct tm *tm) +{ +#if HAVE_TM_ZONE || HAVE_TZNAME + char const *zone = NULL; + char **tzname_zone = NULL; + char *zone_copy = (char *) ""; +# if HAVE_TM_ZONE + zone = tm->tm_zone; +# endif +# if HAVE_TZNAME + if (! (zone && *zone) && 0 <= tm->tm_isdst) + zone = *(tzname_zone = &tzname[0 < tm->tm_isdst]); +# endif + + /* No need to replace null zones, or zones within the struct tm. */ + if (!zone || ((char *) tm <= zone && zone < (char *) (tm + 1))) + return true; + + if (*zone) + { + zone_copy = tz->abbrs; + + while (strcmp (zone_copy, zone) != 0) + { + if (! (*zone_copy || (zone_copy == tz->abbrs && tz->tz_is_set))) + { + size_t zone_size = strlen (zone) + 1; + if (zone_size < tz->abbrs + ABBR_SIZE_MIN - zone_copy) + extend_abbrs (zone_copy, zone, zone_size); + else + { + tz = tz->next = tzalloc (zone); + if (!tz) + return false; + tz->tz_is_set = 0; + zone_copy = tz->abbrs; + } + break; + } + + zone_copy += strlen (zone_copy) + 1; + if (!*zone_copy && tz->next) + { + tz = tz->next; + zone_copy = tz->abbrs; + } + } + } + + /* Replace the zone name so that its lifetime matches that of TZ. */ +# if HAVE_TM_ZONE + if (!tzname_zone) + tm->tm_zone = zone_copy; +# endif +# if HAVE_TZNAME + tzname_address = tzname_zone; + tzname_value = zone_copy; +# endif +#endif + return true; +} + +/* Free a time zone. */ +void +tzfree (timezone_t tz) +{ + if (tz != local_tz) + while (tz) + { + timezone_t next = tz->next; + free (tz); + tz = next; + } +} + +/* Get and set the TZ environment variable. These functions can be + overridden by programs like Emacs that manage their own environment. */ + +#ifndef getenv_TZ +static char * +getenv_TZ (void) +{ + return getenv (TZ); +} +#endif + +#ifndef setenv_TZ +static int +setenv_TZ (char const *tz) +{ + return tz ? setenv (TZ, tz, 1) : unsetenv (TZ); +} +#endif + +/* Change the environment to match the specified timezone_t value. + Return true if successful, false (setting errno) otherwise. */ +static bool +change_env (timezone_t tz) +{ + if (setenv_TZ (tz->tz_is_set ? tz->abbrs : NULL) != 0) + return false; + tzset (); + return true; +} + +/* Temporarily set the time zone to TZ, which must not be null. + Return LOCAL_TZ if the time zone setting is already correct. + Otherwise return a newly allocated time zone representing the old + setting, or NULL (setting errno) on failure. */ +static timezone_t +set_tz (timezone_t tz) +{ + char *env_tz = getenv_TZ (); + if (env_tz + ? tz->tz_is_set && strcmp (tz->abbrs, env_tz) == 0 + : !tz->tz_is_set) + return local_tz; + else + { + timezone_t old_tz = tzalloc (env_tz); + if (!old_tz) + return old_tz; + if (! change_env (tz)) + { + int saved_errno = errno; + tzfree (old_tz); + errno = saved_errno; + return NULL; + } + return old_tz; + } +} + +/* Restore an old setting returned by set_tz. It must not be null. + Return true (preserving errno) if successful, false (setting errno) + otherwise. */ +static bool +revert_tz (timezone_t tz) +{ + if (tz == local_tz) + return true; + else + { + int saved_errno = errno; + bool ok = change_env (tz); + if (!ok) + saved_errno = errno; +#if HAVE_TZNAME + if (!ok) + tzname_address = NULL; + if (tzname_address) + { + char *old_value = *tzname_address; + *tzname_address = tzname_value; + tzname_value = old_value; + } +#endif + tzfree (tz); + errno = saved_errno; + return ok; + } +} + +/* Restore an old tzname setting that was temporarily munged by revert_tz. */ +static void +restore_tzname (void) +{ +#if HAVE_TZNAME + if (tzname_address) + { + *tzname_address = tzname_value; + tzname_address = NULL; + } +#endif +} + +/* Use time zone TZ to compute localtime_r (T, TM). */ +struct tm * +localtime_rz (timezone_t tz, time_t const *t, struct tm *tm) +{ + restore_tzname (); + + if (!tz) + return gmtime_r (t, tm); + else + { + timezone_t old_tz = set_tz (tz); + if (old_tz) + { + tm = localtime_r (t, tm); + if (tm && !save_abbr (tz, tm)) + tm = NULL; + if (revert_tz (old_tz)) + return tm; + } + return NULL; + } +} + +/* Use time zone TZ to compute mktime (TM). */ +time_t +mktime_z (timezone_t tz, struct tm *tm) +{ + restore_tzname (); + + if (!tz) + return timegm (tm); + else + { + timezone_t old_tz = set_tz (tz); + if (old_tz) + { + time_t t = mktime (tm); +#if HAVE_TM_ZONE || HAVE_TZNAME + time_t badtime = -1; + struct tm tm_1; + if ((t != badtime + || (localtime_r (&t, &tm_1) && equal_tm (tm, &tm_1))) + && !save_abbr (tz, tm)) + t = badtime; +#endif + if (revert_tz (old_tz)) + return t; + } + return -1; + } +} diff --git a/lib/timegm.c b/lib/timegm.c new file mode 100644 index 00000000000..11c485ff05a --- /dev/null +++ b/lib/timegm.c @@ -0,0 +1,38 @@ +/* Convert UTC calendar time to simple time. Like mktime but assumes UTC. + + Copyright (C) 1994, 1997, 2003-2004, 2006-2007, 2009-2015 Free Software + Foundation, Inc. This file is part of the GNU C Library. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. */ + +#ifndef _LIBC +# include <config.h> +#endif + +#include <time.h> + +#ifndef _LIBC +# undef __gmtime_r +# define __gmtime_r gmtime_r +# define __mktime_internal mktime_internal +# include "mktime-internal.h" +#endif + +time_t +timegm (struct tm *tmp) +{ + static time_t gmtime_offset; + tmp->tm_isdst = 0; + return __mktime_internal (tmp, __gmtime_r, &gmtime_offset); +} diff --git a/lisp/time-stamp.el b/lisp/time-stamp.el index 24e5ef47d29..1e0fe9919d2 100644 --- a/lisp/time-stamp.el +++ b/lisp/time-stamp.el @@ -420,16 +420,8 @@ format the string." (or ts-format (setq ts-format time-stamp-format)) (if (stringp ts-format) - (if (stringp time-stamp-time-zone) - (let ((ts-real-time-zone (getenv "TZ"))) - (unwind-protect - (progn - (setenv "TZ" time-stamp-time-zone) - (format-time-string - (time-stamp-string-preprocess ts-format))) - (setenv "TZ" ts-real-time-zone))) - (format-time-string - (time-stamp-string-preprocess ts-format))) + (format-time-string (time-stamp-string-preprocess ts-format) + nil time-stamp-time-zone) ;; handle version 1 compatibility (cond ((or (eq time-stamp-old-format-warn 'error) (and (eq time-stamp-old-format-warn 'ask) diff --git a/lisp/time.el b/lisp/time.el index ae0e598b64c..d35f5b93964 100644 --- a/lisp/time.el +++ b/lisp/time.el @@ -160,15 +160,8 @@ LABEL is a string to display as the label of that TIMEZONE's time." (defcustom display-time-world-list ;; Determine if zoneinfo style timezones are supported by testing that ;; America/New York and Europe/London return different timezones. - (let ((old-tz (getenv "TZ")) - gmt nyt) - (unwind-protect - (progn - (setenv "TZ" "America/New_York") - (setq nyt (format-time-string "%z")) - (setenv "TZ" "Europe/London") - (setq gmt (format-time-string "%z"))) - (setenv "TZ" old-tz)) + (let ((nyt (format-time-string "%z" nil "America/New_York")) + (gmt (format-time-string "%z" nil "Europe/London"))) (if (string-equal nyt gmt) legacy-style-world-list zoneinfo-style-world-list)) @@ -523,21 +516,19 @@ See `display-time-world'." "Replace current buffer text with times in various zones, based on ALIST." (let ((inhibit-read-only t) (buffer-undo-list t) - (old-tz (getenv "TZ")) + (now (current-time)) (max-width 0) result fmt) (erase-buffer) - (unwind-protect - (dolist (zone alist) - (let* ((label (cadr zone)) - (width (string-width label))) - (setenv "TZ" (car zone)) - (push (cons label - (format-time-string display-time-world-time-format)) - result) - (when (> width max-width) - (setq max-width width)))) - (setenv "TZ" old-tz)) + (dolist (zone alist) + (let* ((label (cadr zone)) + (width (string-width label))) + (push (cons label + (format-time-string display-time-world-time-format + now (car zone))) + result) + (when (> width max-width) + (setq max-width width)))) (setq fmt (concat "%-" (int-to-string max-width) "s %s\n")) (dolist (timedata (nreverse result)) (insert (format fmt (car timedata) (cdr timedata)))) diff --git a/lisp/vc/add-log.el b/lisp/vc/add-log.el index eb7e5bfdfad..c90413c42c2 100644 --- a/lisp/vc/add-log.el +++ b/lisp/vc/add-log.el @@ -581,8 +581,8 @@ If t, use universal time.") (put 'add-log-time-zone-rule 'safe-local-variable (lambda (x) (or (booleanp x) (stringp x)))) -(defun add-log-iso8601-time-zone (&optional time) - (let* ((utc-offset (or (car (current-time-zone time)) 0)) +(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)) @@ -596,12 +596,11 @@ If t, use universal time.") (defvar add-log-iso8601-with-time-zone nil) -(defun add-log-iso8601-time-string () - (let ((time (format-time-string "%Y-%m-%d" - nil (eq t add-log-time-zone-rule)))) +(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 time " " (add-log-iso8601-time-zone)) - time))) + (concat date " " (add-log-iso8601-time-zone time zone)) + date))) (defun change-log-name () "Return (system-dependent) default name for a change log file." @@ -848,14 +847,8 @@ non-nil, otherwise in local time." (let ((new-entries (mapcar (lambda (addr) (concat - (if (stringp add-log-time-zone-rule) - (let ((tz (getenv "TZ"))) - (unwind-protect - (progn - (setenv "TZ" add-log-time-zone-rule) - (funcall add-log-time-format)) - (setenv "TZ" tz))) - (funcall add-log-time-format)) + (funcall add-log-time-format + nil add-log-time-zone-rule) " " full-name " <" addr ">")) (if (consp mailing-address) diff --git a/lisp/vc/log-edit.el b/lisp/vc/log-edit.el index d59549772c0..acbd9c0cd9f 100644 --- a/lisp/vc/log-edit.el +++ b/lisp/vc/log-edit.el @@ -872,7 +872,8 @@ Return non-nil if it is." (and (boundp 'user-mail-address) user-mail-address))) (time (or (and (boundp 'add-log-time-format) (functionp add-log-time-format) - (funcall add-log-time-format)) + (funcall add-log-time-format + nil add-log-time-zone-rule)) (format-time-string "%Y-%m-%d")))) (if (null log-edit-changelog-use-first) (looking-at (regexp-quote (format "%s %s <%s>" time name mail))) diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4 index 0425d02241a..cf71d7eb187 100644 --- a/m4/gnulib-comp.m4 +++ b/m4/gnulib-comp.m4 @@ -98,6 +98,7 @@ AC_DEFUN([gl_EARLY], # Code from module memrchr: # Code from module mkostemp: # Code from module mktime: + # Code from module mktime-internal: # Code from module multiarch: # Code from module nocrash: # Code from module openat-h: @@ -141,13 +142,14 @@ AC_DEFUN([gl_EARLY], # Code from module tempname: # Code from module time: # Code from module time_r: + # Code from module time_rz: + # Code from module timegm: # Code from module timer-time: # Code from module timespec: # Code from module timespec-add: # Code from module timespec-sub: # Code from module u64: # Code from module unistd: - # Code from module unsetenv: # Code from module update-copyright: # Code from module utimens: # Code from module vararrays: @@ -385,15 +387,20 @@ AC_DEFUN([gl_INIT], gl_PREREQ_TIME_R fi gl_TIME_MODULE_INDICATOR([time_r]) + gl_TIME_RZ + if test "$HAVE_TIMEZONE_T" = 0; then + AC_LIBOBJ([time_rz]) + fi + gl_TIME_MODULE_INDICATOR([time_rz]) + gl_FUNC_TIMEGM + if test $HAVE_TIMEGM = 0 || test $REPLACE_TIMEGM = 1; then + AC_LIBOBJ([timegm]) + gl_PREREQ_TIMEGM + fi + gl_TIME_MODULE_INDICATOR([timegm]) gl_TIMER_TIME gl_TIMESPEC gl_UNISTD_H - gl_FUNC_UNSETENV - if test $HAVE_UNSETENV = 0 || test $REPLACE_UNSETENV = 1; then - AC_LIBOBJ([unsetenv]) - gl_PREREQ_UNSETENV - fi - gl_STDLIB_MODULE_INDICATOR([unsetenv]) gl_UTIMENS AC_C_VARARRAYS gl_gnulib_enabled_260941c0e5dc67ec9e87d1fb321c300b=false @@ -404,6 +411,7 @@ AC_DEFUN([gl_INIT], gl_gnulib_enabled_getgroups=false gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36=false gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1=false + gl_gnulib_enabled_5264294aa0a5557541b53c8c741f7f31=false gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7=false gl_gnulib_enabled_pathmax=false gl_gnulib_enabled_6099e9737f757db36c47fa9d9f02e88c=false @@ -506,6 +514,17 @@ AC_DEFUN([gl_INIT], fi fi } + func_gl_gnulib_m4code_5264294aa0a5557541b53c8c741f7f31 () + { + if ! $gl_gnulib_enabled_5264294aa0a5557541b53c8c741f7f31; then + gl_FUNC_MKTIME_INTERNAL + if test $REPLACE_MKTIME = 1; then + AC_LIBOBJ([mktime]) + gl_PREREQ_MKTIME + fi + gl_gnulib_enabled_5264294aa0a5557541b53c8c741f7f31=true + fi + } func_gl_gnulib_m4code_03e0aaad4cb89ca757653bd367a6ccb7 () { if ! $gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7; then @@ -653,6 +672,9 @@ AC_DEFUN([gl_INIT], if { test $HAVE_DECL_STRTOUMAX = 0 || test $REPLACE_STRTOUMAX = 1; } && test $ac_cv_type_unsigned_long_long_int = yes; then func_gl_gnulib_m4code_strtoull fi + if test $HAVE_TIMEGM = 0 || test $REPLACE_TIMEGM = 1; then + func_gl_gnulib_m4code_5264294aa0a5557541b53c8c741f7f31 + fi m4_pattern_allow([^gl_GNULIB_ENABLED_]) AM_CONDITIONAL([gl_GNULIB_ENABLED_260941c0e5dc67ec9e87d1fb321c300b], [$gl_gnulib_enabled_260941c0e5dc67ec9e87d1fb321c300b]) AM_CONDITIONAL([gl_GNULIB_ENABLED_dirfd], [$gl_gnulib_enabled_dirfd]) @@ -662,6 +684,7 @@ AC_DEFUN([gl_INIT], AM_CONDITIONAL([gl_GNULIB_ENABLED_getgroups], [$gl_gnulib_enabled_getgroups]) AM_CONDITIONAL([gl_GNULIB_ENABLED_be453cec5eecf5731a274f2de7f2db36], [$gl_gnulib_enabled_be453cec5eecf5731a274f2de7f2db36]) AM_CONDITIONAL([gl_GNULIB_ENABLED_a9786850e999ae65a836a6041e8e5ed1], [$gl_gnulib_enabled_a9786850e999ae65a836a6041e8e5ed1]) + AM_CONDITIONAL([gl_GNULIB_ENABLED_5264294aa0a5557541b53c8c741f7f31], [$gl_gnulib_enabled_5264294aa0a5557541b53c8c741f7f31]) AM_CONDITIONAL([gl_GNULIB_ENABLED_03e0aaad4cb89ca757653bd367a6ccb7], [$gl_gnulib_enabled_03e0aaad4cb89ca757653bd367a6ccb7]) AM_CONDITIONAL([gl_GNULIB_ENABLED_pathmax], [$gl_gnulib_enabled_pathmax]) AM_CONDITIONAL([gl_GNULIB_ENABLED_6099e9737f757db36c47fa9d9f02e88c], [$gl_gnulib_enabled_6099e9737f757db36c47fa9d9f02e88c]) @@ -938,6 +961,8 @@ AC_DEFUN([gl_FILE_LIST], [ lib/tempname.h lib/time.in.h lib/time_r.c + lib/time_rz.c + lib/timegm.c lib/timespec-add.c lib/timespec-sub.c lib/timespec.c @@ -946,7 +971,6 @@ AC_DEFUN([gl_FILE_LIST], [ lib/u64.h lib/unistd.c lib/unistd.in.h - lib/unsetenv.c lib/utimens.c lib/utimens.h lib/verify.h @@ -1011,7 +1035,6 @@ AC_DEFUN([gl_FILE_LIST], [ m4/readlink.m4 m4/readlinkat.m4 m4/secure_getenv.m4 - m4/setenv.m4 m4/sha1.m4 m4/sha256.m4 m4/sha512.m4 @@ -1043,6 +1066,8 @@ AC_DEFUN([gl_FILE_LIST], [ m4/tempname.m4 m4/time_h.m4 m4/time_r.m4 + m4/time_rz.m4 + m4/timegm.m4 m4/timer_time.m4 m4/timespec.m4 m4/tm_gmtoff.m4 diff --git a/m4/sys_time_h.m4 b/m4/sys_time_h.m4 index 50133b9ff9a..28c8b1acbba 100644 --- a/m4/sys_time_h.m4 +++ b/m4/sys_time_h.m4 @@ -105,6 +105,7 @@ AC_DEFUN([gl_HEADER_SYS_TIME_H_DEFAULTS], HAVE_GETTIMEOFDAY=1; AC_SUBST([HAVE_GETTIMEOFDAY]) HAVE_STRUCT_TIMEVAL=1; AC_SUBST([HAVE_STRUCT_TIMEVAL]) HAVE_SYS_TIME_H=1; AC_SUBST([HAVE_SYS_TIME_H]) + HAVE_TIMEZONE_T=0; AC_SUBST([HAVE_TIMEZONE_T]) REPLACE_GETTIMEOFDAY=0; AC_SUBST([REPLACE_GETTIMEOFDAY]) REPLACE_STRUCT_TIMEVAL=0; AC_SUBST([REPLACE_STRUCT_TIMEVAL]) ]) diff --git a/m4/time_h.m4 b/m4/time_h.m4 index d9c41a48f33..754b469a0d9 100644 --- a/m4/time_h.m4 +++ b/m4/time_h.m4 @@ -109,6 +109,7 @@ AC_DEFUN([gl_HEADER_TIME_H_DEFAULTS], GNULIB_STRPTIME=0; AC_SUBST([GNULIB_STRPTIME]) GNULIB_TIMEGM=0; AC_SUBST([GNULIB_TIMEGM]) GNULIB_TIME_R=0; AC_SUBST([GNULIB_TIME_R]) + GNULIB_TIME_RZ=0; AC_SUBST([GNULIB_TIME_RZ]) dnl Assume proper GNU behavior unless another module says otherwise. HAVE_DECL_LOCALTIME_R=1; AC_SUBST([HAVE_DECL_LOCALTIME_R]) HAVE_NANOSLEEP=1; AC_SUBST([HAVE_NANOSLEEP]) diff --git a/m4/time_rz.m4 b/m4/time_rz.m4 new file mode 100644 index 00000000000..0c1f2c3736c --- /dev/null +++ b/m4/time_rz.m4 @@ -0,0 +1,21 @@ +dnl Time zone functions: tzalloc, localtime_rz, etc. + +dnl Copyright (C) 2015 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl Written by Paul Eggert. + +AC_DEFUN([gl_TIME_RZ], +[ + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + AC_REQUIRE([gl_HEADER_SYS_TIME_H_DEFAULTS]) + AC_REQUIRE([AC_STRUCT_TIMEZONE]) + AC_CHECK_FUNCS_ONCE([tzset]) + + AC_CHECK_TYPES([timezone_t], [], [], [[#include <time.h>]]) + if test "$ac_cv_type_timezone_t" = yes; then + HAVE_TIMEZONE_T=1 + fi +]) diff --git a/m4/timegm.m4 b/m4/timegm.m4 new file mode 100644 index 00000000000..8e68b99baa2 --- /dev/null +++ b/m4/timegm.m4 @@ -0,0 +1,26 @@ +# timegm.m4 serial 11 +dnl Copyright (C) 2003, 2007, 2009-2015 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_FUNC_TIMEGM], +[ + AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS]) + AC_REQUIRE([gl_FUNC_MKTIME]) + REPLACE_TIMEGM=0 + AC_CHECK_FUNCS_ONCE([timegm]) + if test $ac_cv_func_timegm = yes; then + if test $gl_cv_func_working_mktime = no; then + # Assume that timegm is buggy if mktime is. + REPLACE_TIMEGM=1 + fi + else + HAVE_TIMEGM=0 + fi +]) + +# Prerequisites of lib/timegm.c. +AC_DEFUN([gl_PREREQ_TIMEGM], [ + : +]) diff --git a/nt/gnulib.mk b/nt/gnulib.mk index 0c2b786f667..8a57d646e7b 100644 --- a/nt/gnulib.mk +++ b/nt/gnulib.mk @@ -43,7 +43,7 @@ # the same distribution terms as the rest of that program. # # Generated by gnulib-tool. -# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=close --avoid=dup --avoid=fchdir --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=open --avoid=openat-die --avoid=opendir --avoid=raise --avoid=save-cwd --avoid=select --avoid=sigprocmask --avoid=stdarg --avoid=stdbool --avoid=threadlib --makefile-name=gnulib.mk --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt binary-io byteswap c-ctype c-strcase careadlinkat close-stream count-one-bits count-trailing-zeros crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 dtoastr dtotimespec dup2 environ execinfo faccessat fcntl fcntl-h fdatasync fdopendir filemode fstatat fsync getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog intprops largefile lstat manywarnings memrchr mkostemp mktime pipe2 pselect pthread_sigmask putenv qcopy-acl readlink readlinkat sig2str socklen stat-time stdalign stddef stdio stpcpy strftime strtoimax strtoumax symlink sys_stat sys_time time time_r timer-time timespec-add timespec-sub unsetenv update-copyright utimens vla warnings +# Reproduce by: gnulib-tool --import --dir=. --lib=libgnu --source-base=lib --m4-base=m4 --doc-base=doc --tests-base=tests --aux-dir=build-aux --avoid=close --avoid=dup --avoid=fchdir --avoid=flexmember --avoid=fstat --avoid=malloc-posix --avoid=msvc-inval --avoid=msvc-nothrow --avoid=open --avoid=openat-die --avoid=opendir --avoid=raise --avoid=save-cwd --avoid=select --avoid=setenv --avoid=sigprocmask --avoid=stdarg --avoid=stdbool --avoid=threadlib --avoid=unsetenv --makefile-name=gnulib.mk --conditional-dependencies --no-libtool --macro-prefix=gl --no-vc-files alloca-opt binary-io byteswap c-ctype c-strcase careadlinkat close-stream count-one-bits count-trailing-zeros crypto/md5 crypto/sha1 crypto/sha256 crypto/sha512 dtoastr dtotimespec dup2 environ execinfo faccessat fcntl fcntl-h fdatasync fdopendir filemode fstatat fsync getloadavg getopt-gnu gettime gettimeofday gitlog-to-changelog intprops largefile lstat manywarnings memrchr mkostemp mktime pipe2 pselect pthread_sigmask putenv qcopy-acl readlink readlinkat sig2str socklen stat-time stdalign stddef stdio stpcpy strftime strtoimax strtoumax symlink sys_stat sys_time time time_r time_rz timegm timer-time timespec-add timespec-sub unsetenv update-copyright utimens vla warnings MOSTLYCLEANFILES += core *.stackdump @@ -487,6 +487,17 @@ EXTRA_libgnu_a_SOURCES += mktime.c ## end gnulib module mktime +## begin gnulib module mktime-internal + +if gl_GNULIB_ENABLED_5264294aa0a5557541b53c8c741f7f31 + +endif +EXTRA_DIST += mktime-internal.h mktime.c + +EXTRA_libgnu_a_SOURCES += mktime.c + +## end gnulib module mktime-internal + ## begin gnulib module openat-h if gl_GNULIB_ENABLED_03e0aaad4cb89ca757653bd367a6ccb7 @@ -944,6 +955,54 @@ EXTRA_libgnu_a_SOURCES += symlink.c ## end gnulib module symlink +## begin gnulib module time + +BUILT_SOURCES += time.h + +# We need the following in order to create <time.h> when the system +# doesn't have one that works with the given compiler. +time.h: time.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H) $(WARN_ON_USE_H) + $(AM_V_GEN)rm -f $@-t $@ && \ + { echo '/* DO NOT EDIT! GENERATED AUTOMATICALLY! */' && \ + sed -e 's|@''GUARD_PREFIX''@|GL|g' \ + -e 's|@''INCLUDE_NEXT''@|$(INCLUDE_NEXT)|g' \ + -e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \ + -e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \ + -e 's|@''NEXT_TIME_H''@|$(NEXT_TIME_H)|g' \ + -e 's/@''GNULIB_GETTIMEOFDAY''@/$(GNULIB_GETTIMEOFDAY)/g' \ + -e 's/@''GNULIB_MKTIME''@/$(GNULIB_MKTIME)/g' \ + -e 's/@''GNULIB_NANOSLEEP''@/$(GNULIB_NANOSLEEP)/g' \ + -e 's/@''GNULIB_STRPTIME''@/$(GNULIB_STRPTIME)/g' \ + -e 's/@''GNULIB_TIMEGM''@/$(GNULIB_TIMEGM)/g' \ + -e 's/@''GNULIB_TIME_R''@/$(GNULIB_TIME_R)/g' \ + -e 's/@''GNULIB_TIME_RZ''@/$(GNULIB_TIME_RZ)/g' \ + -e 's|@''HAVE_DECL_LOCALTIME_R''@|$(HAVE_DECL_LOCALTIME_R)|g' \ + -e 's|@''HAVE_NANOSLEEP''@|$(HAVE_NANOSLEEP)|g' \ + -e 's|@''HAVE_STRPTIME''@|$(HAVE_STRPTIME)|g' \ + -e 's|@''HAVE_TIMEGM''@|$(HAVE_TIMEGM)|g' \ + -e 's|@''HAVE_TIMEZONE_T''@|$(HAVE_TIMEZONE_T)|g' \ + -e 's|@''REPLACE_GMTIME''@|$(REPLACE_GMTIME)|g' \ + -e 's|@''REPLACE_LOCALTIME''@|$(REPLACE_LOCALTIME)|g' \ + -e 's|@''REPLACE_LOCALTIME_R''@|$(REPLACE_LOCALTIME_R)|g' \ + -e 's|@''REPLACE_MKTIME''@|$(REPLACE_MKTIME)|g' \ + -e 's|@''REPLACE_NANOSLEEP''@|$(REPLACE_NANOSLEEP)|g' \ + -e 's|@''REPLACE_TIMEGM''@|$(REPLACE_TIMEGM)|g' \ + -e 's|@''PTHREAD_H_DEFINES_STRUCT_TIMESPEC''@|$(PTHREAD_H_DEFINES_STRUCT_TIMESPEC)|g' \ + -e 's|@''SYS_TIME_H_DEFINES_STRUCT_TIMESPEC''@|$(SYS_TIME_H_DEFINES_STRUCT_TIMESPEC)|g' \ + -e 's|@''TIME_H_DEFINES_STRUCT_TIMESPEC''@|$(TIME_H_DEFINES_STRUCT_TIMESPEC)|g' \ + -e 's|@''UNISTD_H_DEFINES_STRUCT_TIMESPEC''@|$(UNISTD_H_DEFINES_STRUCT_TIMESPEC)|g' \ + -e '/definitions of _GL_FUNCDECL_RPL/r $(CXXDEFS_H)' \ + -e '/definition of _GL_ARG_NONNULL/r $(ARG_NONNULL_H)' \ + -e '/definition of _GL_WARN_ON_USE/r $(WARN_ON_USE_H)' \ + < $(srcdir)/time.in.h; \ + } > $@-t && \ + mv $@-t $@ +MOSTLYCLEANFILES += time.h time.h-t + +EXTRA_DIST += time.in.h + +## end gnulib module time + ## begin gnulib module time_r @@ -953,6 +1012,24 @@ EXTRA_libgnu_a_SOURCES += time_r.c ## end gnulib module time_r +## begin gnulib module time_rz + + +EXTRA_DIST += time_rz.c + +EXTRA_libgnu_a_SOURCES += time_rz.c + +## end gnulib module time_rz + +## begin gnulib module timegm + + +EXTRA_DIST += mktime-internal.h timegm.c + +EXTRA_libgnu_a_SOURCES += timegm.c + +## end gnulib module timegm + ## begin gnulib module timespec libgnu_a_SOURCES += timespec.c @@ -981,15 +1058,6 @@ EXTRA_DIST += u64.h ## end gnulib module u64 -## begin gnulib module unsetenv - - -EXTRA_DIST += unsetenv.c - -EXTRA_libgnu_a_SOURCES += unsetenv.c - -## end gnulib module unsetenv - ## begin gnulib module update-copyright diff --git a/src/conf_post.h b/src/conf_post.h index 1a080fad635..785e5d7554b 100644 --- a/src/conf_post.h +++ b/src/conf_post.h @@ -206,6 +206,13 @@ extern void _DebPrint (const char *fmt, ...); #define RE_TRANSLATE_P(TBL) (!EQ (TBL, make_number (0))) #endif +/* Tell time_rz.c to use Emacs's getter and setter for TZ. + Only Emacs uses time_rz so this is OK. */ +#define getenv_TZ emacs_getenv_TZ +#define setenv_TZ emacs_setenv_TZ +extern char *emacs_getenv_TZ (void); +extern int emacs_setenv_TZ (char const *); + #include <string.h> #include <stdlib.h> diff --git a/src/editfns.c b/src/editfns.c index e39eed6e870..9ff39f9bf19 100644 --- a/src/editfns.c +++ b/src/editfns.c @@ -44,8 +44,10 @@ along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ #include <sys/resource.h> #endif +#include <errno.h> #include <float.h> #include <limits.h> + #include <intprops.h> #include <strftime.h> #include <verify.h> @@ -65,9 +67,8 @@ extern Lisp_Object w32_get_internal_run_time (void); #endif static struct lisp_time lisp_time_struct (Lisp_Object, int *); -static void set_time_zone_rule (char const *); static Lisp_Object format_time_string (char const *, ptrdiff_t, struct timespec, - bool, struct tm *); + Lisp_Object, struct tm *); static long int tm_gmtoff (struct tm *); static int tm_diff (struct tm *, struct tm *); static void update_buffer_properties (ptrdiff_t, ptrdiff_t); @@ -76,8 +77,13 @@ static void update_buffer_properties (ptrdiff_t, ptrdiff_t); # define HAVE_TM_GMTOFF false #endif -/* The startup value of the TZ environment variable; null if unset. */ -static char const *initial_tz; +enum { tzeqlen = sizeof "TZ=" - 1 }; + +/* Time zones equivalent to current local time, to wall clock time, + and to UTC, respectively. */ +static timezone_t local_tz; +static timezone_t wall_clock_tz; +static timezone_t const utc_tz = 0; /* A valid but unlikely setting for the TZ environment variable. It is OK (though a bit slower) if the user chooses this value. */ @@ -94,8 +100,97 @@ init_and_cache_system_name (void) cached_system_name = Vsystem_name; } +static struct tm * +emacs_localtime_rz (timezone_t tz, time_t const *t, struct tm *tm) +{ + tm = localtime_rz (tz, t, tm); + if (!tm && errno == ENOMEM) + memory_full (SIZE_MAX); + return tm; +} + +static time_t +emacs_mktime_z (timezone_t tz, struct tm *tm) +{ + errno = 0; + time_t t = mktime_z (tz, tm); + if (t == (time_t) -1 && errno == ENOMEM) + memory_full (SIZE_MAX); + return t; +} + +/* Allocate a timezone, signaling on failure. */ +static timezone_t +xtzalloc (char const *name) +{ + timezone_t tz = tzalloc (name); + if (!tz) + memory_full (SIZE_MAX); + return tz; +} + +/* Free a timezone, except do not free the time zone for local time. + Freeing utc_tz is also a no-op. */ +static void +xtzfree (timezone_t tz) +{ + if (tz != local_tz) + tzfree (tz); +} + +/* Convert the Lisp time zone rule ZONE to a timezone_t object. + The returned value either is 0, or is LOCAL_TZ, or is newly allocated. + If SETTZ, set Emacs local time to the time zone rule; otherwise, + the caller should eventually pass the returned value to xtzfree. */ +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; + + if (NILP (zone)) + return local_tz; + else if (EQ (zone, Qt)) + { + zone_string = "UTC0"; + new_tz = utc_tz; + } + else + { + if (EQ (zone, Qwall)) + zone_string = 0; + else if (STRINGP (zone)) + zone_string = SSDATA (zone); + else if (INTEGERP (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; + } + else + xsignal2 (Qerror, build_string ("Invalid time zone specification"), + zone); + new_tz = xtzalloc (zone_string); + } + + if (settz) + { + block_input (); + emacs_setenv_TZ (zone_string); + timezone_t old_tz = local_tz; + local_tz = new_tz; + tzfree (old_tz); + unblock_input (); + } + + return new_tz; +} + void -init_editfns (void) +init_editfns (bool dumping) { const char *user_name; register char *p; @@ -108,7 +203,7 @@ init_editfns (void) #ifndef CANNOT_DUMP /* When just dumping out, set the time zone to a known unlikely value and skip the rest of this function. */ - if (!initialized) + if (dumping) { # ifdef HAVE_TZSET xputenv (dump_tz_string); @@ -119,7 +214,6 @@ init_editfns (void) #endif char *tz = getenv ("TZ"); - initial_tz = tz; #if !defined CANNOT_DUMP && defined HAVE_TZSET /* If the execution TZ happens to be the same as the dump TZ, @@ -127,7 +221,7 @@ init_editfns (void) to force the underlying implementation to reload the TZ info. This is needed on implementations that load TZ info from files, since the TZ file contents may differ between dump and execution. */ - if (tz && strcmp (tz, &dump_tz_string[sizeof "TZ=" - 1]) == 0) + if (tz && strcmp (tz, &dump_tz_string[tzeqlen]) == 0) { ++*tz; tzset (); @@ -135,9 +229,10 @@ init_editfns (void) } #endif - /* Call set_time_zone_rule now, so that its call to putenv is done + /* Set the time zone rule now, so that the call to putenv is done before multiple threads are active. */ - set_time_zone_rule (tz); + wall_clock_tz = xtzalloc (0); + tzlookup (tz ? build_string (tz) : Qwall, true); pw = getpwuid (getuid ()); #ifdef MSDOS @@ -1206,7 +1301,7 @@ of the user with that uid, or nil if there is no such user. */) (That can happen if Emacs is dumpable but you decide to run `temacs -l loadup' and not dump. */ if (NILP (Vuser_login_name)) - init_editfns (); + init_editfns (false); if (NILP (uid)) return Vuser_login_name; @@ -1229,7 +1324,7 @@ This ignores the environment variables LOGNAME and USER, so it differs from (That can happen if Emacs is dumpable but you decide to run `temacs -l loadup' and not dump. */ if (NILP (Vuser_login_name)) - init_editfns (); + init_editfns (false); return Vuser_real_login_name; } @@ -1384,30 +1479,6 @@ check_time_validity (int validity) } } -/* A substitute for mktime_z on platforms that lack it. It's not - thread-safe, but should be good enough for Emacs in typical use. */ -#ifndef HAVE_TZALLOC -static time_t -mktime_z (timezone_t tz, struct tm *tm) -{ - char *oldtz = getenv ("TZ"); - USE_SAFE_ALLOCA; - if (oldtz) - { - size_t oldtzsize = strlen (oldtz) + 1; - char *oldtzcopy = SAFE_ALLOCA (oldtzsize); - oldtz = strcpy (oldtzcopy, oldtz); - } - block_input (); - set_time_zone_rule (tz); - time_t t = mktime (tm); - set_time_zone_rule (oldtz); - unblock_input (); - SAFE_FREE (); - return t; -} -#endif - /* Return the upper part of the time T (everything but the bottom 16 bits). */ static EMACS_INT hi_time (time_t t) @@ -1848,7 +1919,7 @@ or (if you need time as a string) `format-time-string'. */) /* Write information into buffer S of size MAXSIZE, according to the FORMAT of length FORMAT_LEN, using time information taken from *TP. - Default to Universal Time if UT, local time otherwise. + Use the time zone specified by TZ. Use NS as the number of nanoseconds in the %N directive. Return the number of bytes written, not including the terminating '\0'. If S is NULL, nothing will be written anywhere; so to @@ -1859,7 +1930,7 @@ or (if you need time as a string) `format-time-string'. */) bytes in FORMAT and it does not support nanoseconds. */ static size_t emacs_nmemftime (char *s, size_t maxsize, const char *format, - size_t format_len, const struct tm *tp, bool ut, int ns) + size_t format_len, const struct tm *tp, timezone_t tz, int ns) { size_t total = 0; @@ -1876,7 +1947,7 @@ emacs_nmemftime (char *s, size_t maxsize, const char *format, if (s) s[0] = '\1'; - result = nstrftime (s, maxsize, format, tp, ut, ns); + result = nstrftime (s, maxsize, format, tp, tz, ns); if (s) { @@ -1901,8 +1972,9 @@ DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0, 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 third, optional, argument UNIVERSAL, if non-nil, means describe TIME -as Universal Time; nil means describe TIME in the local time zone. +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 +`set-time-zone-rule' for a time zone rule. The value is a copy of FORMAT-STRING, but with certain constructs replaced by text that describes the specified date and time in TIME: @@ -1951,8 +2023,8 @@ The modifiers are `E' and `O'. For certain characters X, For example, to produce full ISO 8601 format, use "%FT%T%z". -usage: (format-time-string FORMAT-STRING &optional TIME UNIVERSAL) */) - (Lisp_Object format_string, Lisp_Object timeval, Lisp_Object universal) +usage: (format-time-string FORMAT-STRING &optional TIME ZONE) */) + (Lisp_Object format_string, Lisp_Object timeval, Lisp_Object zone) { struct timespec t = lisp_time_argument (timeval); struct tm tm; @@ -1961,12 +2033,12 @@ usage: (format-time-string FORMAT-STRING &optional TIME UNIVERSAL) */) format_string = code_convert_string_norecord (format_string, Vlocale_coding_system, 1); return format_time_string (SSDATA (format_string), SBYTES (format_string), - t, ! NILP (universal), &tm); + t, zone, &tm); } static Lisp_Object format_time_string (char const *format, ptrdiff_t formatlen, - struct timespec t, bool ut, struct tm *tmp) + struct timespec t, Lisp_Object zone, struct tm *tmp) { char buffer[4000]; char *buf = buffer; @@ -1976,36 +2048,48 @@ format_time_string (char const *format, ptrdiff_t formatlen, int ns = t.tv_nsec; USE_SAFE_ALLOCA; - tmp = ut ? gmtime_r (&t.tv_sec, tmp) : localtime_r (&t.tv_sec, tmp); + timezone_t tz = tzlookup (zone, false); + tmp = emacs_localtime_rz (tz, &t.tv_sec, tmp); if (! tmp) - time_overflow (); + { + xtzfree (tz); + time_overflow (); + } synchronize_system_time_locale (); while (true) { buf[0] = '\1'; - len = emacs_nmemftime (buf, size, format, formatlen, tmp, ut, ns); + len = emacs_nmemftime (buf, size, format, formatlen, tmp, tz, ns); if ((0 < len && len < size) || (len == 0 && buf[0] == '\0')) break; /* Buffer was too small, so make it bigger and try again. */ - len = emacs_nmemftime (NULL, SIZE_MAX, format, formatlen, tmp, ut, ns); + len = emacs_nmemftime (NULL, SIZE_MAX, format, formatlen, tmp, tz, ns); if (STRING_BYTES_BOUND <= len) - string_overflow (); + { + xtzfree (tz); + string_overflow (); + } size = len + 1; buf = SAFE_ALLOCA (size); } + xtzfree (tz); bufstring = make_unibyte_string (buf, len); SAFE_FREE (); return code_convert_string_norecord (bufstring, Vlocale_coding_system, 0); } -DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 1, 0, - doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST ZONE). +DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 2, 0, + doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF). 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 +`set-time-zone-rule' for a time zone rule. + 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 support. MINUTE is an integer between 0 and 59. HOUR is an integer @@ -2013,15 +2097,20 @@ between 0 and 23. DAY is an integer between 1 and 31. MONTH is an integer between 1 and 12. YEAR is an integer indicating the four-digit year. DOW is the day of week, an integer between 0 and 6, where 0 is Sunday. DST is t if daylight saving time is in effect, -otherwise nil. ZONE is an integer indicating the number of seconds -east of Greenwich. (Note that Common Lisp has different meanings for -DOW and ZONE.) */) - (Lisp_Object specified_time) +otherwise nil. UTCOFF is an integer indicating the UTC offset in +seconds, i.e., the number of seconds east of Greenwich. (Note that +Common Lisp has different meanings for DOW and UTCOFF.) + +usage: (decode-time &optional TIME ZONE) */) + (Lisp_Object specified_time, Lisp_Object zone) { time_t time_spec = lisp_seconds_argument (specified_time); struct tm local_tm, gmt_tm; + timezone_t tz = tzlookup (zone, false); + struct tm *tm = emacs_localtime_rz (tz, &time_spec, &local_tm); + xtzfree (tz); - if (! (localtime_r (&time_spec, &local_tm) + if (! (tm && MOST_NEGATIVE_FIXNUM - TM_YEAR_BASE <= local_tm.tm_year && local_tm.tm_year <= MOST_POSITIVE_FIXNUM - TM_YEAR_BASE)) time_overflow (); @@ -2059,35 +2148,13 @@ check_tm_member (Lisp_Object obj, int offset) return n - offset; } -/* Decode ZONE as a time zone specification. */ - -static Lisp_Object -decode_time_zone (Lisp_Object zone) -{ - if (EQ (zone, Qt)) - return build_string ("UTC0"); - else if (STRINGP (zone)) - return zone; - else if (INTEGERP (zone)) - { - static char const tzbuf_format[] = "XXX%s%"pI"d:%02d:%02d"; - char tzbuf[sizeof tzbuf_format + INT_STRLEN_BOUND (EMACS_INT)]; - EMACS_INT abszone = eabs (XINT (zone)), zone_hr = abszone / (60 * 60); - int zone_min = (abszone / 60) % 60, zone_sec = abszone % 60; - - return make_formatted_string (tzbuf, tzbuf_format, &"-"[XINT (zone) < 0], - zone_hr, zone_min, zone_sec); - } - else - xsignal2 (Qerror, build_string ("Invalid time zone specification"), zone); -} - 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. -ZONE defaults to the current time zone rule. This can -be a string or t (as from `set-time-zone-rule'), or it can be a list -\(as from `current-time-zone') or an integer (as from `decode-time') +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 +`set-time-zone-rule' for a time zone rule. 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. You can pass more than 7 arguments; then the first six arguments @@ -2120,14 +2187,9 @@ usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE) */) if (CONSP (zone)) zone = XCAR (zone); - if (NILP (zone)) - value = mktime (&tm); - else - { - timezone_t tz = tzalloc (SSDATA (decode_time_zone (zone))); - value = mktime_z (tz, &tm); - tzfree (tz); - } + timezone_t tz = tzlookup (zone, false); + value = emacs_mktime_z (tz, &tm); + xtzfree (tz); if (value == (time_t) -1) time_overflow (); @@ -2135,7 +2197,8 @@ usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE) */) return list2i (hi_time (value), lo_time (value)); } -DEFUN ("current-time-string", Fcurrent_time_string, Scurrent_time_string, 0, 1, 0, +DEFUN ("current-time-string", Fcurrent_time_string, Scurrent_time_string, + 0, 2, 0, doc: /* Return the current local time, as a human-readable string. Programs can use this function to decode a time, since the number of columns in each field is fixed @@ -2148,17 +2211,24 @@ If SPECIFIED-TIME is given, it is a time to format instead of 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. */) - (Lisp_Object specified_time) +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 +`set-time-zone-rule' for a time zone rule. */) + (Lisp_Object specified_time, Lisp_Object zone) { time_t value = lisp_seconds_argument (specified_time); + timezone_t tz = tzlookup (zone, false); /* Convert to a string in ctime format, except without the trailing newline, and without the 4-digit year limit. Don't use asctime or ctime, as they might dump core if the year is outside the range -999 .. 9999. */ struct tm tm; - if (! localtime_r (&value, &tm)) + struct tm *tmp = emacs_localtime_rz (tz, &value, &tm); + xtzfree (tz); + if (! tmp) time_overflow (); static char const wday_name[][4] = @@ -2210,7 +2280,7 @@ tm_gmtoff (struct tm *a) #endif } -DEFUN ("current-time-zone", Fcurrent_time_zone, Scurrent_time_zone, 0, 1, 0, +DEFUN ("current-time-zone", Fcurrent_time_zone, Scurrent_time_zone, 0, 2, 0, doc: /* Return the offset and name for the local time zone. This returns a list of the form (OFFSET NAME). OFFSET is an integer number of seconds ahead of UTC (east of Greenwich). @@ -2221,11 +2291,13 @@ 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 `set-time-zone-rule'. Some operating systems cannot provide all this information to Emacs; in this case, `current-time-zone' returns a list containing nil for the data it can't find. */) - (Lisp_Object specified_time) + (Lisp_Object specified_time, Lisp_Object zone) { struct timespec value; struct tm local_tm, gmt_tm; @@ -2233,7 +2305,8 @@ the data it can't find. */) zone_offset = Qnil; value = make_timespec (lisp_seconds_argument (specified_time), 0); - zone_name = format_time_string ("%Z", sizeof "%Z" - 1, value, 0, &local_tm); + zone_name = format_time_string ("%Z", sizeof "%Z" - 1, value, + zone, &local_tm); if (HAVE_TM_GMTOFF || gmtime_r (&value.tv_sec, &gmt_tm)) { @@ -2259,42 +2332,48 @@ 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 local time zone using TZ, a string specifying a time zone rule. -If TZ is nil, use implementation-defined default time zone information. -If TZ is t, use Universal Time. If TZ is an integer, it is treated as in -`encode-time'. - -Instead of calling this function, you typically want (setenv "TZ" TZ). -That changes both the environment of the Emacs process and the -variable `process-environment', whereas `set-time-zone-rule' affects -only the former. */) + 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. If TZ is t, use +Universal Time. If TZ is an integer, treat it as in `encode-time'. + +Instead of calling this function, you typically want something else. +To temporarily use a different time zone rule for just one invocation +of `decode-time', `encode-time', or `format-time-string', pass the +function a ZONE argument. To change local time consistently +throughout Emacs, call (setenv "TZ" TZ): this changes both the +environment of the Emacs process and the variable +`process-environment', whereas `set-time-zone-rule' affects only the +former. */) (Lisp_Object tz) { - const char *tzstring = NILP (tz) ? initial_tz : SSDATA (decode_time_zone (tz)); + tzlookup (NILP (tz) ? Qwall : tz, true); + return Qnil; +} - block_input (); - set_time_zone_rule (tzstring); - unblock_input (); +/* A buffer holding a string of the form "TZ=value", intended + to be part of the environment. If TZ is supposed to be unset, + the buffer string is "tZ=". */ + static char *tzvalbuf; - return Qnil; +/* Get the local time zone rule. */ +char * +emacs_getenv_TZ (void) +{ + return tzvalbuf[0] == 'T' ? tzvalbuf + tzeqlen : 0; } -/* Set the local time zone rule to TZSTRING. +/* Set the local time zone rule to TZSTRING, which can be null to + denote wall clock time. Do not record the setting in LOCAL_TZ. This function is not thread-safe, in theory because putenv is not, but mostly because of the static storage it updates. Other threads that invoke localtime etc. may be adversely affected while this function is executing. */ -static void -set_time_zone_rule (const char *tzstring) +int +emacs_setenv_TZ (const char *tzstring) { - /* A buffer holding a string of the form "TZ=value", intended - to be part of the environment. */ - static char *tzvalbuf; static ptrdiff_t tzvalbufsize; - - int tzeqlen = sizeof "TZ=" - 1; ptrdiff_t tzstringlen = tzstring ? strlen (tzstring) : 0; char *tzval = tzvalbuf; bool new_tzvalbuf = tzvalbufsize <= tzeqlen + tzstringlen; @@ -2346,9 +2425,7 @@ set_time_zone_rule (const char *tzstring) xputenv (tzval); } -#ifdef HAVE_TZSET - tzset (); -#endif + return 0; } /* Insert NARGS Lisp objects in the array ARGS by calling INSERT_FUNC @@ -4943,6 +5020,7 @@ void syms_of_editfns (void) { DEFSYM (Qbuffer_access_fontify_functions, "buffer-access-fontify-functions"); + DEFSYM (Qwall, "wall"); DEFVAR_LISP ("inhibit-field-text-motion", Vinhibit_field_text_motion, doc: /* Non-nil means text motion commands don't notice fields. */); diff --git a/src/emacs.c b/src/emacs.c index 93fb5870247..6e35496eb8a 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -1552,7 +1552,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem /* This calls putenv and so must precede init_process_emacs. Also, it sets Voperating_system_release, which init_process_emacs uses. */ - init_editfns (); + init_editfns (dumping); /* These two call putenv. */ #ifdef HAVE_DBUS diff --git a/src/lisp.h b/src/lisp.h index 341603f311f..02109d72174 100644 --- a/src/lisp.h +++ b/src/lisp.h @@ -4055,7 +4055,7 @@ extern _Noreturn void time_overflow (void); extern Lisp_Object make_buffer_string (ptrdiff_t, ptrdiff_t, bool); extern Lisp_Object make_buffer_string_both (ptrdiff_t, ptrdiff_t, ptrdiff_t, ptrdiff_t, bool); -extern void init_editfns (void); +extern void init_editfns (bool); extern void syms_of_editfns (void); /* Defined in buffer.c. */ diff --git a/src/systime.h b/src/systime.h index 744af17b640..abbe60114d5 100644 --- a/src/systime.h +++ b/src/systime.h @@ -106,20 +106,6 @@ extern struct timespec lisp_to_timespec (struct lisp_time); extern struct timespec lisp_time_argument (Lisp_Object); #endif -#ifndef HAVE_TZALLOC -# undef mktime_z -# undef timezone_t -# undef tzalloc -# undef tzfree -# define mktime_z emacs_mktime_z -# define timezone_t emacs_timezone_t -# define tzalloc emacs_tzalloc -# define tzfree emacs_tzfree -typedef char const *timezone_t; -INLINE timezone_t tzalloc (char const *name) { return name; } -INLINE void tzfree (timezone_t tz) { } -#endif - INLINE_HEADER_END #endif /* EMACS_SYSTIME_H */ |