;;;; srfi-19-date.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. (module srfi-19-date (;export ; SRFI-19 current-date current-julian-day current-modified-julian-day make-date date-nanosecond date-second date-minute date-hour date-day date-month date-year date-zone-offset leap-year? ; Actually part of SRFI 19 but not in original document date-year-day days-in-month/year natural-year date-week-day date-week-number date->julian-day date->modified-julian-day date->time-monotonic date->time-tai date->time-utc julian-day->date julian-day->time-monotonic julian-day->time-tai julian-day->time-utc modified-julian-day->date modified-julian-day->time-monotonic modified-julian-day->time-tai modified-julian-day->time-utc time-monotonic->date time-monotonic->julian-day time-monotonic->modified-julian-day time-tai->date time-tai->julian-day time-tai->modified-julian-day time-utc->date time-utc->julian-day time-utc->modified-julian-day ; Extensions seconds->date read-leap-second-table time->date default-date-clock-type default-date-adjust-integer date-zone-name date-dst? copy-date date->time date-adjust date-difference date-add-duration date-subtract-duration date=? date>? date=? date<=? date-max date-min time->julian-day time->modified-julian-day date-compare ; DEPRECATED seconds->date/type) (import (except scheme zero? negative? positive? real?) chicken (only srfi-1 fold list-index) srfi-69 #;srfi-8 (only numbers zero? negative? positive? real?) miscmacros (only locale-components check-timezone-components timezone-components?) type-checks type-errors srfi-19-timezone srfi-19-support) (require-library #;srfi-8 numbers miscmacros locale-components type-checks type-errors srfi-19-timezone srfi-19-support) ;;; (include "srfi-19-common") ;; (define (checked-tm:time->date loc tim tzi) (or (tm:time->date tim tzi) (error-convert loc 'time 'date tim)) ) ;; (define (checked-tm:date->time loc dat tt) (check-clock-type loc tt) (or (tm:date->time dat tt) (error-convert loc 'date 'time dat)) ) ;; (define (read-leap-second-table flnm) (check-string 'read-leap-second-table flnm) ;FIXME should be check-pathname (tm:read-leap-second-table flnm) ) ;;; Date Object (Public Immutable) ;; (define-syntax date-adjuster-create (er-macro-transformer (lambda (frm r cmp) (let ((_date-adjuster-set! (r 'date-adjuster-set!)) (_begin (r 'begin)) ) `(,_begin ,@(let loop ((args (cdr frm)) (ls '())) (if (null? args) ls (let ((?key (car args)) (?syns (cadr args)) (?hdlr (caddr args)) ) (loop (cdddr args) (cons `(,_date-adjuster-set! ',?key ',?syns ,?hdlr) ls) ) ) ) ) ) ) ) ) ) ;; ;FIXME should this be thread-specific? (define-parameter default-date-clock-type 'utc (lambda (obj) (cond ((clock-type? obj) obj) (else (warning-argument-type 'default-date-clock-type obj 'clock-type) (default-date-clock-type) ) ) ) ) (define-parameter default-date-adjust-integer tm:default-date-adjust-integer (lambda (obj) (cond ((procedure? obj) obj) (else (warning-argument-type 'default-date-adjust-integer obj 'procedure) (default-date-adjust-integer) ) ) ) ) ;; Date CTOR (define make-date-unique (cons #t #f)) (define (make-date ns sec min hr dy mn yr . args) (let-optionals args ((tzo (timezone-locale-offset)) (tzn #f) (dstf make-date-unique)) (let ((no-dstf (eq? make-date-unique dstf))) (cond ((timezone-components? tzo) ; Supplied parameters override (set! dstf (if no-dstf (timezone-locale-dst? tzo) dstf)) (set! tzn (or tzn (timezone-locale-name tzo))) (set! tzo (timezone-locale-offset tzo)) ) (else (when no-dstf (set! dstf #f)) ) ) ) (check-date-elements 'make-date ns sec min hr dy mn yr tzo tzn) (tm:make-date ns sec min hr dy mn yr tzo tzn dstf #f #f #f) ) ) (define (copy-date dat) (check-date 'copy-date dat) (tm:copy-date dat) ) ;; Converts a seconds value, may be fractional, into a date type. ;; The seconds value is number of seconds since 00:00:00 January 1, 1970. ;; A local (#t), utc (#f), or other (timezone-components) date depending on ;; the optional 2nd argument. The default is #t. (define (seconds->date sec . tzi) (check-raw-seconds 'seconds->date sec) (let ((tzc (checked-optional-timezone-info 'seconds->date (optional tzi #t)))) (check-timezone-components 'seconds->date tzc) (tm:seconds->date/type sec tzc) ) ) (define seconds->date/type seconds->date) ; DEPRECATED (define (current-date . tzi) (tm:current-date (checked-optional-timezone-info 'current-date (optional tzi #t))) ) ;; (define (date-nanosecond dat) (check-date 'date-nanosecond dat) (tm:date-nanosecond dat) ) (define (date-second dat) (check-date 'date-second dat) (tm:date-second dat) ) (define (date-minute dat) (check-date 'date-minute dat) (tm:date-minute dat) ) (define (date-hour dat) (check-date 'date-hour dat) (tm:date-hour dat) ) (define (date-day dat) (check-date 'date-day dat) (tm:date-day dat) ) (define (date-month dat) (check-date 'date-month dat) (tm:date-month dat) ) (define (date-year dat) (check-date 'date-year dat) (tm:date-year dat) ) (define (date-dst? dat) (check-date 'date-dst? dat) (tm:date-dst? dat) ) (define (date-zone-offset dat) (check-date 'date-zone-offset dat) (tm:date-zone-offset dat) ) (define (date-zone-name dat) (check-date 'date-zone-name dat) (tm:date-zone-name dat) ) ;; Date Comparison (define (checked-date-compare loc dat1 dat2) (check-date loc dat1) (check-date loc dat2) (check-date-compatible-timezone-offsets loc dat1 dat2) (tm:date-compare dat1 dat2) ) ;; (define (date-compare dat1 dat2) (let ((dif (checked-date-compare 'date-compare dat1 dat2))) (cond ((fx> 0 dif) -1) ((fx< 0 dif) 1) (else 0) ) ) ) (define (date=? dat1 dat2) (fx= 0 (checked-date-compare 'date=? dat1 dat2)) ) (define (date 0 (checked-date-compare 'date= 0 (checked-date-compare 'date<=? dat1 dat2)) ) (define (date>? dat1 dat2) (fx< 0 (checked-date-compare 'date>? dat1 dat2)) ) (define (date>=? dat1 dat2) (fx<= 0 (checked-date-compare 'date>=? dat1 dat2)) ) (define (date-max dat1 . rest) (fold (lambda (dat acc) (check-date 'date-max dat) (check-date-compatible-timezone-offsets 'date-max acc dat) (if (fx> 0 (tm:date-compare acc dat)) dat acc) ) (check-date 'date-max dat1) rest) ) (define (date-min dat1 . rest) (fold (lambda (dat acc) (check-date 'date-max dat) (check-date-compatible-timezone-offsets 'date-min acc dat) (if (fx< 0 (tm:date-compare acc dat)) dat acc) ) (check-date 'date-min dat1) rest) ) ;; Date Arithmetic (define (date-adjust dat amt key . args) (check-date 'date-adjust dat) (check-integer 'date-adjust amt) (let-optionals args ((tt (default-date-clock-type)) ) ((date-adjuster-ref 'date-adjust key) dat ((default-date-adjust-integer) amt) key ;only used for duration conversion tt) ) ) (define (date-difference dat1 dat2 . args) (check-date 'date-difference dat1) (check-date 'date-difference dat2) (let-optionals args ((tt (default-date-clock-type))) (let ((tim1 (checked-tm:date->time 'date-difference dat1 tt)) (tim2 (checked-tm:date->time 'date-difference dat2 tt)) ) (tm:time-difference tim1 tim2 (tm:some-time 'duration)) ) ) ) (define (date-add-duration dat dur . args) (check-date 'date-add-duration dat) (check-duration 'date-add-duration dur) (let-optionals args ((tt (default-date-clock-type))) (let ((tim (checked-tm:date->time 'date-add-duration dat tt)) ) (checked-tm:time->date 'date-add-duration (tm:add-duration tim dur (tm:as-some-time tim)) (tm:date-timezone-info dat)) ) ) ) (define (date-subtract-duration dat dur . args) (check-date 'date-subtract-duration dat) (check-duration 'date-subtract-duration dur) (let-optionals args ((tt (default-date-clock-type))) (let ((tim (checked-tm:date->time 'date-subtract-duration dat tt)) ) (checked-tm:time->date 'date-subtract-duration (tm:subtract-duration tim dur (tm:as-some-time tim)) (tm:date-timezone-info dat)) ) ) ) ;; Date Adjust Handlers (define (date-adjuster-years dat amt key tt) (let ((yr (fx+ (tm:date-year dat) amt)) (ndat (tm:copy-date dat)) ) (tm:date-year-set! ndat yr) (when (and (tm:leap-day? (tm:date-day dat) (tm:date-month dat)) (not (tm:leap-year? yr))) (tm:date-day-set! ndat (tm:days-in-month (tm:date-month dat) yr))) ndat ) ) (define (date-adjuster-quarters dat amt key tt) (date-adjuster-months dat (fx* 3 amt) 'months tt) ) (define (date-adjuster-months dat amt key tt) (if (zero? amt) (tm:copy-date dat) (let ((ndat (copy-date dat)) (yrs (quotient amt 12)) (mns (remainder amt 12)) ) (cond ((positive? mns) (when (fx< 12 (fx+ (tm:date-month dat) mns)) (tm:date-month-set! ndat 1) (set! mns (fx- mns (fx- 12 (tm:date-month dat)))) (set! yrs (fx+ 1 yrs)) ) ) (else ;(negative? amt) (when (fx> 1 (fx+ (tm:date-month dat) mns)) (tm:date-month-set! ndat 12) (set! mns (fx+ mns (tm:date-month dat))) (set! yrs (fx- yrs 1)) ) ) ) (tm:date-month-set! ndat (fx+ mns (tm:date-month ndat))) (tm:date-year-set! ndat (fx+ yrs (tm:date-year ndat))) (when (fx< (tm:days-in-month (tm:date-year ndat) (tm:date-month ndat)) (tm:date-day ndat)) (tm:date-day-set! ndat (tm:days-in-month (tm:date-year ndat) (tm:date-month ndat))) ) ndat ) ) ) (define (date-adjuster-weeks dat amt key tt) (date-adjuster-duration dat (fx* amt 7) 'days tt) ) (define (date-adjuster-duration dat amt key tt) (let ((tim (checked-tm:date->time 'date-adjust-duration dat tt)) (dur (make-duration (string->keyword (symbol->string key)) amt)) ) (checked-tm:time->date 'date-adjust-duration (tm:add-duration tim dur (tm:as-some-time tim)) (tm:date-timezone-info dat)) ) ) ;FIXME dup code ;From srfi-19-time (define (make-duration #!key (days 0) (hours 0) (minutes 0) (seconds 0) (milliseconds 0) (microseconds 0) (nanoseconds 0)) (receive (ns sec) (tm:duration-elements->time-values days hours minutes seconds milliseconds microseconds nanoseconds) (tm:make-time 'duration ns sec) ) ) ;; Date Adjust Support (define (date-adjust-key? obj) (hash-table-exists? +date-adjust-synonym-map+ obj) ) (define (date-adjuster-ref loc key) (let ((key (hash-table-ref/default +date-adjust-synonym-map+ key 'UNKNOWN))) (hash-table-ref/default +date-adjuster-map+ key (unknown-date-key-handler loc)) ) ) (define (date-adjuster-set! key syns hdlr) ;all are key (hash-table-set! +date-adjust-synonym-map+ key key) (for-each (lambda (syn) (hash-table-set! +date-adjust-synonym-map+ syn key) ) syns) ;adjuster for key (hash-table-set! +date-adjuster-map+ key hdlr) ) (define +date-adjust-synonym-map+ (make-hash-table eq? symbol-hash)) (define +date-adjuster-map+ (make-hash-table eq? symbol-hash)) (define date-key? date-adjust-key?) (define ((unknown-date-key-handler loc) dat amt key tt) (error loc "unknown date-key" key) ) ;; Time to Date (define (time-tai->date tim . tzi) (check-time-and-type 'time-tai->date tim 'tai) (tm:time-tai->date tim (checked-optional-timezone-info 'time-tai->date (optional tzi #t))) ) (define (time-utc->date tim . tzi) (check-time-and-type 'time-utc->date tim 'utc) (tm:time-utc->date tim (checked-optional-timezone-info 'time-utc->date (optional tzi #t))) ) (define (time-monotonic->date tim . tzi) (check-time-and-type 'time-monotonic->date tim 'monotonic) (tm:time-utc->date tim (checked-optional-timezone-info 'time-monotonic->date (optional tzi #t))) ) (define (time->date tim . tzi) (check-time 'time->date tim) (checked-tm:time->date 'time->date tim (checked-optional-timezone-info 'time->date (optional tzi #t))) ) ;; Date to Time (define (date->time-utc dat) (check-date 'date->time-utc dat) (tm:date->time-utc dat) ) (define (date->time-tai dat) (check-date 'date->time-tai dat) (tm:date->time-tai dat) ) (define (date->time-monotonic dat) (check-date 'date->time-monotonic dat) (tm:date->time-monotonic dat) ) (define (date->time dat . args) (check-date 'date->time dat) (let-optionals args ((tt (default-date-clock-type))) (checked-tm:date->time 'date->time dat tt) ) ) ;; Given a 'two digit' number, find the year within 50 years +/- (define (natural-year n . tzi) (check-date-year 'natural-year n) (tm:natural-year n (checked-optional-timezone-info 'natural-year (optional tzi #t))) ) ;; Leap Year (define (leap-year? dat) (tm:leap-year? ;assume a number is a year, otherwise extract (if (fixnum? dat) dat (tm:date-year (check-date 'date-leap-year? dat)))) ) ;; Day of Year (define (date-year-day dat) (check-date 'date-year-day dat) (tm:date-year-day dat) ) (define (days-in-month/year mn yr) (check-date-year 'days-in-month/year yr) (check-date-month 'days-in-month/year mn) (tm:days-in-month yr mn) ) ;; Week Day (define (date-week-day dat) (check-date 'date-week-day dat) (tm:date-week-day dat) ) ;; (define (date-week-number dat . args) (check-date 'date-week-number dat) (let-optionals args ((1st-weekday 0)) (check-week-day 'date-week-number 1st-weekday) (tm:date-week-number dat 1st-weekday) ) ) ;; Julian-day Operations (define (date->julian-day dat) (check-date 'date->julian-day dat) (tm:date->julian-day dat) ) (define (date->modified-julian-day dat) (check-date 'date->modified-julian-day dat) (tm:julian-day->modified-julian-day (tm:date->julian-day dat)) ) ;; Time to Julian-day (define (time-utc->julian-day tim) (check-time-and-type 'time-utc->julian-day tim 'utc) (tm:time-utc->julian-day tim) ) (define (time-tai->julian-day tim) (check-time-and-type 'time-tai->julian-day tim 'tai) (tm:time-tai->julian-day tim) ) (define (time-monotonic->julian-day tim) (check-time-and-type 'time-monotonic->julian-day tim 'monotonic) (tm:time-monotonic->julian-day tim) ) (define (time->julian-day tim) (check-time 'time->julian-day tim) (or (tm:time->julian-day tim) (error-convert 'time->julian-day 'time 'julian-day tim) ) ) (define (time-utc->modified-julian-day tim) (check-time-and-type 'time-utc->modified-julian-day tim 'utc) (tm:time-utc->modified-julian-day tim) ) (define (time-tai->modified-julian-day tim) (check-time-and-type 'time-tai->modified-julian-day tim 'tai) (tm:time-tai->modified-julian-day tim) ) (define (time-monotonic->modified-julian-day tim) (check-time-and-type 'time-monotonic->modified-julian-day tim 'monotonic) (tm:time-monotonic->modified-julian-day tim) ) (define (time->modified-julian-day tim) (check-time 'time->modified-julian-day tim) (or (tm:time->modified-julian-day tim) (error-convert 'time->modified-julian-day 'time 'modified-julian-day tim) ) ) ;; Julian-day to Time (define (julian-day->time-utc jdn) (check-julian-day 'julian-day->time-utc jdn) (tm:julian-day->time-utc jdn) ) (define (julian-day->time-tai jdn) (check-julian-day 'julian-day->time-tai jdn) (let ((tim (tm:julian-day->time-utc jdn))) (tm:time-utc->time-tai tim tim) ) ) (define (julian-day->time-monotonic jdn) (check-julian-day 'julian-day->time-monotonic jdn) (let ((tim (julian-day->time-utc jdn))) (tm:time-utc->time-monotonic tim tim) ) ) (define (julian-day->date jdn . tzi) (check-julian-day 'julian-day->date jdn) (tm:time-utc->date (tm:julian-day->time-utc jdn) (checked-optional-timezone-info 'julian-day->date (optional tzi #t))) ) (define (modified-julian-day->time-utc mjdn) (check-julian-day 'modified-julian-day->time-utc mjdn) (tm:julian-day->time-utc (tm:modified-julian-day->julian-day mjdn)) ) (define (modified-julian-day->time-tai mjdn) (check-julian-day 'modified-julian-day->time-tai mjdn) (let ((tim (tm:julian-day->time-utc (tm:modified-julian-day->julian-day mjdn)))) (tm:time-utc->time-tai tim tim) ) ) (define (modified-julian-day->time-monotonic mjdn) (check-julian-day 'modified-julian-day->time-monotonic mjdn) (let ((tim (tm:julian-day->time-utc (tm:modified-julian-day->julian-day mjdn)))) (tm:time-utc->time-monotonic tim tim) ) ) (define (modified-julian-day->date mjdn . tzi) (check-julian-day 'modified-julian-day->date mjdn) (tm:time-utc->date (tm:julian-day->time-utc (tm:modified-julian-day->julian-day mjdn)) (checked-optional-timezone-info 'modified-julian-day->date (optional tzi #t))) ) ;; The Julian-day (define (current-julian-day) (tm:time-utc->julian-day (tm:current-time-utc)) ) (define (current-modified-julian-day) (tm:time-utc->modified-julian-day (tm:current-time-utc)) ) ;;; Module Begin (date-adjuster-create years (year yrs yr y) date-adjuster-years quarters (quarter qtrs qtr Q) date-adjuster-quarters months (month mons mon mns mn M) date-adjuster-months weeks (week wks wk w) date-adjuster-weeks days (day dys dy d) date-adjuster-duration hours (hour hrs hr h) date-adjuster-duration minutes (minute mins min m) date-adjuster-duration seconds (second secs sec s) date-adjuster-duration milliseconds (millisecond millis milli ms) date-adjuster-duration microseconds (microsecond micros micro us) date-adjuster-duration nanoseconds (nanosecond nanos nano ns) date-adjuster-duration ) (define +date-key-lexographic-order+ '( years quarters months weeks days hours minutes seconds milliseconds microseconds nanoseconds )) (define (date-key= a b) (eq? a b) ) (define (date-key< a b) (< 0 (date-key-compare a b)) ) (define (date-key-compare a b) (- (list-index (cut eq? a <>) +date-key-lexographic-order+) (list-index (cut eq? b <>) +date-key-lexographic-order+)) ) ) ;module srfi-19-date