#lang scheme/base
(require 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 (copy-date date
#:nanosecond [nanosecond #f]
#:second [second #f]
#:minute [minute #f]
#:hour [hour #f]
#:day [day #f]
#:month [month #f]
#:year [year #f]
#:zone-offset [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 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 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/contract
[copy-date (->* (date?)
(#:nanosecond (or/c (integer-in 0 999999999) false/c)
#:second (or/c (integer-in 0 59) false/c)
#:minute (or/c (integer-in 0 59) false/c)
#:hour (or/c (integer-in 0 23) false/c)
#:day (or/c (integer-in 1 31) false/c)
#:month (or/c (integer-in 1 12) false/c)
#:year (or/c integer? false/c)
#:zone-offset (or/c (integer-in min-zone-offset max-zone-offset) false/c))
date?)]
[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?)])