;;;; "timecore.scm" Core time conversion routines
;;; Copyright (C) 1994, 1997, 2004, 2005 Aubrey Jaffer
;
;Permission to copy this software, to modify it, to redistribute it,
;to distribute modified versions, and to use it for any purpose is
;granted, subject to the following restrictions and understandings.
;
;1. Any copy made of this software must include this copyright notice
;in full.
;
;2. I have made no warranty or representation that the operation of
;this software will be error-free, and I am under no obligation to
;provide any services, by way of maintenance, update, or otherwise.
;
;3. In conjunction with products arising from the use of this
;material, there shall be no use of my name in any advertising,
;promotional, or sales literature without prior written consent in
;each case.
;;; No, it doesn't do leap seconds.
(require 'multiarg-apply)
(define time:days/month
'#(#(31 28 31 30 31 30 31 31 30 31 30 31) ; Normal years.
#(31 29 31 30 31 30 31 31 30 31 30 31)))
(define (leap-year? year)
(and (zero? (remainder year 4))
(or (not (zero? (remainder year 100)))
(zero? (remainder year 400))))) ; Leap years.
;;; Returns the `struct tm' representation of T,
;;; offset TM_GMTOFF seconds east of UCT.
;@
(define (time:split t tm_isdst tm_gmtoff tm_zone)
(define tms (inexact->exact
(round (- (difftime t time:year-70) tm_gmtoff))))
(let* ((secs (modulo tms 86400)) ; SECS/DAY
(days (+ (quotient tms 86400) ; SECS/DAY
(if (and (negative? tms) (positive? secs)) -1 0))))
(let ((tm_hour (quotient secs 3600))
(secs (remainder secs 3600))
(tm_wday (modulo (+ 4 days) 7))) ; January 1, 1970 was a Thursday.
(let loop ((tm_year 1970)
(tm_yday days))
(let ((diy (if (leap-year? tm_year) 366 365)))
(cond
((negative? tm_yday) (loop (+ -1 tm_year) (+ tm_yday diy)))
((>= tm_yday diy) (loop (+ 1 tm_year) (- tm_yday diy)))
(else
(let ((mv (vector-ref time:days/month (- diy 365))))
(do ((tm_mon 0 (+ 1 tm_mon))
(tm_mday tm_yday (- tm_mday (vector-ref mv tm_mon))))
((< tm_mday (vector-ref mv tm_mon))
(vector
(remainder secs 60) ; Seconds. [0-61] (2 leap seconds)
(quotient secs 60) ; Minutes. [0-59]
tm_hour ; Hours. [0-23]
(+ tm_mday 1) ; Day. [1-31]
tm_mon ; Month. [0-11]
(- tm_year 1900) ; Year - 1900.
tm_wday ; Day of week. [0-6]
tm_yday ; Days in year. [0-365]
tm_isdst ; DST. [-1/0/1]
tm_gmtoff ; Seconds west of UTC.
tm_zone ; Timezone abbreviation.
)))))))))))
(define time:year-70
(let ((t (current-time)))
(offset-time t (- (difftime t 0)))))
;@
(define (time:invert decoder target)
(let* ((times '#(1 60 3600 86400 2678400 32140800))
(trough ; rough time for target
(do ((i 5 (+ i -1))
(trough time:year-70
(offset-time trough (* (vector-ref target i)
(vector-ref times i)))))
((negative? i) trough))))
;;; (print 'trough trough 'target target)
(let loop ((guess trough)
(j 0)
(guess-tm (decoder trough)))
;;; (print 'guess guess 'guess-tm guess-tm)
(do ((i 5 (+ i -1))
(rough time:year-70
(offset-time rough (* (vector-ref guess-tm i)
(vector-ref times i))))
(sign (let ((d (- (vector-ref target 5)
(vector-ref guess-tm 5))))
(and (not (zero? d)) d))
(or sign
(let ((d (- (vector-ref target i)
(vector-ref guess-tm i))))
(and (not (zero? d)) d)))))
((negative? i)
(let ((distance (abs (difftime trough rough))))
(cond ((and (zero? distance) sign)
;;; (print "trying to jump")
(set! distance (if (negative? sign) -86400 86400)))
((and sign (negative? sign)) (set! distance (- distance))))
(set! guess (offset-time guess distance))
;;; (print 'distance distance 'sign sign)
(cond ((zero? distance) guess)
((> j 5) #f) ;to prevent inf loops.
(else
(loop guess
(+ 1 j)
(decoder guess))))))))))
;@
(define (time:gmtime tm)
(time:split tm 0 0 "GMT"))
;;;; Use the timezone
(define (tzrule->caltime year previous-gmt-offset
tr-month tr-week tr-day tr-time)
(define leap? (leap-year? year))
(define gmmt
(time:invert time:gmtime
(vector 0 0 0 1 (if tr-month (+ -1 tr-month) 0) year #f #f 0)))
(offset-time
gmmt
(+ tr-time previous-gmt-offset
(* 3600 24
(if tr-month
(let ((fdow (vector-ref (time:gmtime gmmt) 6)))
(case tr-week
((1 2 3 4) (+ (modulo (- tr-day fdow) 7)
(* 7 (+ -1 tr-week))))
((5)
(do ((mmax (vector-ref
(vector-ref time:days/month (if leap? 1 0))
(+ -1 tr-month)))
(d (modulo (- tr-day fdow) 7) (+ 7 d)))
((>= d mmax) (+ -7 d))))
(else (slib:error 'tzrule->caltime
"week out of range" tr-week))))
(+ tr-day
(if (and (not tr-week) (>= tr-day 60) (leap-year? year))
1 0)))))))
;@
(define (tz:params caltime tz)
(case (vector-ref tz 0)
((tz:fixed) (list 0 (vector-ref tz 3) (vector-ref tz 2)))
((tz:rule)
(let* ((year (vector-ref (time:gmtime caltime) 5))
(ttime0 (apply tzrule->caltime
year (vector-ref tz 4) (vector-ref tz 6)))
(ttime1 (apply tzrule->caltime
year (vector-ref tz 5) (vector-ref tz 7)))
(dst (if (and (not (negative? (difftime caltime ttime0)))
(negative? (difftime caltime ttime1)))
1 0)))
(list dst (vector-ref tz (+ 4 dst)) (vector-ref tz (+ 2 dst)))
;;(for-each display (list (gtime ttime0) (gtime caltime) (gtime ttime1)))
))
((tz:file) (let ((zone-spec (tzfile:get-zone-spec caltime tz)))
(list (if (vector-ref zone-spec 2) 1 0)
(- (vector-ref zone-spec 1))
(vector-ref zone-spec 0))))
(else (slib:error 'tz:params "unknown timezone type" tz))))
(define (tzfile:transition-index time zone)
(define times (difftime time time:year-70))
(and zone
(apply
(lambda (path mode-table leap-seconds transition-times transition-types)
(let ((ntrns (vector-length transition-times)))
(if (zero? ntrns) -1
(let loop ((lidx (quotient (+ 1 ntrns) 2))
(jmp (quotient (+ 1 ntrns) 4)))
(let* ((idx (max 0 (min lidx (+ -1 ntrns))))
(idx-time (vector-ref transition-times idx)))
(cond ((<= jmp 0)
(+ idx (if (>= times idx-time) 0 -1)))
((= times idx-time) idx)
((and (zero? idx) (< times idx-time)) -1)
((and (not (= idx lidx)) (not (< times idx-time))) idx)
(else
(loop ((if (< times idx-time) - +) idx jmp)
(if (= 1 jmp) 0 (quotient (+ 1 jmp) 2))))))))))
(cdr (vector->list zone)))))
(define (tzfile:get-std-spec mode-table)
(do ((type-idx 0 (+ 1 type-idx)))
((or (>= type-idx (vector-length mode-table))
(not (vector-ref (vector-ref mode-table type-idx) 2)))
(if (>= type-idx (vector-length mode-table))
(vector-ref mode-table 0)
(vector-ref mode-table type-idx)))))
(define (tzfile:get-zone-spec time zone)
(apply
(lambda (path mode-table leap-seconds transition-times transition-types)
(let ((trans-idx (tzfile:transition-index time zone)))
(if (zero? (vector-length transition-types))
(vector-ref mode-table 0)
(if (negative? trans-idx)
(tzfile:get-std-spec mode-table)
(vector-ref mode-table
(vector-ref transition-types trans-idx))))))
(cdr (vector->list zone))))