1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-17 17:20:29 +02:00

SRFI-19: Fix TAI->UTC conversions, leap second handling, etc.

Fixes <https://bugs.gnu.org/21911>.
Fixes <https://bugs.gnu.org/22034>.
Fixes <https://bugs.gnu.org/21902>.
Partially fixes <https://bugs.gnu.org/21904>.
Reported by Zefram <zefram@fysh.org>.

* doc/ref/srfi-modules.texi (SRFI-19 Introduction): Fix the definitions
of Julian Day and Modified Julian Day.  Give the correct full names of
UTC and TAI.
* module/srfi/srfi-19.scm: Import (srfi srfi-1).  Use modern Guile
keyword syntax in the 'define-module' form.
(leap-second-neg-delta): New procedure, derived from a similar procedure
in the latest upstream SRFI-19 reference implementation.
(priv:time-tai->time-utc!, time-tai->julian-day)
(time-monotonic->julian-day): Use 'leap-second-neg-delta'.
(local-tz-offset): Fix comment.
(leap-second?): Remove.
(tai-before-leap-second?): New procedure, derived from upstream SRFI-19.
(time-utc->date): Use 'define*' to handle the optional argument.  Remove
the leap second handling, following upstream SRFI-19.
(time-tai->date): Rewrite in terms of 'time-utc->date'.  Add special
leap second handling, following upstream SRFI-19.
(time-monotonic->date): Rewrite in terms of 'time-tai->date'.
(date->time-tai, date->time-monotonic): Add special leap second
handling, following upstream SRFI-19.
(directives): In the entry for the "~Y" escape in 'date->string', pad
the year field to 4 characters, following upstream SRFI-19.
* test-suite/tests/srfi-19.test: Add tests.
This commit is contained in:
Mark H Weaver 2018-10-20 03:34:56 -04:00 committed by Andy Wingo
parent 4e24cca595
commit e00563492a
3 changed files with 182 additions and 102 deletions

View file

