;;
;;
;; A parser for the grammar defined in RFC 3339, "Date and Time on
;; the Internet: Timestamps".
;;
;; Copyright 2009-2011 Ivan Raikov and the Okinawa Institute of
;; Science and Technology.
;;
;;
;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; A full copy of the GPL license can be found at
;; .
(module internet-timestamp
(parser ts? ts-date ts-time ts-offset ts->list
CoreABNF->InetTimestamp )
(import scheme chicken data-structures extras srfi-1 )
(import (only srfi-13 string-trim string-concatenate))
(require-extension typeclass)
(require-library abnf abnf-consumers)
(import (prefix abnf abnf:)
(prefix abnf-consumers abnf:)
(only abnf )
)
(define-class ( A)
date-time)
;; construct numbers from consumed chars
(define consumed-chars->number
(abnf:consumed-chars->list
(compose string->number list->string)))
;; shortcut for (bind consumed-chars->number (longest ... ))
(define-syntax bind-consumed->number
(syntax-rules ()
((_ p) (abnf:bind consumed-chars->number p))
))
(define consumed-numbers
(abnf:consumed-objects number?))
(define consumed-numbers->list
(abnf:consumed-objects-lift
consumed-numbers))
(define consumed-objects-lift-any
(abnf:consumed-objects-lift
(abnf:consumed-objects identity)))
;; timestamp datatype
(define-record-type ts (make-ts date time offset)
ts? (date ts-date )
(time ts-time )
(offset ts-offset )
)
;; convert a number to a string with leading zeros
(define (number->lzstring n)
(lambda (x)
(or (and (number? x)
(let loop ((d (string->list (number->string x))))
(if (>= (length d) n) (list->string d)
(loop (cons #\0 d)))))
x)))
(define-record-printer (ts x out)
(let ((number->lzstring2 (number->lzstring 2))
(number->lzstring4 (number->lzstring 4)))
(fprintf out "~AT~A~A"
(let ((date-list (vector->list (ts-date x))))
(apply sprintf (cons* "~A-~A-~A"
(number->lzstring4 (car date-list))
(map number->lzstring2 (cdr date-list)))))
(string-concatenate
(cons
(apply sprintf
(cons "~A:~A:~A"
(map number->lzstring2 (vector->list (ts-time x)))))
(cond ((vector-ref (ts-time x) 3) =>
(lambda (frac)
(list (string-trim (number->string frac) #\0))))
(else (list)))))
(case (vector-ref (ts-offset x) 0)
((Z) "Z")
(else (apply sprintf
(cons "~A~A:~A"
(map number->lzstring2 (vector->list (ts-offset x)))))))
)))
(define (ts->list x)
(and (ts? x)
(let ((date (ts-date x))
(time (ts-time x))
(offset (ts-offset x)))
`((date ,(vector->list date))
(time ,(filter identity (vector->list time)))
(offset ,(filter identity (vector->list offset)))))))
(define (make-date year month day)
(make-ts (vector year month day) #f #f))
(define (make-partial-time hr min sec . rest)
(let-optionals rest ((frac #f))
(make-ts #f (vector hr min sec frac)
#f)))
(define (make-offset sign . rest)
(let-optionals rest ((hr #f) (min #f))
(make-ts #f #f (vector sign hr min))))
(define (make-full-time time offset)
(let ((time (ts-time time))
(offset (ts-offset offset)))
(make-ts #f time offset)))
(define (make-date-time date time )
(let ((time (ts-time time))
(offset (ts-offset time))
(date (ts-date date)))
(make-ts date time offset)))
(define=> (number-n )
(lambda (n)
(bind-consumed->number
(abnf:repetition-n n decimal))))
(define=> (time-secfrac )
(bind-consumed->number
(abnf:concatenation
(char #\.)
(abnf:repetition1 decimal))))
(define=> (time-numoffset )
(lambda (time-hour time-minute)
(abnf:bind
(consumed-objects-lift-any
(lambda (x) (make-offset (first x) (second x) (third x))))
(abnf:concatenation
(abnf:bind-consumed->symbol
(set-from-string "+-"))
time-hour (abnf:drop-consumed (char #\:))
time-minute))))
(define=> (time-offset )
(lambda (time-numoffset)
(abnf:alternatives
(abnf:bind (lambda (x) (list (make-offset 'Z))) (char #\Z))
time-numoffset)))
(define=> (partial-time )
(lambda (time-hour time-minute time-second time-secfrac)
(abnf:bind
(consumed-numbers->list (lambda (x) (apply make-partial-time x)))
(abnf:concatenation
time-hour (abnf:drop-consumed (char #\:) )
time-minute (abnf:drop-consumed (char #\:) )
time-second
(abnf:optional-sequence time-secfrac)))))
(define=> (full-time )
(lambda (partial-time time-offset)
(abnf:bind
(consumed-objects-lift-any
(lambda (x) (make-full-time (first x) (second x))))
(abnf:concatenation
partial-time
time-offset))))
(define=> (full-date )
(lambda (date-fullyear date-month date-mday)
(abnf:bind
(consumed-numbers->list (lambda (x) (apply make-date x)))
(abnf:concatenation
date-fullyear
(abnf:drop-consumed (char #\-)) date-month
(abnf:drop-consumed (char #\-)) date-mday))))
(define=> (date-time )
(lambda (full-date full-time)
(abnf:bind
(consumed-objects-lift-any
(lambda (x) (make-date-time (first x) (second x) )))
(abnf:concatenation
full-date (abnf:drop-consumed (char #\T))
full-time))))
(define (CoreABNF->InetTimestamp A)
(let* ((number-n (number-n A))
(date-fullyear (number-n 4))
(date-month (number-n 2)) ; 01-12
(date-mday (number-n 2)) ; 01-28, 01-29, 01-30, 01-31 based on month/year
(time-hour (number-n 2)) ; 00-23
(time-minute (number-n 2)) ; 00-59
(time-second (number-n 2)) ; 00-58, 00-59, 00-60 based on leap second
; rules
(time-secfrac (time-secfrac A))
(time-numoffset ((time-numoffset A) time-hour time-minute))
(time-offset ((time-offset A) time-numoffset))
(partial-time ((partial-time A) time-hour time-minute time-second time-secfrac))
(full-time ((full-time A) partial-time time-offset))
(full-date ((full-date A) date-fullyear date-month date-mday))
(date-time ((date-time A) full-date full-time))
)
(make- A date-time)
))
(define (->char-list s)
(if (string? s) (string->list s) s))
(define (err s)
(print "time stamp parser error on stream: " s)
(list))
(define=> (parser )
(let ((p date-time))
(lambda (s)
(date-time caar err `(() ,(->char-list s))))))
)