* bug#21020: 24.4; `display-time-world' tampers with TZ @ 2015-07-09 19:39 William G. Gardella 2015-07-26 7:09 ` Paul Eggert 0 siblings, 1 reply; 16+ messages in thread From: William G. Gardella @ 2015-07-09 19:39 UTC (permalink / raw) To: 21020 I use `set-time-zone-rule' to run emacs with an altered time zone for the sake of having a local clock in the modeline via `display-time-mode' on a system that overall uses UTC time. However, `display-time-world' (in the `display-time-world-display' function, and also the initialization for the variable `display-time-world-list') resets the time zone using the TZ environment variable, and attempts to restore the time zone from there, while ignoring any value that might have been set by the user using `set-time-zone-rule'. Recipe: (display-time-mode 1) (set-time-zone-rule "some tz string different than system's TZ") (display-time-event-handler) ;; Time should now be localized to the TZ passed to`set-time-zone-rule' (display-time-world) (display-time-event-handler) ;; We're in a different TZ now! To make matters worse, if one attempts to manually correct Emacs's displayed time to the desired TZ after invoking (display-time-world), display-time-world-timer continually overwrites it until canceled. In GNU Emacs 24.4.1 (x86_64-slackware-linux-gnu) of 2015-03-06 on aporia Windowing system distributor `The X.Org Foundation', version 11.0.11403000 Configured using: `configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var --program-prefix= --program-suffix= --mandir=/usr/man --infodir=/usr/info --without-gconf --without-gsettings --with-x --with-x-toolkit=no --with-m17n-flt --build=x86_64-slackware-linux 'CFLAGS=-O2 -fPIC'' Important settings: value of $LC_COLLATE: C value of $LANG: en_US.UTF-8 locale-coding-system: utf-8-unix Major mode: ERC Minor modes in effect: semantic-minor-modes-format: ((:eval (if (or semantic-highlight-edits-mode semantic-show-unmatched-syntax-mode semantic-idle-scheduler-mode) S))) pdf-occur-global-minor-mode: t erc-services-mode: t erc-autoaway-mode: t erc-dcc-mode: t global-rainbow-delimiters-mode: t rainbow-delimiters-mode: t show-paren-mode: t electric-pair-mode: t winner-mode: t display-time-mode: t recentf-mode: t savehist-mode: t xterm-mouse-mode: t global-magit-wip-save-mode: t magit-auto-revert-mode: t shell-dirtrack-mode: t diff-auto-refine-mode: t erc-list-mode: t erc-menu-mode: t erc-autojoin-mode: t erc-ring-mode: t erc-networks-mode: t erc-pcomplete-mode: t erc-match-mode: t erc-netsplit-mode: t erc-hl-nicks-mode: t erc-button-mode: t erc-fill-mode: t erc-stamp-mode: t erc-irccontrols-mode: t erc-noncommands-mode: t erc-keep-place-mode: t erc-move-to-prompt-mode: t erc-readonly-mode: t electric-indent-mode: t mouse-wheel-mode: t file-name-shadow-mode: t global-font-lock-mode: t font-lock-mode: t auto-composition-mode: t auto-encryption-mode: t auto-compression-mode: t transient-mark-mode: t Recent input: y SPC u s e c a s e SPC i f SPC y o u ' r e SPC i n t e r e s t e d SPC i s SPC t h a t SPC I SPC p r e f e r SPC t h e SPC o v e r a l l SPC s y s t e m SPC ( <backspace> <backspace> SPC <backspace> SPC ( b r o w s s <backspace> e r s , SPC s t u f f SPC l i k SPC <backspace> e SPC w g e t , SPC o u t g o i n g SPC m a i l , SPC e t c . C-a C-e C-a C-e SPC t o SPC u s e SPC U T C S-SPC f o r SPC p r i v a c y SPC r e a s o n s C-a <return> b u t SPC I SPC u s e SPC e m a c s ' s SPC m o d e l i n e SPC a s SPC a SPC c l o c k <return> s-f C-s b a u d C-s C-s C-g C-g C-h v b a u d <tab> <return> s-b q e h e h s-b e h e h e <return> C-h v b a u d - r <tab> <return> s-f q <select-window> <select-window> <help-echo> <help-echo> <select-window> <select-window> <help-echo> <select-window> M-< C-s d i s p l a y - t i m e - w o r l d - d i s p <return> C-v s-b C-x C-g M-x r e p o r t - e m a c s - b u f <backspace> g <return> Recent messages: Mark saved where search started Mark set delete-backward-char: Text is read-only Quit [3 times] Type "q" in help window to restore its previous buffer. call-interactively: Buffer is read-only: #<buffer time.el.gz> [3 times] Type "q" in help window to restore its previous buffer. Mark set Mark saved where search started C-x C-g is undefined Load-path shadows: /usr/share/emacs/site-lisp/t-mouse hides /usr/share/emacs/24.4/lisp/t-mouse /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-clojure hides /usr/share/emacs/24.4/lisp/org/ob-clojure /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-asymptote hides /usr/share/emacs/24.4/lisp/org/ob-asymptote /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-lisp hides /usr/share/emacs/24.4/lisp/org/ob-lisp /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-lob hides /usr/share/emacs/24.4/lisp/org/ob-lob /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-gnuplot hides /usr/share/emacs/24.4/lisp/org/ob-gnuplot /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-bibtex hides /usr/share/emacs/24.4/lisp/org/org-bibtex /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-haskell hides /usr/share/emacs/24.4/lisp/org/ob-haskell /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-C hides /usr/share/emacs/24.4/lisp/org/ob-C /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-js hides /usr/share/emacs/24.4/lisp/org/ob-js /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-mhe hides /usr/share/emacs/24.4/lisp/org/org-mhe /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-html hides /usr/share/emacs/24.4/lisp/org/ox-html /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-icalendar hides /usr/share/emacs/24.4/lisp/org/ox-icalendar /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-matlab hides /usr/share/emacs/24.4/lisp/org/ob-matlab /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-fortran hides /usr/share/emacs/24.4/lisp/org/ob-fortran /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-protocol hides /usr/share/emacs/24.4/lisp/org/org-protocol /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-ctags hides /usr/share/emacs/24.4/lisp/org/org-ctags /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-scala hides /usr/share/emacs/24.4/lisp/org/ob-scala /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-picolisp hides /usr/share/emacs/24.4/lisp/org/ob-picolisp /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-entities hides /usr/share/emacs/24.4/lisp/org/org-entities /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-md hides /usr/share/emacs/24.4/lisp/org/ox-md /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-eshell hides /usr/share/emacs/24.4/lisp/org/org-eshell /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-clock hides /usr/share/emacs/24.4/lisp/org/org-clock /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-w3m hides /usr/share/emacs/24.4/lisp/org/org-w3m /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-src hides /usr/share/emacs/24.4/lisp/org/org-src /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-crypt hides /usr/share/emacs/24.4/lisp/org/org-crypt /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-ditaa hides /usr/share/emacs/24.4/lisp/org/ob-ditaa /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-css hides /usr/share/emacs/24.4/lisp/org/ob-css /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-ocaml hides /usr/share/emacs/24.4/lisp/org/ob-ocaml /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-compat hides /usr/share/emacs/24.4/lisp/org/org-compat /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-exp hides /usr/share/emacs/24.4/lisp/org/ob-exp /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-attach hides /usr/share/emacs/24.4/lisp/org/org-attach /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-agenda hides /usr/share/emacs/24.4/lisp/org/org-agenda /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-timer hides /usr/share/emacs/24.4/lisp/org/org-timer /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-screen hides /usr/share/emacs/24.4/lisp/org/ob-screen /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-table hides /usr/share/emacs/24.4/lisp/org/ob-table /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-octave hides /usr/share/emacs/24.4/lisp/org/ob-octave /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob hides /usr/share/emacs/24.4/lisp/org/ob /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-macro hides /usr/share/emacs/24.4/lisp/org/org-macro /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-footnote hides /usr/share/emacs/24.4/lisp/org/org-footnote /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-capture hides /usr/share/emacs/24.4/lisp/org/org-capture /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-makefile hides /usr/share/emacs/24.4/lisp/org/ob-makefile /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-man hides /usr/share/emacs/24.4/lisp/org/ox-man /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-mscgen hides /usr/share/emacs/24.4/lisp/org/ob-mscgen /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-feed hides /usr/share/emacs/24.4/lisp/org/org-feed /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-plantuml hides /usr/share/emacs/24.4/lisp/org/ob-plantuml /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-inlinetask hides /usr/share/emacs/24.4/lisp/org/org-inlinetask /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-latex hides /usr/share/emacs/24.4/lisp/org/ob-latex /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-scheme hides /usr/share/emacs/24.4/lisp/org/ob-scheme /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-mobile hides /usr/share/emacs/24.4/lisp/org/org-mobile /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-comint hides /usr/share/emacs/24.4/lisp/org/ob-comint /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-sh hides /usr/share/emacs/24.4/lisp/org/ob-sh /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-ledger hides /usr/share/emacs/24.4/lisp/org/ob-ledger /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-plot hides /usr/share/emacs/24.4/lisp/org/org-plot /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-sql hides /usr/share/emacs/24.4/lisp/org/ob-sql /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-beamer hides /usr/share/emacs/24.4/lisp/org/ox-beamer /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-id hides /usr/share/emacs/24.4/lisp/org/org-id /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-sqlite hides /usr/share/emacs/24.4/lisp/org/ob-sqlite /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-element hides /usr/share/emacs/24.4/lisp/org/org-element /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-R hides /usr/share/emacs/24.4/lisp/org/ob-R /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-mouse hides /usr/share/emacs/24.4/lisp/org/org-mouse /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-io hides /usr/share/emacs/24.4/lisp/org/ob-io /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-colview hides /usr/share/emacs/24.4/lisp/org/org-colview /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-indent hides /usr/share/emacs/24.4/lisp/org/org-indent /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-eval hides /usr/share/emacs/24.4/lisp/org/ob-eval /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-emacs-lisp hides /usr/share/emacs/24.4/lisp/org/ob-emacs-lisp /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox hides /usr/share/emacs/24.4/lisp/org/ox /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-macs hides /usr/share/emacs/24.4/lisp/org/org-macs /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-calc hides /usr/share/emacs/24.4/lisp/org/ob-calc /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-core hides /usr/share/emacs/24.4/lisp/org/ob-core /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-docview hides /usr/share/emacs/24.4/lisp/org/org-docview /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-ruby hides /usr/share/emacs/24.4/lisp/org/ob-ruby /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-lilypond hides /usr/share/emacs/24.4/lisp/org/ob-lilypond /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-org hides /usr/share/emacs/24.4/lisp/org/ob-org /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-rmail hides /usr/share/emacs/24.4/lisp/org/org-rmail /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-info hides /usr/share/emacs/24.4/lisp/org/org-info /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-list hides /usr/share/emacs/24.4/lisp/org/org-list /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-ascii hides /usr/share/emacs/24.4/lisp/org/ox-ascii /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-datetree hides /usr/share/emacs/24.4/lisp/org/org-datetree /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-maxima hides /usr/share/emacs/24.4/lisp/org/ob-maxima /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-org hides /usr/share/emacs/24.4/lisp/org/ox-org /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org hides /usr/share/emacs/24.4/lisp/org/org /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-java hides /usr/share/emacs/24.4/lisp/org/ob-java /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-bbdb hides /usr/share/emacs/24.4/lisp/org/org-bbdb /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-tangle hides /usr/share/emacs/24.4/lisp/org/ob-tangle /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-odt hides /usr/share/emacs/24.4/lisp/org/ox-odt /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-awk hides /usr/share/emacs/24.4/lisp/org/ob-awk /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-sass hides /usr/share/emacs/24.4/lisp/org/ob-sass /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-archive hides /usr/share/emacs/24.4/lisp/org/org-archive /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-shen hides /usr/share/emacs/24.4/lisp/org/ob-shen /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-dot hides /usr/share/emacs/24.4/lisp/org/ob-dot /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-gnus hides /usr/share/emacs/24.4/lisp/org/org-gnus /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-texinfo hides /usr/share/emacs/24.4/lisp/org/ox-texinfo /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-pcomplete hides /usr/share/emacs/24.4/lisp/org/org-pcomplete /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-python hides /usr/share/emacs/24.4/lisp/org/ob-python /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-ref hides /usr/share/emacs/24.4/lisp/org/ob-ref /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-publish hides /usr/share/emacs/24.4/lisp/org/ox-publish /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-keys hides /usr/share/emacs/24.4/lisp/org/ob-keys /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-faces hides /usr/share/emacs/24.4/lisp/org/org-faces /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ox-latex hides /usr/share/emacs/24.4/lisp/org/ox-latex /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-habit hides /usr/share/emacs/24.4/lisp/org/org-habit /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-irc hides /usr/share/emacs/24.4/lisp/org/org-irc /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-table hides /usr/share/emacs/24.4/lisp/org/org-table /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/ob-perl hides /usr/share/emacs/24.4/lisp/org/ob-perl /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-version hides /usr/share/emacs/24.4/lisp/org/org-version /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-install hides /usr/share/emacs/24.4/lisp/org/org-install /home/wgg/.emacs.d/elpa/org-plus-contrib-20150629/org-loaddefs hides /usr/share/emacs/24.4/lisp/org/org-loaddefs Features: (shadow bbdb-message emacsbug sendmail debug hippie-exp vc-rcs pdf-sync pdf-annot pdf-outline pdf-links pdf-history pcmpl-unix find-dired dired-aux ispell man w3m-form w3m-filter w3m-cookie w3m-bookmark w3m-tabmenu w3m-session ffap w3m w3m-hist w3m-fb bookmark-w3m w3m-ems w3m-ccl ccl w3m-favicon w3m-image w3m-proc w3m-util nnir qp xterm gnus-cite gnus-html url-cache mm-url url url-proxy url-privacy url-expand url-methods url-history url-cookie url-domsuf url-util url-parse url-vars mm-archive mail-extr gnus-bcklg misearch multi-isearch vc-git sh-script smie executable eieio-opt speedbar sb-image dframe rx re-builder sgml-mode ox-koma-letter ox-texinfo ox-org ox-icalendar ox-html ox-beamer ox-latex ox-ascii ox-publish ox tabify org-table image-file org-element org-rmail org-mhe org-irc org-info org-gnus org-docview doc-view org-bibtex bibtex org-bbdb org-w3m org org-macro org-footnote org-pcomplete org-list org-faces org-entities org-version ob-emacs-lisp ob ob-tangle ob-ref ob-lob ob-table ob-exp org-src ob-keys ob-comint ob-core ob-eval org-compat org-macs org-loaddefs find-func em-unix em-term term ehelp em-smart em-script em-prompt em-ls em-hist em-pred em-glob em-dirs em-cmpl em-basic em-banner em-alias esh-var esh-io esh-cmd esh-opt esh-ext esh-proc esh-arg esh-groups eshell esh-module esh-mode esh-util gnus-async sort gnus-ml disp-table windmove gnus-topic parse-time netrc nnfolder bbdb-gnus nnmaildir network-stream starttls tls elisp-slime-nav pdf-occur ibuf-ext ibuffer tablist tablist-filter semantic/wisent/comp semantic/wisent semantic/wisent/wisent dired-x dired pdf-isearch pdf-misc imenu pdf-tools cus-edit pdf-view jka-compr pdf-cache pdf-info tq pdf-util image-mode mule-util sauron-identica sauron-jabber sauron-twittering sauron-notifications notifications sauron-org appt diary-lib diary-loaddefs cal-menu calendar cal-loaddefs sauron-dbus dbus xml sauron-erc sauron erc-services erc-autoaway erc-dcc erc-sasl gnus-delay gnus-draft gnus-agent gnus-srvr gnus-score score-mode nnvirtual nntp gnus-cache gnus-msg gnus-art mm-uu mml2015 mm-view mml-smime smime dig mailcap gnus-sum nndraft nnmh nnoo gnus-group gnus-undo gnus-start gnus-spec gnus-win nnmail gnus-int gnus-range mail-source gnus gnus-ems nnheader bbdb-mua bbdb-com crm bbdb bbdb-site timezone bbdb-loaddefs slime-fancy slime-trace-dialog slime-fontifying-fu slime-package-fu slime-references slime-compiler-notes-tree slime-scratch slime-presentations bridge slime-fuzzy slime-fancy-trace slime-fancy-inspector slime-c-p-c slime-editing-commands slime-autodoc eldoc slime-repl elp slime-parse slime gud apropos etags arc-mode archive-mode noutline outline hyperspec browse-url slime-autoloads rainbow-delimiters paren elec-pair winner time saveplace tramp-cache tramp-sh recentf tree-widget savehist semantic/idle semantic/format ezimage semantic/tag-ls semantic/find semantic/ctxt tomorrow-night-bright-theme color-theme-tomorrow xt-mouse semantic/util-modes semantic/util semantic semantic/tag semantic/lex semantic/fw mode-local cedet whitespace magit-wip magit-key-mode magit view tramp tramp-compat tramp-loaddefs trampver shell advice help-mode grep compile diff-mode autorevert filenotify git-rebase-mode git-commit-mode server log-edit easy-mmode message idna cl-macs rfc822 mml mml-sec mm-decode mm-bodies mm-encode mail-parse rfc2231 rfc2047 rfc2045 ietf-drums mailabbrev mail-utils gmm-utils mailheader pcvs-util add-log company edmacro kmacro erc-list erc-menu erc-join erc-ring erc-networks erc-pcomplete pcomplete comint ansi-color ring erc-track erc-match erc-netsplit erc-hl-nicks color cl gv erc-button erc-fill erc-stamp wid-edit cl-loaddefs cl-lib erc-goodies erc erc-backend erc-compat format-spec auth-source eieio byte-opt bytecomp byte-compile cconv eieio-core gnus-util time-date mm-util help-fns mail-prsvr password-cache thingatpt pp cus-start cus-load epa-file epa derived epg tex-site darkroom-autoloads dash-autoloads erc-hl-nicks-autoloads esxml-autoloads db-autoloads gratuitous-dark-theme-autoloads kv-autoloads late-night-theme-autoloads finder-inf info easymenu rainbow-delimiters-autoloads tron-theme-autoloads package epg-config tooltip electric uniquify ediff-hook vc-hooks lisp-float-type mwheel x-win x-dnd tool-bar dnd fontset image regexp-opt fringe tabulated-list newcomment lisp-mode prog-mode register page menu-bar rfn-eshadow timer select scroll-bar mouse jit-lock font-lock syntax facemenu font-core frame cham georgian utf-8-lang misc-lang vietnamese tibetan thai tai-viet lao korean japanese hebrew greek romanian slovak czech european ethiopic indian cyrillic chinese case-table epa-hook jka-cmpr-hook help simple abbrev minibuffer nadvice loaddefs button faces cus-face macroexp files text-properties overlay sha1 md5 base64 format env code-pages mule custom widget hashtable-print-readable backquote make-network-process dbusbind gfilenotify dynamic-setting font-render-setting x multi-tty emacs) Memory information: ((conses 16 2371792 287587) (symbols 48 172482 17) (miscs 40 3807 12000) (strings 32 527198 47542) (string-bytes 1 28336991) (vectors 16 187907) (vector-slots 8 2963927 182255) (floats 8 3416 26348) (intervals 56 106399 3626) (buffers 960 140) (heap 1024 154824 12981)) ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-09 19:39 bug#21020: 24.4; `display-time-world' tampers with TZ William G. Gardella @ 2015-07-26 7:09 ` Paul Eggert 2015-07-26 14:48 ` Eli Zaretskii 0 siblings, 1 reply; 16+ messages in thread From: Paul Eggert @ 2015-07-26 7:09 UTC (permalink / raw) To: William G. Gardella; +Cc: 21020 [-- Attachment #1: Type: text/plain, Size: 276 bytes --] Thanks for the bug report. Improving Emacs time zones has been on my TODO list for ages, and the report prompted me to do that. Attached is a patch which works for me on GNU/Linux. I'm CC'ing this to Eli to give him a heads-up in case it has implications on MS-Windows. [-- Attachment #2: 0001-New-optional-ZONE-arg-for-format-time-string-etc.txt --] [-- Type: text/plain, Size: 76255 bytes --] From 3dbc7a7c3e302a31bba4fac259d3ef2372749456 Mon Sep 17 00:00:00 2001 From: Paul Eggert <eggert@cs.ucla.edu> Date: Sun, 26 Jul 2015 00:01:34 -0700 Subject: [PATCH] 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: * src/time_rz.c: 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. * 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. --- admin/admin.el | 6 +- admin/merge-gnulib | 8 +- configure.ac | 2 +- doc/lispref/os.texi | 80 ++++++----- etc/NEWS | 9 ++ lib/gnulib.mk | 42 ++++-- lib/strftime.c | 64 +++------ lib/strftime.h | 7 +- lib/time.in.h | 19 +++ lib/time_rz.c | 374 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/timegm.c | 38 ++++++ lisp/time-stamp.el | 12 +- lisp/time.el | 33 ++--- lisp/vc/add-log.el | 23 ++-- lisp/vc/log-edit.el | 3 +- m4/gnulib-comp.m4 | 43 ++++-- m4/sys_time_h.m4 | 1 + m4/time_h.m4 | 1 + m4/time_rz.m4 | 21 +++ m4/timegm.m4 | 26 ++++ src/conf_post.h | 7 + src/editfns.c | 324 ++++++++++++++++++++++++++++----------------- src/emacs.c | 2 +- src/lisp.h | 2 +- src/systime.h | 14 -- src/time_rz.c | 0 26 files changed, 863 insertions(+), 298 deletions(-) create mode 100644 lib/time_rz.c create mode 100644 lib/timegm.c create mode 100644 m4/time_rz.m4 create mode 100644 m4/timegm.m4 create mode 100644 src/time_rz.c diff --git a/admin/admin.el b/admin/admin.el index 93e9124..267f2c4 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 e7910a6..963c3a0 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 b58c7de..19b8b9d 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 0c39be9..4b5a1b4 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: diff --git a/etc/NEWS b/etc/NEWS index 666cccf..5bb7a00 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -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 2dd0ef8..1ca12a2 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 2426aae..c7cec26 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 3967afc..2ce6cc5 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 1a6b746..a983f49 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 0000000..8a4d7d1 --- /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 0000000..11c485f --- /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 24e5ef4..1e0fe99 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 ae0e598..d35f5b9 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 eb7e5bf..c90413c 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 d595497..acbd9c0 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 0425d02..cf71d7e 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 50133b9..28c8b1a 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 d9c41a4..754b469 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 0000000..0c1f2c3 --- /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 0000000..8e68b99 --- /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/src/conf_post.h b/src/conf_post.h index 1a080fa..785e5d7 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 e39eed6..9ff39f9 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; } \f /* 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 93fb587..6e35496 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 341603f..02109d7 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 744af17..abbe601 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 */ diff --git a/src/time_rz.c b/src/time_rz.c new file mode 100644 index 0000000..e69de29 -- 2.1.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-26 7:09 ` Paul Eggert @ 2015-07-26 14:48 ` Eli Zaretskii 2015-07-26 17:05 ` Paul Eggert 0 siblings, 1 reply; 16+ messages in thread From: Eli Zaretskii @ 2015-07-26 14:48 UTC (permalink / raw) To: Paul Eggert; +Cc: 21020, wgg2 > Date: Sun, 26 Jul 2015 00:09:40 -0700 > From: Paul Eggert <eggert@cs.ucla.edu> > CC: 21020@debbugs.gnu.org, Eli Zaretskii <eliz@gnu.org> > > Thanks for the bug report. Improving Emacs time zones has been on my TODO list > for ages, and the report prompted me to do that. Attached is a patch which > works for me on GNU/Linux. I'm CC'ing this to Eli to give him a heads-up in > case it has implications on MS-Windows. This is a large patch, touching a lot of code. I hope we have some tests to make sure this doesn't cause any regressions. Other than that, please make in nt/gnulib.mk the same changes you did in lib/gnulib.mk, otherwise I think the changes will not compile on Windows. Thanks. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-26 14:48 ` Eli Zaretskii @ 2015-07-26 17:05 ` Paul Eggert 2015-07-26 17:34 ` Eli Zaretskii 0 siblings, 1 reply; 16+ messages in thread From: Paul Eggert @ 2015-07-26 17:05 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 21020, wgg2 [-- Attachment #1: Type: text/plain, Size: 647 bytes --] Eli Zaretskii wrote: > I hope we have some > tests to make sure this doesn't cause any regressions. test/automated/icalender-tests.el exercises the new code. > Other than that, please make in nt/gnulib.mk the same changes you did > in lib/gnulib.mk, otherwise I think the changes will not compile on > Windows. OK, done in the revised patch (attached). Also, the MS-Windows port can't entirely skip the time module any more, as that module arranges for time.h to declare localtime_rz and friends. I guess one possibility is to have nt/gnulib.mk mimic lib/gnulib.mk as far as the time module goes; this is also done in the revised patch. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-New-optional-ZONE-arg-for-format-time-string-etc.patch --] [-- Type: text/x-diff; name="0001-New-optional-ZONE-arg-for-format-time-string-etc.patch", Size: 84887 bytes --] From e0b2b39847c73c02ecfbd30c8c715b8913d54a44 Mon Sep 17 00:00:00 2001 From: Paul Eggert <eggert@cs.ucla.edu> Date: Sun, 26 Jul 2015 00:01:34 -0700 Subject: [PATCH] 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. --- admin/admin.el | 6 +- admin/merge-gnulib | 8 +- configure.ac | 2 +- doc/lispref/os.texi | 80 ++++++----- etc/NEWS | 9 ++ lib/gnulib.mk | 42 ++++-- lib/strftime.c | 64 +++------ lib/strftime.h | 7 +- lib/time.in.h | 19 +++ lib/time_rz.c | 374 ++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/timegm.c | 38 ++++++ lisp/time-stamp.el | 12 +- lisp/time.el | 33 ++--- lisp/vc/add-log.el | 23 ++-- lisp/vc/log-edit.el | 3 +- m4/gnulib-comp.m4 | 43 ++++-- m4/sys_time_h.m4 | 1 + m4/time_h.m4 | 1 + m4/time_rz.m4 | 21 +++ m4/timegm.m4 | 26 ++++ nt/gnulib.mk | 88 +++++++++++-- src/conf_post.h | 7 + src/editfns.c | 324 ++++++++++++++++++++++++++++----------------- src/emacs.c | 2 +- src/lisp.h | 2 +- src/systime.h | 14 -- 26 files changed, 941 insertions(+), 308 deletions(-) create mode 100644 lib/time_rz.c create mode 100644 lib/timegm.c create mode 100644 m4/time_rz.m4 create mode 100644 m4/timegm.m4 diff --git a/admin/admin.el b/admin/admin.el index 93e9124..267f2c4 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 e7910a6..963c3a0 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 b58c7de..19b8b9d 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 0c39be9..4b5a1b4 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: diff --git a/etc/NEWS b/etc/NEWS index 666cccf..5bb7a00 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -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 2dd0ef8..1ca12a2 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 2426aae..c7cec26 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 3967afc..2ce6cc5 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 1a6b746..a983f49 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 0000000..8a4d7d1 --- /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 0000000..11c485f --- /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 24e5ef4..1e0fe99 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 ae0e598..d35f5b9 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 eb7e5bf..c90413c 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 d595497..acbd9c0 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 0425d02..cf71d7e 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 50133b9..28c8b1a 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 d9c41a4..754b469 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 0000000..0c1f2c3 --- /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 0000000..8e68b99 --- /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 0c2b786..8a57d64 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 1a080fa..785e5d7 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 e39eed6..9ff39f9 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; } \f /* 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 93fb587..6e35496 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 341603f..02109d7 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 744af17..abbe601 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 */ -- 2.1.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-26 17:05 ` Paul Eggert @ 2015-07-26 17:34 ` Eli Zaretskii 2015-07-26 17:37 ` Eli Zaretskii 2015-07-26 17:55 ` Paul Eggert 0 siblings, 2 replies; 16+ messages in thread From: Eli Zaretskii @ 2015-07-26 17:34 UTC (permalink / raw) To: Paul Eggert; +Cc: 21020, wgg2 > Date: Sun, 26 Jul 2015 10:05:38 -0700 > From: Paul Eggert <eggert@cs.ucla.edu> > CC: wgg2@member.fsf.org, 21020@debbugs.gnu.org > > > I hope we have some > > tests to make sure this doesn't cause any regressions. > > test/automated/icalender-tests.el exercises the new code. But the tests there that play with TZ are all expected failures on MS-Windows, because they use Posix format of time-zone definition that Windows runtime doesn't support. So that probably means there are no tests that could be used on Windows. > Also, the MS-Windows port can't entirely skip the time module any > more, as that module arranges for time.h to declare localtime_rz and > friends. This could conflict with time.h in nt/inc/sys/, no? ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-26 17:34 ` Eli Zaretskii @ 2015-07-26 17:37 ` Eli Zaretskii 2015-07-26 17:55 ` Paul Eggert 1 sibling, 0 replies; 16+ messages in thread From: Eli Zaretskii @ 2015-07-26 17:37 UTC (permalink / raw) To: eggert; +Cc: 21020, wgg2 > Date: Sun, 26 Jul 2015 20:34:22 +0300 > From: Eli Zaretskii <eliz@gnu.org> > Cc: 21020@debbugs.gnu.org, wgg2@member.fsf.org > > > Also, the MS-Windows port can't entirely skip the time module any > > more, as that module arranges for time.h to declare localtime_rz and > > friends. > > This could conflict with time.h in nt/inc/sys/, no? Also, lib/time.h seems to want to replace 'localtime', which might clash with 'sys_localtime' we currently use as replacement on w32. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-26 17:34 ` Eli Zaretskii 2015-07-26 17:37 ` Eli Zaretskii @ 2015-07-26 17:55 ` Paul Eggert 2015-07-26 18:21 ` Eli Zaretskii 1 sibling, 1 reply; 16+ messages in thread From: Paul Eggert @ 2015-07-26 17:55 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 21020, wgg2 Eli Zaretskii wrote: >> test/automated/icalender-tests.el exercises the new code. > > But the tests there that play with TZ are all expected failures on > MS-Windows, because they use Posix format of time-zone definition that > Windows runtime doesn't support. So that probably means there are no > tests that could be used on Windows. OK, didn't know that. Presumably this could be addressed by adding tests for MS-Windows TZ settings, if somebody has the time for this (pun intended...). >> Also, the MS-Windows port can't entirely skip the time module any >> more, as that module arranges for time.h to declare localtime_rz and >> friends. > > This could conflict with time.h in nt/inc/sys/, no? I don't see why. <sys/time.h> is a different API. This patch affects only <time.h>. >lib/time.h seems to want to replace 'localtime', which might > clash with 'sys_localtime' we currently use as replacement on w32. The replacement is conditional, and the condition should be false on w32. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-26 17:55 ` Paul Eggert @ 2015-07-26 18:21 ` Eli Zaretskii 2015-07-26 19:48 ` Paul Eggert 0 siblings, 1 reply; 16+ messages in thread From: Eli Zaretskii @ 2015-07-26 18:21 UTC (permalink / raw) To: Paul Eggert; +Cc: 21020, wgg2 > Date: Sun, 26 Jul 2015 10:55:38 -0700 > From: Paul Eggert <eggert@cs.ucla.edu> > CC: wgg2@member.fsf.org, 21020@debbugs.gnu.org > > Eli Zaretskii wrote: > > >> test/automated/icalender-tests.el exercises the new code. > > > > But the tests there that play with TZ are all expected failures on > > MS-Windows, because they use Posix format of time-zone definition that > > Windows runtime doesn't support. So that probably means there are no > > tests that could be used on Windows. > > OK, didn't know that. Presumably this could be addressed by adding tests for > MS-Windows TZ settings, if somebody has the time for this (pun intended...). MS-Windows supports only the simplest TZ format, as in EST-5EDT, you cannot tell it when the DST rules begin and end. And those test exercise precisely those begin/end rules that cannot work. > >> Also, the MS-Windows port can't entirely skip the time module any > >> more, as that module arranges for time.h to declare localtime_rz and > >> friends. > > > > This could conflict with time.h in nt/inc/sys/, no? > > I don't see why. <sys/time.h> is a different API. This patch affects only > <time.h>. > > >lib/time.h seems to want to replace 'localtime', which might > > clash with 'sys_localtime' we currently use as replacement on w32. > > The replacement is conditional, and the condition should be false on w32. Then I guess we are lucky, and nothing could possibly become broken. Thanks. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-26 18:21 ` Eli Zaretskii @ 2015-07-26 19:48 ` Paul Eggert 2015-07-27 12:29 ` Eli Zaretskii 0 siblings, 1 reply; 16+ messages in thread From: Paul Eggert @ 2015-07-26 19:48 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 21020-done, wgg2 Eli Zaretskii wrote: > Then I guess we are lucky, and nothing could possibly become broken. Ha! Fat chance. But I gave it a shot by installing the patch as master commit af32fa956267af40db61051c248597144d41521c and I'm marking this bug as done. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-26 19:48 ` Paul Eggert @ 2015-07-27 12:29 ` Eli Zaretskii 2015-07-27 15:02 ` Paul Eggert 2015-07-27 17:29 ` Eli Zaretskii 0 siblings, 2 replies; 16+ messages in thread From: Eli Zaretskii @ 2015-07-27 12:29 UTC (permalink / raw) To: Paul Eggert; +Cc: 21020, wgg2 > Date: Sun, 26 Jul 2015 12:48:44 -0700 > From: Paul Eggert <eggert@cs.ucla.edu> > CC: wgg2@member.fsf.org, 21020-done@debbugs.gnu.org > > Eli Zaretskii wrote: > > Then I guess we are lucky, and nothing could possibly become broken. > > Ha! Fat chance. But I gave it a shot by installing the patch as master > commit af32fa956267af40db61051c248597144d41521c and I'm marking this bug as done. I needed the changes in 7009674 to get this to build cleanly on MinGW. One of the changes in that commit is in lib/time.in.h. This is the same problem already reported for another Gnulib header: http://lists.gnu.org/archive/html/bug-gnulib/2015-06/msg00035.html It sounds like similar changes are needed in other Gnulib headers, lest they produce similar problems at some point. I can provide a list of all the __need_SOMETHING symbols used by the MinGW headers, if that will help. After the above commit, Emacs builds and generally seems to work, but crashes inside icalendar tests. Seems like something with heap corruption. I'm looking into that now. Thanks. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-27 12:29 ` Eli Zaretskii @ 2015-07-27 15:02 ` Paul Eggert 2015-07-27 15:22 ` Eli Zaretskii 2015-07-27 17:29 ` Eli Zaretskii 1 sibling, 1 reply; 16+ messages in thread From: Paul Eggert @ 2015-07-27 15:02 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 21020, wgg2 Eli Zaretskii wrote: > I needed the changes in 7009674 to get this to build cleanly on > MinGW. One of the changes in that commit is in lib/time.in.h. Thanks, I ported that code fix back to gnulib, as gnulib master commit b18e6d98e211be6bb0e720952a9baef7da1d27cb. > It sounds like similar changes are needed in other Gnulib headers, > lest they produce similar problems at some point. I can provide a > list of all the __need_SOMETHING symbols used by the MinGW headers, if > that will help. I looked into that and came up with the following list: __need_NULL __need_ptrdiff_t __need_size_t __need_struct_timespec __need_time_t __need___va_list __need_wchar_t __need_wint_t The only other Gnulib header that refers to any of these symbols is stddef.in.h and if it's not used in MinGW then I expect we don't need to worry about it. Though there may be issues in MinGW itself, if it's using these symbols incompatibly with what GCC expects (stddef.h is a GCC header). ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-27 15:02 ` Paul Eggert @ 2015-07-27 15:22 ` Eli Zaretskii 2015-07-27 15:38 ` Eli Zaretskii 0 siblings, 1 reply; 16+ messages in thread From: Eli Zaretskii @ 2015-07-27 15:22 UTC (permalink / raw) To: Paul Eggert; +Cc: 21020, wgg2 > Date: Mon, 27 Jul 2015 08:02:53 -0700 > From: Paul Eggert <eggert@cs.ucla.edu> > CC: wgg2@member.fsf.org, 21020@debbugs.gnu.org > > > It sounds like similar changes are needed in other Gnulib headers, > > lest they produce similar problems at some point. I can provide a > > list of all the __need_SOMETHING symbols used by the MinGW headers, if > > that will help. > > I looked into that and came up with the following list: > > __need_NULL > __need_ptrdiff_t > __need_size_t > __need_struct_timespec > __need_time_t > __need___va_list > __need_wchar_t > __need_wint_t I think this is about right. > The only other Gnulib header that refers to any of these symbols is stddef.in.h > and if it's not used in MinGW then I expect we don't need to worry about it. It's your call. > Though there may be issues in MinGW itself, if it's using these symbols > incompatibly with what GCC expects (stddef.h is a GCC header). And MinGW comes with it. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-27 15:22 ` Eli Zaretskii @ 2015-07-27 15:38 ` Eli Zaretskii 0 siblings, 0 replies; 16+ messages in thread From: Eli Zaretskii @ 2015-07-27 15:38 UTC (permalink / raw) To: eggert; +Cc: 21020, wgg2 > Date: Mon, 27 Jul 2015 18:22:14 +0300 > From: Eli Zaretskii <eliz@gnu.org> > Cc: 21020@debbugs.gnu.org, wgg2@member.fsf.org > > > The only other Gnulib header that refers to any of these symbols is stddef.in.h > > and if it's not used in MinGW then I expect we don't need to worry about it. > > It's your call. Btw, I didn't mean only headers used by Emacs, I meant all the Gnulib headers that reference those symbols in a similar manner (i.e. to exclude certain parts of the header). ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-27 12:29 ` Eli Zaretskii 2015-07-27 15:02 ` Paul Eggert @ 2015-07-27 17:29 ` Eli Zaretskii 2015-07-28 1:09 ` Paul Eggert 1 sibling, 1 reply; 16+ messages in thread From: Eli Zaretskii @ 2015-07-27 17:29 UTC (permalink / raw) To: eggert; +Cc: 21020, wgg2 > Date: Mon, 27 Jul 2015 15:29:33 +0300 > From: Eli Zaretskii <eliz@gnu.org> > Cc: 21020@debbugs.gnu.org, wgg2@member.fsf.org > > After the above commit, Emacs builds and generally seems to work, but > crashes inside icalendar tests. Seems like something with heap > corruption. I'm looking into that now. The reason seems to be these assignments in time_rz.c: 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; } } static void restore_tzname (void) { #if HAVE_TZNAME if (tzname_address) { *tzname_address = tzname_value; <<<<<<<<<<<<<<<<<<<<<< tzname_address = NULL; } #endif } If I ifdef away the 2 marked lines, the test suite runs flawlessly to completion. tzname_address is the address of one of the members of the tzname[] array, which holds pointers into the bowels of libc. I don't know why overwriting them with our values causes such trouble, but even if this is supposed to work, it makes me nervous. I'm not even sure I understand completely why this trick is needed (can you explain?). In any case, can we please do this in some less intrusive way, e.g., by copying the TZ names to our private storage? Thanks. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-27 17:29 ` Eli Zaretskii @ 2015-07-28 1:09 ` Paul Eggert 2015-07-28 10:37 ` Eli Zaretskii 0 siblings, 1 reply; 16+ messages in thread From: Paul Eggert @ 2015-07-28 1:09 UTC (permalink / raw) To: Eli Zaretskii; +Cc: 21020, wgg2 Eli Zaretskii wrote: > I'm not even sure I > understand completely why this trick is needed (can you explain?). On many platforms, calling 'tzset' (either directly, or indirectly via localtime etc.) invalidates tzname[0] and tzname[1]. For example: time_t t = 0; char *p = tzname[0]; localtime (&t); puts (p ? p : "(null)"); Here 'puts' has undefined behavior if tzname[0] has been updated and its old value (equal to P) is no longer a valid pointer. lib/time_rz.c attempted to work around this problem by replacing tzname[0] and tzname[1] with pointers to more-stable copies. Apparently this doesn't work under MinGW. > can we please do this in some less intrusive way, e.g., by > copying the TZ names to our private storage? OK, I did that in master commit 2856b1dd6f0ff5164eb5a54ddfadb9963f9e9237. ^ permalink raw reply [flat|nested] 16+ messages in thread
* bug#21020: 24.4; `display-time-world' tampers with TZ 2015-07-28 1:09 ` Paul Eggert @ 2015-07-28 10:37 ` Eli Zaretskii 0 siblings, 0 replies; 16+ messages in thread From: Eli Zaretskii @ 2015-07-28 10:37 UTC (permalink / raw) To: Paul Eggert; +Cc: 21020, wgg2 > Date: Mon, 27 Jul 2015 18:09:37 -0700 > From: Paul Eggert <eggert@cs.ucla.edu> > CC: 21020@debbugs.gnu.org, wgg2@member.fsf.org > > Eli Zaretskii wrote: > > I'm not even sure I > > understand completely why this trick is needed (can you explain?). > > On many platforms, calling 'tzset' (either directly, or indirectly via localtime > etc.) invalidates tzname[0] and tzname[1]. For example: > > time_t t = 0; > char *p = tzname[0]; > localtime (&t); > puts (p ? p : "(null)"); > > Here 'puts' has undefined behavior if tzname[0] has been updated and its old > value (equal to P) is no longer a valid pointer. > > lib/time_rz.c attempted to work around this problem by replacing tzname[0] and > tzname[1] with pointers to more-stable copies. Apparently this doesn't work > under MinGW. I see, thanks for explaining. > > can we please do this in some less intrusive way, e.g., by > > copying the TZ names to our private storage? > > OK, I did that in master commit 2856b1dd6f0ff5164eb5a54ddfadb9963f9e9237. Thanks, this solves the problem. ^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2015-07-28 10:37 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2015-07-09 19:39 bug#21020: 24.4; `display-time-world' tampers with TZ William G. Gardella 2015-07-26 7:09 ` Paul Eggert 2015-07-26 14:48 ` Eli Zaretskii 2015-07-26 17:05 ` Paul Eggert 2015-07-26 17:34 ` Eli Zaretskii 2015-07-26 17:37 ` Eli Zaretskii 2015-07-26 17:55 ` Paul Eggert 2015-07-26 18:21 ` Eli Zaretskii 2015-07-26 19:48 ` Paul Eggert 2015-07-27 12:29 ` Eli Zaretskii 2015-07-27 15:02 ` Paul Eggert 2015-07-27 15:22 ` Eli Zaretskii 2015-07-27 15:38 ` Eli Zaretskii 2015-07-27 17:29 ` Eli Zaretskii 2015-07-28 1:09 ` Paul Eggert 2015-07-28 10:37 ` Eli Zaretskii
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).