iso-8601.ss
(module iso-8601 mzscheme
  
  ; This module contains the beginnings of a generic ISO 8601 date/time parser.
  ; The eventual aim is to have a procedure that turns any ISO 8601 string into
  ; some uniform data structure.
  
  ; This module:
  ;   - WILL support dates, times and date/time combinations;
  ;   - WILL support timezones;
  ;   - WILL support up to 9 optional fractional digits on seconds;
  ;   - WILL NOT support hyphen-prefixed abbreviations;
  ;   - WILL NOT support periods of time.
  
  (require (lib "pregexp.ss"))
  
  ; Calendar date formats:
  
  (define px:ccyy-mm-dd #px"^(\\d{4})-(\\d{2})-(\\d{2})$") ; 4 2 2
  (define px:ccyymmdd   #px"^(\\d{4})(\\d{2})(\\d{2})$")   ; 8
  (define px:ccyy-mm    #px"^(\\d{4})-(\\d{2})$")          ; 4 2
  (define px:ccyy       #px"^(\\d{4})$")                   ; 4
  (define px:cc         #px"^(\\d{2})$")                   ; 2
  
  (define px:yy-mm-dd   #px"^(\\d{2})-(\\d{2})-(\\d{2})$") ; 2 2 2
  (define px:yymmdd     #px"^(\\d{2})(\\d{2})(\\d{2})$")   ; 6

  ; Ordinal date formats:
  
  (define px:ccyy-ddd   #px"^(\\d{4})-(\\d{3})$")          ; 4 3
  (define px:ccyyddd    #px"^(\\d{4})(\\d{3})$")           ; 7
  
  (define px:yy-ddd     #px"^(\\d{2})-(\\d{3})$")          ; 2 3
  (define px:yyddd      #px"^(\\d{2})(\\d{3})$")           ; 5
  
  ; Week/day formats:
  
  (define px:ccyy-www-d #px"^(\\d{4})-W(\\d{2})-(\\d)$")   ; W 4 2 1
  (define px:ccyywwwd   #px"^(\\d{4})W(\\d{2})(\\d)$")     ; W 4 3
  (define px:ccyy-www   #px"^(\\d{4})-W(\\d{2})$")         ; W 4 2
  (define px:ccyywww    #px"^(\\d{4})W(\\d{2})$")          ; W 4 2
  
  (define px:yy-www-d   #px"^(\\d{2})-W(\\d{2})-(\\d)$")   ; W 2 2 1
  (define px:yywwwd     #px"^(\\d{2})W(\\d{2})(\\d)$")     ; W 2 3
  (define px:yy-www     #px"^(\\d{2})-W(\\d{2})$")         ; W 2 2
  (define px:yywww      #px"^(\\d{2})W(\\d{2})$")          ; W 2 2
  
  ; Local time of day:
  
  (define px:hh:mm:ss   #px"^(\\d{2}):(\\d{2}):(\\d{2})$") ; 2 2 2
  (define px:hhmmss     #px"^(\\d{2})(\\d{2})(\\d{2})$")   ; 6
  (define px:hh:mm      #px"^(\\d{2}):(\\d{2})$")          ; 2 2
  (define px:hhmm       #px"^(\\d{2})(\\d{2})$")           ; 4
  (define px:hh         #px"^(\\d{2})$")                   ; 2
  
  ; Utility procedures ---------------------------
  
  ;; read-digit : string integer -> (U integer #f)
  (define (read-digit str pos)
    (let ([chr (string-ref str pos)])
      (if (char-numeric? chr)
          (char->integer chr)
          #f)))
  
  ;; read-number : string integer integer -> (U integer #f)
  (define (read-number str start end)
    (let loop ([accum 0] [pos start])
      (if (< pos end)
          (let ([digit (read-digit str pos)])
            (loop (+ (* 10 accum) digit) (add1 pos)))
          accum)))
  
  ;; read-fraction : string integer integer integer -> (U integer #f)
  (define (read-fraction input start end normalize-to)
    ; First read the digits that are available:
    (let loop ([accum 0] [pos start])
      (if (< pos end)
          (let ([digit (read-digit input pos)])
            (loop (+ (* 10 accum) digit) (add1 pos)))
          ; Normalize the result:
          (let loop ([accum accum] [pos (- pos start)])
            (if (< pos normalize-to)
                (loop (* 10 accum) (add1 pos))
                accum)))))
  
  ; Parsing dates --------------------------------
  
  ;; parse-normal-date : string (list-of integer) -> date
  (define (parse-normal-date str format)
    (match format
      [(list 4 2 2)
       ]
      [(list 8)
       ]
      [(list 4 2)
       ]
      [(list 4)
       ]
      [(list 2)
       ]
      [(list 2 2 2)
       ]
      [(list 6)
       ]
      [(list 4 3)
       ]
      [(list 7)
       ]))
  
  (define parse-date
    (case-lambda 
      [(str) (parse-date str (date-format str))]
      [(str format)
       (case (car format)
         [(#\D) ; Normal date format
          (parse-normal-date str (cdr format))]
         [(#\W) ; Week date format
          (parse-week-date str (cdr format))])]))
  )