;;;; srfi-19-support.scm ;;;; Chicken port, Kon Lovett, Dec '05 ;; SRFI-19: Time Data Types and Procedures. ;; ;; Copyright (C) I/NET, Inc. (2000, 2002, 2003). All Rights Reserved. ;; Copyright (C) Neodesic Corporation (2000). All Rights Reserved. ;; ;; This document and translations of it may be copied and furnished to others, ;; and derivative works that comment on or otherwise explain it or assist in its ;; implementation may be prepared, copied, published and distributed, in whole or ;; in part, without restriction of any kind, provided that the above copyright ;; notice and this paragraph are included on all such copies and derivative works. ;; However, this document itself may not be modified in any way, such as by ;; removing the copyright notice or references to the Scheme Request For ;; Implementation process or editors, except as needed for the purpose of ;; developing SRFIs in which case the procedures for copyrights defined in the SRFI ;; process must be followed, or as required to translate it into languages other ;; than English. ;; ;; The limited permissions granted above are perpetual and will not be revoked ;; by the authors or their successors or assigns. ;; ;; This document and the information contained herein is provided on an "AS IS" ;; basis and THE AUTHOR AND THE SRFI EDITORS DISCLAIM ALL WARRANTIES, EXPRESS OR ;; IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE ;; INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF ;; MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. ;; Issues ;; ;; - Gregorian calendar only. ;; ;; - Initialization is scattered throughout the code, so converting to a module will ;; involve some search. ;; ;; - Some errors have incorrect procedure labels (not the top-level loc) ;; Bugs ;; ;; - The 'date-dst?' field is problimatic. It is only valid on certain ;; platforms & only when current. A past or future date will not have this ;; field correct! ;; ;; - Time -> Date conversion takes account of the CURRENT daylight saving time state, ;; NOT the state of the converted date. ;; Notes ;; ;; - There is no year zero. So when converting from a BCE year on the sign of the year ;; needs to be changed, do not subtract one. i.e. 4714 BCE is -4714, not -4713! ;; ;; - Uses ISO 8601 timezone offset interpretation! So an added offset is "away" from ;; UTC & a subtracted offset is "towards" UTC. ;; ;; - Monotonic Time (almost) same as TAI. To redefine Monotonic Time must visit every ;; conversion procedure. (module srfi-19-support (;export ; time? time-type? time-seconds? time-nanoseconds? clock-type? date? date-nanoseconds? date-seconds? date-minutes? date-hours? date-day? date-month? date-year? week-day? julian-day? time-record-printer-format date-record-printer-format ; check-time check-time-type check-time-seconds check-time-nanoseconds check-raw-seconds check-raw-milliseconds check-time-has-type check-time-and-type check-duration check-time-elements #;check-times check-time-binop check-time-compare check-time-aritmetic check-clock-type check-date check-date-nanoseconds check-date-seconds check-date-minutes check-date-hours check-date-day check-date-month check-date-year check-date-elements check-date-compatible-timezone-offsets check-week-day check-julian-day ; error-time error-time-type error-time-seconds error-time-nanoseconds error-incompatible-time-types error-clock-type error-convert error-date error-date-nanoseconds error-date-seconds error-date-minutes error-date-hours error-date-day error-date-month error-date-year error-date-compatible-timezone error-week-day error-julian-day ; tm:read-tai-utc-data tm:calc-second-before-leap-second-table tm:read-leap-second-table (tm:any-time %make-time) (tm:some-time %make-time) (tm:as-some-time %time-type %make-time) (tm:time-type %time-type) (tm:time-nanosecond %time-second) (tm:time-second %time-nanosecond) (tm:time-type-set! %time-type-set!) (tm:time-nanosecond-set! %time-nanosecond-set!) (tm:time-second-set! %time-second-set!) tm:make-time (tm:copy-time %make-time) (tm:time-has-type? %time-type) tm:nanoseconds->time-values tm:time->nanoseconds tm:time->milliseconds tm:nanoseconds->seconds tm:milliseconds->seconds tm:time->seconds tm:duration-elements->time-values tm:milliseconds->time-values tm:seconds->time-values tm:seconds->time (tm:current-time-values tm:current-nanoseconds) tm:current-time-utc (tm:current-time-tai leap-second-delta) tm:current-time-monotonic (tm:current-time-thread current-thread-milliseconds) (tm:current-time-process current-process-milliseconds) (tm:current-time-gc current-gc-milliseconds total-gc-milliseconds) tm:time-resolution tm:time-compare tm:time=? tm:time? tm:time>=? tm:time-max tm:time-min tm:time-difference tm:add-duration tm:subtract-duration tm:divide-duration tm:multiply-duration tm:time-abs tm:time-negate tm:time-zero? tm:time-positive? tm:time-negative? (tm:time-tai->time-utc leap-second-neg-delta) tm:time-tai->time-monotonic tm:time-utc->time-tai tm:time-utc->time-monotonic tm:time-monotonic->time-tai tm:time-monotonic->time-utc tm:leap-year? (tm:leap-day? +leap-year-dys/mn+) (tm:days-in-month +leap-year-dys/mn+ +year-dys/mn+) (tm:date-nanosecond %date-nanosecond) (tm:date-second %date-second) (tm:date-minute %date-minute) (tm:date-hour %date-hour) (tm:date-day %date-day) (tm:date-month %date-month) (tm:date-year %date-year) (tm:date-zone-offset %date-zone-offset) (tm:date-zone-name %date-zone-name) (tm:date-dst? %date-dst?) tm:date-wday tm:date-yday tm:date-jday (tm:date-timezone-info %make-date-timezone-info) (tm:date-nanosecond-set! %date-nanosecond-set!) (tm:date-second-set! %date-second-set!) (tm:date-minute-set! %date-minute-set!) (tm:date-hour-set! %date-hour-set!) (tm:date-day-set! %date-day-set!) (tm:date-month-set! %date-month-set!) (tm:date-year-set! %date-year-set!) (tm:date-zone-offset-set! %date-zone-offset-set!) (tm:make-incomplete-date %make-date) (tm:make-date %make-date) (tm:copy-date %date-nanosecond %date-second %date-minute %date-hour %date-day %date-month %date-year %date-zone-offset %date-zone-name %date-jday %date-yday %date-wday %make-date) tm:seconds->date/type tm:current-date (tm:date-compare %date-nanosecond %date-second %date-minute %date-hour %date-day %date-month %date-year) tm:decode-julian-day-number tm:seconds->julian-day-number tm:tai-before-leap-second? tm:time-utc->date tm:time-tai->date tm:time->date tm:encode-julian-day-number tm:date->time-utc tm:date->time-tai tm:date->time-monotonic tm:date->time tm:natural-year tm:year-day tm:date-year-day tm:week-day tm:days-before-first-week tm:date-week-day tm:date-week-number tm:julian-day->modified-julian-day tm:julian-day (tm:date->julian-day %date-nanosecond %date-second %date-minute %date-hour %date-day %date-month %date-year %date-zone-offset %date-jday %date-jday-set!) tm:seconds->julian-day tm:time-utc->julian-day tm:time-tai->julian-day tm:time-monotonic->julian-day tm:time->julian-day tm:time-utc->modified-julian-day tm:time-tai->modified-julian-day tm:time-monotonic->modified-julian-day tm:time->modified-julian-day tm:julian-day->nanoseconds tm:julian-day->time-values tm:modified-julian-day->julian-day tm:julian-day->time-utc tm:default-date-adjust-integer) (import (except scheme + - * / remainder quotient abs round floor truncate real? integer? inexact? zero? negative? positive? = <= >= < > inexact->exact exact->inexact string->number) chicken (only srfi-1 fold) (only posix seconds->utc-time) (only extras format read-line) (only data-structures conc) (only ports with-input-from-port with-input-from-string) (only numbers + - * / remainder quotient abs round floor truncate real? integer? inexact? zero? negative? positive? = <= >= < > inexact->exact exact->inexact string->number) locale record-variants type-checks type-errors srfi-19-timezone) (require-library srfi-18 posix extras ports numbers locale record-variants type-checks type-errors srfi-19-timezone) ;;; (include "srfi-19-common") ;;;NOTE the use of syntax for inlining is an experiment. no procedure w/ arithmetic can ;;;be exported as syntax. ;; ;; For storage savings since some aritmetic routines do not ;; return fixnums when possible. ;; ;; ##sys#integer? ;; returns #t for integer fixnum or flonum ;; ;; C_double_to_number ;; returns a fixnum for the flonum x iff x isa integer in fixnum-range ;; otherwise the flonum x ;; ;; When domain is integer and range is fixnum ;; Number MUST be a fixnum or flonum (define-syntax number->genint (syntax-rules () ((_ ?x) (let ((x ?x)) #; ;DEBUG (when (or (< x -1073741824) (< 1073741823 x)) (print "*** > 31 bits: " x " flonum?: " (flonum? x))) (if (fixnum? x) x (let ((x (floor x))) (if (fixnum? x) x (##core#inline "C_double_to_number" (exact->inexact x))) ) ) ) ) ) ) ;;; Timing Routines ;; Provide system timing reporting procedures (define total-gc-milliseconds (let ((accum-ms 0)) (lambda () (set! accum-ms (+ accum-ms (current-gc-milliseconds))) accum-ms ) ) ) (define (current-process-milliseconds) (let-values (((ums sms) (cpu-time))) (+ ums sms) ) ) ;FIXME needs a srfi-18 extension (define current-thread-milliseconds current-process-milliseconds) ;;; Constants ;; TAI-EPOCH: 1 January 1970 CE at 00:00:00 UTC (define-constant TAI-EPOCH-YEAR 1970) ;(Chicken reader doesn't grok ratios w/o numbers egg at compile time.) ;; Used in julian calculation (define ONE-HALF (string->number "1/2")) ;; Julian Day 0 = 1 January 4713 BCE at 12:00:00 UTC (Julian proleptic calendar) ;; Julian Day 0 = 24 November 4714 BCE at 12:00:00 UTC (Gregorian proleptic calendar) (define TAI-EPOCH-IN-JD (string->number "4881175/2")) ;; Modified Julian Day 0 = 17 Nov 1858 CE at 00:00:00 UTC ;; Number of days less than a julian day. (define TAI-EPOCH-IN-MODIFIED-JD (string->number "4800001/2")) ;; Julian conversion base century (define-constant JDYR 4800) ;;; Leap Seconds ;; First leap year after epoch (define-constant FIRST-LEAP-YEAR 1972) ;; Number of seconds after epoch of first leap year (define LEAP-START (fx* (fx- FIRST-LEAP-YEAR TAI-EPOCH-YEAR) (fx* DY/YR SEC/DY)) ) ;; A table of leap seconds ;; See "ftp://maia.usno.navy.mil/ser7/tai-utc.dat" and update as necessary. ;; Each entry is (utc seconds since epoch . # seconds to add for tai) ;; Note they go higher (2009) to lower (1972). (define tm:leap-second-table '((1435708800 . 36) (1341100800 . 35) (1230768000 . 34) (1136073600 . 33) (915148800 . 32) (867715200 . 31) (820454400 . 30) (773020800 . 29) (741484800 . 28) (709948800 . 27) (662688000 . 26) (631152000 . 25) (567993600 . 24) (489024000 . 23) (425865600 . 22) (394329600 . 21) (362793600 . 20) (315532800 . 19) (283996800 . 18) (252460800 . 17) (220924800 . 16) (189302400 . 15) (157766400 . 14) (126230400 . 13) (94694400 . 12) (78796800 . 11) (63072000 . 10) #;(-60480000 . 4.21317) ; Before 1972 #;(-126230400 . 4.31317) #;(-136771200 . 3.84013) #;(-142128000 . 3.74013) #;(-152668800 . 3.64013) #;(-157766400 . 3.54013) #;(-168307200 . 3.44013) #;(-181526400 . 3.34013) #;(-189388800 . 3.24013) #;(-194659200 . 1.945858) #;(-252460800 . 1.845858) #;(-265680000 . 1.372818) #;(-283996800 . 1.422818) ) ) ;; This procedure reads the file in the ;; ftp://maia.usno.navy.mil/ser7/tai-utc.dat format and ;; creates a leap second table (define (tm:read-tai-utc-data flnm) (define (convert-jd jd) (* (- (inexact->exact jd) TAI-EPOCH-IN-JD) SEC/DY)) (define (convert-sec sec) (inexact->exact sec)) (define (read-data) (let loop ((ls '())) (let ((line (read-line))) (if (eof-object? line) ls (let ((data (with-input-from-string (string-append "(" line ")") read))) (let ((year (car data)) (jd (cadddr (cdr data))) (secs (cadddr (cdddr data)))) (loop (if (< year FIRST-LEAP-YEAR) ls (cons (cons (convert-jd jd) (convert-sec secs)) ls))) ) ) ) ) ) ) (with-input-from-port (open-input-file flnm) read-data) ) ;; Table of cummulative seconds, one second before the leap second. (define (tm:calc-second-before-leap-second-table table) (let loop ((inlst table) (outlst '())) (if (null? inlst) (reverse outlst) ;keep input order anyway (let ((itm (car inlst))) (loop (cdr inlst) (cons (- (+ (car itm) (cdr itm)) 1) outlst)))) ) ) (define tm:second-before-leap-second-table (tm:calc-second-before-leap-second-table tm:leap-second-table)) ;; Read a leap second table file in U.S. Naval Observatory format (define (tm:read-leap-second-table flnm) (set! tm:leap-second-table (tm:read-tai-utc-data flnm)) (set! tm:second-before-leap-second-table (tm:calc-second-before-leap-second-table tm:leap-second-table)) ) ;; leap-second-delta algorithm ; 'leap-second-item' is like the 'it' in the anaphoric 'if' ; (define-syntax find-leap-second-delta* (er-macro-transformer (lambda (form r c) (let ((_let (r 'let)) (_if (r 'if)) (_null? (r 'null?)) (_car (r 'car)) (_cdr (r 'cdr)) (_leap-second-item (r 'leap-second-item)) ) (let ((?secs (cadr form)) (?ls (caddr form)) (?tst (cadddr form)) ) `(,_let loop ((lsvar ,?ls)) (,_if (,_null? lsvar) 0 (,_let ((leap-second-item (,_car lsvar))) (,_if ,?tst (,_cdr leap-second-item) (loop (,_cdr lsvar)) ) ) ) ) ) ) ) ) ) (define-syntax leap-second-delta* (er-macro-transformer (lambda (form r c) (let ((_let (r 'let)) (_if (r 'if)) (_< (r '<)) (_tm:leap-second-table (r 'tm:leap-second-table)) (_LEAP-START (r 'LEAP-START)) (_find-leap-second-delta* (r 'find-leap-second-delta*)) ) (let ((?secs (cadr form)) (?tst (caddr form)) ) `(,_if (,_< ,?secs ,_LEAP-START) 0 (,_find-leap-second-delta* ,?secs ,_tm:leap-second-table ,?tst) ) ) ) ) ) ) ;; Going from utc seconds ... (define (leap-second-delta utc-seconds) (leap-second-delta* utc-seconds (<= (car leap-second-item) utc-seconds)) ) ;; Going from tai seconds to utc seconds ... (define (leap-second-neg-delta tai-seconds) (leap-second-delta* tai-seconds (<= (cdr leap-second-item) (- tai-seconds (car leap-second-item)))) ) ;;; Time Object (Public Mutable) ;; There are 3 kinds of time record procedures: ;; *... - generated ;; tm:... - argument processing then *... ;; ... - argument checking then tm:... #; ; (define-record-type-variant time (unchecked inline unsafe) (%make-time tt ns sec) %time? (tt %time-type %time-type-set!) (ns %time-nanosecond %time-nanosecond-set!) (sec %time-second %time-second-set!) ) (define-record-type time (%make-time tt ns sec) %time? (tt %time-type %time-type-set!) (ns %time-nanosecond %time-nanosecond-set!) (sec %time-second %time-second-set!) ) (define time? %time?) ;; Time to Date (define ONE-SECOND-DURATION (%make-time 'duration 0 1)) ;; ;; -> (define-inline (normalize-timeval t t/t+1) (values (remainder t t/t+1) (quotient t t/t+1)) ) (define (normalize-nanoseconds ns) (normalize-timeval ns NS/S) ) ; -> ; #; ;UNUSED (define (normalize-time ns sec min hr) (let*-values (((ns ns-sec) (normalize-nanoseconds ns)) ((sec sec-min) (normalize-timeval (+ sec ns-sec) SEC/MIN)) ((min min-hr) (normalize-timeval (+ min sec-min) MIN/HR)) ((hr hr-dy) (normalize-timeval (+ hr min-hr) HR/DY)) ) (values ns sec min hr (+ dy hr-dy)) ) ) ;; (define-constant TIME-FORMAT-SRFI-10 "#,(time ~A ~A ~A)") (define-constant TIME-FORMAT-BRACKET "