;;;; srfi-19-date.scm ;;;; Chicken port, Kon Lovett, Dec '05 ;;Issues ;; ;; - use of check-* im or/and forms is problematic ;; 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? ;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 date-record-printer-format seconds->date read-leap-second-table time->date default-date-clock-type default-date-adjust-integer date-zone-name date-dst? copy-date date->seconds 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?)) (import chicken) (use (only numbers zero? negative? positive? real?)) (use (only srfi-1 fold list-index) (only srfi-69 make-hash-table symbol-hash hash-table-exists? hash-table-ref/default hash-table-set!) #;srfi-8 (only locale-components check-timezone-components timezone-components?) miscmacros 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) (or (tm:date->time dat (check-clock-type loc tt)) (error-convert loc 'date 'time dat)) ) ;; (define (read-leap-second-table flnm) ;FIXME should be check-pathname (tm:read-leap-second-table (check-string '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) (if (clock-type? obj) obj (begin (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) (if (procedure? obj) obj (begin (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) (let ((tzc (checked-optional-timezone-info 'seconds->date (optional tzi #t)))) (check-timezone-components 'seconds->date tzc) (tm:seconds->date/type (check-raw-seconds 'seconds->date sec) tzc) ) ) (define (date->seconds dat #!optional (tt (default-date-clock-type))) (let* ( (dat (check-date 'date->seconds dat)) (tim (case (check-clock-type 'date->seconds tt) ((utc) (tm:date->time-utc dat)) ((tai) (tm:date->time-tai dat)) ((monotonic) (tm:date->time-monotonic dat)) ) ) ) (tm:time-second tim) ) ) (define (current-date . tzi) (tm:current-date (checked-optional-timezone-info 'current-date (optional tzi #t))) ) ;; (define (date-nanosecond dat) (tm:date-nanosecond (check-date 'date-nanosecond dat)) ) (define (date-second dat) (tm:date-second (check-date 'date-second dat)) ) (define (date-minute dat) (tm:date-minute (check-date 'date-minute dat)) ) (define (date-hour dat) (tm:date-hour (check-date 'date-hour dat)) ) (define (date-day dat) (tm:date-day (check-date 'date-day dat)) ) (define (date-month dat) (tm:date-month (check-date 'date-month dat)) ) (define (date-year dat) (tm:date-year (check-date 'date-year dat)) ) (define (date-dst? dat) (tm:date-dst? (check-date 'date-dst? dat)) ) (define (date-zone-offset dat) (tm:date-zone-offset (check-date 'date-zone-offset dat)) ) (define (date-zone-name dat) (tm:date-zone-name (check-date 'date-zone-name dat)) ) ;; Date Comparison (define (checked-date-compare loc dat1 dat2) (check-date-compatible-timezone-offsets loc (check-date loc dat1) (check-date loc 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-compatible-timezone-offsets 'date-max acc (check-date 'date-max 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-compatible-timezone-offsets 'date-min acc (check-date 'date-max 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) (let-optionals args ((tt (default-date-clock-type))) (let-values (((key adjuster) (date-adjuster-ref 'date-adjust key))) (adjuster (check-date 'date-adjust dat) ((default-date-adjust-integer) (check-integer 'date-adjust amt)) key ;only used for duration conversion tt) ) ) ) (define (date-difference dat1 dat2 . args) (let-optionals args ((tt (default-date-clock-type))) (let ((tim1 (checked-tm:date->time 'date-difference (check-date 'date-difference dat1) tt)) (tim2 (checked-tm:date->time 'date-difference (check-date 'date-difference dat2) tt)) ) (tm:time-difference tim1 tim2 (tm:some-time 'duration)) ) ) ) (define (date-add-duration dat dur . args) (check-duration 'date-add-duration dur) (let-optionals args ((tt (default-date-clock-type))) (let ((tim (checked-tm:date->time 'date-add-duration (check-date '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-duration 'date-subtract-duration dur) (let-optionals args ((tt (default-date-clock-type))) (let ((tim (checked-tm:date->time 'date-subtract-duration (check-date '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-synonym-map+ (make-hash-table eq? symbol-hash)) (define +date-adjuster-map+ (make-hash-table eq? symbol-hash)) (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))) (values key (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 (cut hash-table-set! +date-adjust-synonym-map+ <> key) syns) ;adjuster for key (hash-table-set! +date-adjuster-map+ key hdlr) ) (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) (checked-tm:time->date 'time->date (check-time 'time->date tim) (checked-optional-timezone-info 'time->date (optional tzi #t))) ) ;; Date to Time (define (date->time-utc dat) (tm:date->time-utc (check-date 'date->time-utc dat)) ) (define (date->time-tai dat) (tm:date->time-tai (check-date 'date->time-tai dat)) ) (define (date->time-monotonic dat) (tm:date->time-monotonic (check-date 'date->time-monotonic dat)) ) (define (date->time dat . args) (let-optionals args ((tt (default-date-clock-type))) (checked-tm:date->time 'date->time (check-date 'date->time dat) tt) ) ) ;; Given a 'two digit' number, find the year within 50 years +/- (define (natural-year n . tzi) (tm:natural-year (check-date-year '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) (tm:date-year-day (check-date 'date-year-day dat)) ) (define (days-in-month/year mn yr) (tm:days-in-month (check-date-year 'days-in-month/year yr) (check-date-month 'days-in-month/year mn)) ) ;; Week Day (define (date-week-day dat) (tm:date-week-day (check-date 'date-week-day dat)) ) ;; (define (date-week-number dat . args) (let-optionals args ((1st-weekday 0)) (tm:date-week-number (check-date 'date-week-number dat) (check-week-day 'date-week-number 1st-weekday)) ) ) ;; Julian-day Operations (define (date->julian-day dat) (tm:date->julian-day (check-date 'date->julian-day dat)) ) (define (date->modified-julian-day dat) (tm:julian-day->modified-julian-day (tm:date->julian-day (check-date 'date->modified-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) (or (tm:time->julian-day (check-time '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) (or (tm:time->modified-julian-day (check-time '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) (tm:julian-day->time-utc (check-julian-day 'julian-day->time-utc jdn)) ) (define (julian-day->time-tai jdn) (let ((tim (tm:julian-day->time-utc (check-julian-day 'julian-day->time-tai jdn)))) (tm:time-utc->time-tai tim tim) ) ) (define (julian-day->time-monotonic jdn) (let ((tim (julian-day->time-utc (check-julian-day 'julian-day->time-monotonic jdn)))) (tm:time-utc->time-monotonic tim tim) ) ) (define (julian-day->date jdn . tzi) (tm:time-utc->date (tm:julian-day->time-utc (check-julian-day 'julian-day->date jdn)) (checked-optional-timezone-info 'julian-day->date (optional tzi #t))) ) (define (modified-julian-day->time-utc mjdn) (tm:modified-julian-day->time-utc (check-julian-day 'modified-julian-day->time-utc mjdn)) ) (define (modified-julian-day->time-tai mjdn) (let ( (tim (tm:modified-julian-day->time-utc (check-julian-day 'modified-julian-day->time-tai mjdn))) ) (tm:time-utc->time-tai tim tim) ) ) (define (modified-julian-day->time-monotonic mjdn) (let ( (tim (tm:modified-julian-day->time-utc (check-julian-day 'modified-julian-day->time-monotonic mjdn))) ) (tm:time-utc->time-monotonic tim tim) ) ) (define (modified-julian-day->date mjdn . tzi) (tm:time-utc->date (tm:modified-julian-day->time-utc (check-julian-day 'modified-julian-day->date 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) (fx< 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+)) ) ;DEPRECATED (: seconds->date/type deprecated) (define seconds->date/type seconds->date) ) ;module srfi-19-date