#lang mzscheme
(require mzlib/etc
mzlib/kw
scheme/contract
scheme/match
srfi/19
(file "base.ss"))
(define min-zone-offset
(* 12 60 60 -1))
(define max-zone-offset
(* 12 60 60))
(define/kw (copy-date date #:key
[nanosecond #f]
[second #f]
[minute #f]
[hour #f]
[day #f]
[month #f]
[year #f]
[zone-offset #f])
(make-date (or nanosecond (date-nanosecond date))
(or second (date-second date))
(or minute (date-minute date))
(or hour (date-hour date))
(or day (date-day date))
(or month (date-month date))
(or year (date-year date))
(or zone-offset (date-zone-offset date))))
(define (time->date time)
(if (time-tai? time)
(time-tai->date time)
(time-utc->date time)))
(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 (date-valid? date)
(let ([nanosecond (date-nanosecond date)]
[second (date-second date)]
[minute (date-minute date)]
[hour (date-hour date)]
[day (date-day date)]
[month (date-month date)]
[year (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-zone-offset) (< tz max-zone-offset))))
(define (date-day-of-the-week date)
(string->symbol (string-downcase (date->string date "~a"))))
(define (date-week-day? date)
(and (memq (date-day-of-the-week date) '(mon tue wed thu fri)) #t))
(define (leap-year? year)
(if (zero? (remainder year 4))
(if (zero? (remainder year 100))
(if (zero? (remainder year 400))
#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 seconds->ago-string
(opt-lambda (then [now (current-seconds)])
(define (make-answer number unit)
(if (= number 1)
(if (equal? unit "day")
"yesterday"
(format "~a ~a ago" number unit))
(format "~a ~as ago" number unit)))
(define 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))])
(seconds->ago-string (time-second then) (time-second now)))]
[(then now)
(if (eq? (time-type then) (time-type now))
(seconds->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)
(date-year (time-tai->date (current-time time-tai))))
(define time/c
(or/c time-tai? time-utc?))
(define month/c
(flat-named-contract
"month/c"
(lambda (x)
(and (integer? x)
(>= x 1)
(<= x 12)))))
(define day-of-the-week/c
(flat-named-contract
"day-of-the-week/c"
(lambda (x)
(and (memq x '(mon tue wed thu fri sat sun)) #t))))
(provide copy-date)
(provide/contract
[time->date (-> time/c date?)]
[time-tai? procedure?]
[time-utc? procedure?]
[time-duration? procedure?]
[date-valid? (-> date? boolean?)]
[date-day-of-the-week (-> date? day-of-the-week/c)]
[date-week-day? (-> date? boolean?)]
[leap-year? (-> integer? boolean?)]
[days-in-month (->* (month/c) (integer?) integer?)]
[seconds->ago-string (->* (integer?) (integer?) string?)]
[time->ago-string (->* (time/c) (time/c) string?)]
[current-time-zone-offset (-> integer?)]
[current-year (-> integer?)])