(module date mzscheme
(require (lib "contract.ss")
(lib "etc.ss")
(lib "plt-match.ss")
(lib "time.ss" "srfi" "19")
(file "base.ss"))
(define (time-tai? datum)
(and (time? datum)
(eq? (time-type datum) time-tai)))
(define (time-utc? datum)
(and (time? datum)
(eq? (time-type datum) time-utc)))
(define (time-duration? datum)
(and (time? datum)
(eq? (time-type datum) time-duration)))
(define (leap-year? year)
(if (= (remainder year 4) 0)
(if (= (remainder year 100) 0)
(if (= (remainder year 400) 0)
#t
#f)
#t)
#f))
(define days-in-month
(opt-lambda (month [year 2001]) (case month
[(9 4 6 11) 30]
[(2) (if (leap-year? year) 29 28)]
[(1 3 5 7 8 9 10 12) 31]
[else (raise-exn
exn:fail:unlib
(format "Month out of range: ~a" month))])))
(define date-valid?
(let ([min-tz (* 12 60 60 -1)]
[max-tz (* 12 60 60)])
(lambda (date)
(let ([nanosecond (date-nanosecond date)]
[second (srfi:date-second date)]
[minute (srfi:date-minute date)]
[hour (srfi:date-hour date)]
[day (srfi:date-day date)]
[month (srfi:date-month date)]
[year (srfi:date-year date)]
[tz (date-zone-offset date)])
(and (>= month 1) (<= month 12)
(>= day 1) (<= day (days-in-month month year))
(>= hour 0) (< hour 24)
(>= minute 0) (< minute 60)
(>= second 0) (< second 60)
(>= nanosecond 0) (< nanosecond 1000000000)
(>= tz min-tz) (< tz max-tz))))))
(define (time-weekday? time)
(define date
(if (time-tai? time)
(time-tai->date time)
(time-utc->date time)))
(if (member (date->string date "~a") '("Mon" "Tue" "Wed" "Thu" "Fri"))
#t
#f))
(define (time->date time)
(if (time-tai? time)
(time-tai->date time)
(time-utc->date time)))
(define timestamp->ago-string
(opt-lambda (then [now (current-seconds)])
(define (make-answer number unit)
(cond [(and (= number 1) (equal? unit "day")) "yesterday"]
[(= number 1) (format "~a ~a ago" number unit)]
[else (format "~a ~as ago" number unit)]))
(let ([difference (- now then)])
(when (< difference 0)
(raise-exn exn:fail:unlib
(format "Expected first argument to be less than second), received ~a ~a." then now)))
(cond [(< difference 60) (make-answer difference "second")]
[(< difference 3600) (make-answer (floor (/ difference 60)) "minute")]
[(< difference 86400) (make-answer (floor (/ difference 3600)) "hour")]
[else (make-answer (floor (/ difference 86400)) "day")]))))
(define time->ago-string
(case-lambda
[(then)
(let ([now (if (time-tai? then)
(current-time time-tai)
(current-time time-utc))])
(timestamp->ago-string (time-second then) (time-second now)))]
[(then now)
(if (eq? (time-type then) (time-type now))
(timestamp->ago-string (time-second then) (time-second now))
(raise-exn exn:fail:contract
(format "Arguments have different time types: ~a ~a" then now)))]))
(define (current-time-zone-offset)
(date-zone-offset (time-tai->date (current-time time-tai))))
(define (current-year)
(srfi:date-year (time-tai->date (current-time time-tai))))
(define month/c
(and/c integer? (between/c 1 12)))
(provide time-tai?
time-utc?
time-duration?)
(provide/contract
[leap-year? (-> integer? boolean?)]
[days-in-month (opt-> (month/c) (integer?) integer?)]
[date-valid? (-> srfi:date? boolean?)]
[time-weekday? (-> (or/c time-tai? time-utc?) boolean?)]
[time->date (-> (or/c time-tai? time-utc?) srfi:date?)]
[timestamp->ago-string (opt-> (integer?) (integer?) string?)]
[time->ago-string (opt-> ((or/c time-tai? time-utc?))
((or/c time-tai? time-utc?))
string?)]
[current-time-zone-offset (-> integer?)]
[current-year (-> integer?)])
)