@ -40,13 +40,14 @@
;; the DATE structure.
(define-module (srfi srfi-19)
:use-module (srfi srfi-6)
:use-module (srfi srfi-8)
:use-module (srfi srfi-9)
:autoload (ice-9 rdelim) (read-line)
:use-module (ice-9 i18n)
:replace (current-time)
:export (;; Constants
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-6)
#:use-module (srfi srfi-8)
#:use-module (srfi srfi-9)
#:autoload (ice-9 rdelim) (read-line)
#:use-module (ice-9 i18n)
#:replace (current-time)
#:export (;; Constants
time-duration
time-monotonic
time-process
@ -244,6 +245,16 @@
(if (< utc-seconds (* (- 1972 1970) 365 sid)) 0
(lsd leap-second-table))))
;; going from tai seconds to utc seconds ...
(define (leap-second-neg-delta tai-seconds)
(letrec ((lsd (lambda (table)
(cond ((null? table) 0)
((>= tai-seconds (+ (caar table) (cdar table)))
(cdar table))
(else (lsd (cdr table)))))) )
(if (< tai-seconds (* (- 1972 1970) 365 sid)) 0
(lsd leap-second-table))))
;;; the TIME structure; creates the accessors, too.
@ -449,7 +460,7 @@
(set-time-type! time-out time-utc)
(set-time-nanosecond! time-out (time-nanosecond time-in))
(set-time-second! time-out (- (time-second time-in)
(leap-second-delta
(leap-second-neg-delta
(time-second time-in))))
time-out)
@ -594,7 +605,7 @@
;; This should be written to be OS specific.
(define (local-tz-offset utc-time)
;; SRFI uses seconds West, but guile (and libc) use seconds East.
;; SRFI 19 uses seconds East, but 'tm:gmtoff' returns seconds West.
(- (tm:gmtoff (localtime (time-second utc-time)))))
;; special thing -- ignores nanos
@ -603,21 +614,16 @@
sid)
tai-epoch-in-jd))
(define (leap-second? second)
(and (assoc second leap-second-table) #t))
(define (tai-before-leap-second? second)
(any (lambda (x)
(= second (+ (car x) (cdr x) -1)))
leap-second-table))
(define (time-utc->date time . tz-offset)
(define* (time-utc->date time #:optional (tz-offset
(local-tz-offset time)))
(if (not (eq? (time-type time) time-utc))
(time-error 'time-utc->date 'incompatible-time-types time))
(let* ((offset (if (null? tz-offset)
(local-tz-offset time)
(car tz-offset)))
(leap-second? (leap-second? (+ offset (time-second time))))
(jdn (time->julian-day-number (if leap-second?
(- (time-second time) 1)
(time-second time))
offset)))
(let ((jdn (time->julian-day-number (time-second time) tz-offset)))
(call-with-values (lambda () (decode-julian-day-number jdn))
(lambda (secs date month year)
;; secs is a real because jdn is a real in Guile;
@ -628,78 +634,34 @@
(minutes (quotient rem 60))
(seconds (remainder rem 60)))
(make-date (time-nanosecond time)
(if leap-second? (+ seconds 1) seconds)
seconds
minutes
hours
date
month
year
offset))))))
tz-offset))))))
(define (time-tai->date time . tz-offset)
(if (not (eq? (time-type time) time-tai))
(time-error 'time-tai->date 'incompatible-time-types time))
(let* ((offset (if (null? tz-offset)
(local-tz-offset (time-tai->time-utc time))
(car tz-offset)))
(seconds (- (time-second time)
(leap-second-delta (time-second time))))
(leap-second? (leap-second? (+ offset seconds)))
(jdn (time->julian-day-number (if leap-second?
(- seconds 1)
seconds)
offset)))
(call-with-values (lambda () (decode-julian-day-number jdn))
(lambda (secs date month year)
;; secs is a real because jdn is a real in Guile;
;; but it is conceptionally an integer.
;; adjust for leap seconds if necessary ...
(let* ((int-secs (inexact->exact (round secs)))
(hours (quotient int-secs (* 60 60)))
(rem (remainder int-secs (* 60 60)))
(minutes (quotient rem 60))
(seconds (remainder rem 60)))
(make-date (time-nanosecond time)
(if leap-second? (+ seconds 1) seconds)
minutes
hours
date
month
year
offset))))))
(if (tai-before-leap-second? (time-second time))
;; If it's *right* before the leap, we must handle this case to
;; avoid the information lost when converting to UTC. We subtract
;; a second before conversion, and then effectively add it back
;; after conversion by setting the second field to 60.
(let ((d (apply time-utc->date
(subtract-duration! (time-tai->time-utc time)
(make-time time-duration 0 1))
tz-offset)))
(set-date-second! d 60)
d)
(apply time-utc->date (time-tai->time-utc time) tz-offset)))
;; this is the same as time-tai->date.
(define (time-monotonic->date time . tz-offset)
(if (not (eq? (time-type time) time-monotonic))
(time-error 'time-monotonic->date 'incompatible-time-types time))
(let* ((offset (if (null? tz-offset)
(local-tz-offset (time-monotonic->time-utc time))
(car tz-offset)))
(seconds (- (time-second time)
(leap-second-delta (time-second time))))
(leap-second? (leap-second? (+ offset seconds)))
(jdn (time->julian-day-number (if leap-second?
(- seconds 1)
seconds)
offset)))
(call-with-values (lambda () (decode-julian-day-number jdn))
(lambda (secs date month year)
;; secs is a real because jdn is a real in Guile;
;; but it is conceptionally an integer.
;; adjust for leap seconds if necessary ...
(let* ((int-secs (inexact->exact (round secs)))
(hours (quotient int-secs (* 60 60)))
(rem (remainder int-secs (* 60 60)))
(minutes (quotient rem 60))
(seconds (remainder rem 60)))
(make-date (time-nanosecond time)
(if leap-second? (+ seconds 1) seconds)
minutes
hours
date
month
year
offset))))))
(apply time-tai->date (time-monotonic->time-tai time) tz-offset))
(define (date->time-utc date)
(let* ((jdays (- (encode-julian-day-number (date-day date)
@ -717,11 +679,17 @@
(date-second date)
(- (date-zone-offset date))))))
(define (date->time-tai date)
(time-utc->time-tai! (date->time-utc date)))
(define (date->time-tai d)
(if (= (date-second d) 60)
(subtract-duration! (time-utc->time-tai! (date->time-utc d))
(make-time time-duration 0 1))
(time-utc->time-tai! (date->time-utc d))))
(define (date->time-monotonic date)
(time-utc->time-monotonic! (date->time-utc date)))
(define (date->time-monotonic d)
(if (= (date-second d) 60)
(subtract-duration! (time-utc->time-monotonic! (date->time-utc d))
(make-time time-duration 0 1))
(time-utc->time-monotonic! (date->time-utc d))))
(define (leap-year? year)
(or (= (modulo year 400) 0)
@ -835,7 +803,7 @@
(if (not (eq? (time-type time) time-tai))
(time-error 'time-tai->julian-day 'incompatible-time-types time))
(+ (/ (+ (- (time-second time)
(leap-second-delta (time-second time)))
(leap-second-neg-delta (time-second time)))
(/ (time-nanosecond time) nano))
sid)
tai-epoch-in-jd))
@ -849,7 +817,7 @@
(if (not (eq? (time-type time) time-monotonic))
(time-error 'time-monotonic->julian-day 'incompatible-time-types time))
(+ (/ (+ (- (time-second time)
(leap-second-delta (time-second time)))
(leap-second-neg-delta (time-second time)))
(/ (time-nanosecond time) nano))
sid)
tai-epoch-in-jd))
@ -1093,7 +1061,10 @@
2)
port)))
(cons #\Y (lambda (date pad-with port)
(display (date-year date) port)))
(display (padding (date-year date)
pad-with
4)
port)))
(cons #\z (lambda (date pad-with port)
(tz-printer (date-zone-offset date) port)))
(cons #\Z (lambda (date pad-with port)