Times Are Hard for Racketeers, too
APIs dealing with dates and times must be hard to get right. I'm not aware of any programming language whose standard library for dealing with times is both intuitive and comprehensive. Racket has built-in libraries that are second to none, but even it presently leaves something to be desired when it comes to support for handling dates and times.
For example, Racket's racket/date
module (in its current incarnation) includes a date->string
function, but no string->date
function. In other words, it doesn't include a function for parsing time strings.
I recently needed time parsing as I wanted to specify times to a Racket program as strings such as "Sun, 06 Jan 2013 02:21:31 +0100", while using objects (such as Racket's date
structure) as in-memory storage format in order to make the individual time components (such as month and year) readily queryable.
For the parsing support I turned to SRFI 19: Time Data Types and Procedures, which Racket implements in the form of the srfi/19
module. Said module does include a string->date
function, albeit for a different date structure (tm:date
).
One possibility for parsing and formatting times then is to use both racket/date
and srfi/19
modules, and to convert between their respective date structures. A restriction there is that Racket date
objects do not appear to be readily constructible from time components, but converting via seconds since Unix epoch (i.e., Unix time) is workable.
The code below shows how to convert between RFC 2822 time strings, Racket's native date
objects, and Unix times (integers). All of the functions return times in UTC, regardless of representation.
#lang racket
(require (prefix-in d. racket/date))
(require (prefix-in s. srfi/19))
(define (date->unix-time d) ;; struct date -> integer
(d.date->seconds d #f))
(define (unix-time->date t) ;; integer -> struct date
(seconds->date t #f))
(define (rfc2822->unix-time s) ;; string -> integer
(let ((d (s.string->date s "~a, ~d ~b ~Y ~H:~M:~S ~z")))
(s.time-second (s.date->time-utc d))))
(define (date->rfc2822 d) ;; struct date -> string
(parameterize ((d.date-display-format 'rfc2822))
(d.date->string d #t)))
(define (unix-time->rfc2822 t) ;; integer -> string
(date->rfc2822 (unix-time->date t)))
(define (rfc2822->date s) ;; string -> struct date
(unix-time->date (rfc2822->unix-time s)))
(provide rfc2822->date rfc2822->unix-time
unix-time->rfc2822 date->rfc2822)
Note that 'rfc2822
is one of the possible date-display-format
choices supported by racket/date
, and my personal favorite due to its readability. It is also widely supported. For example, try date -R
on the Linux command line, or Time.now.rfc2822
in Ruby. For Emacs one can define support easily enough.
(defun insert-current-time-in-rfc2822 ()
(interactive)
(insert (format-time-string "%a, %d %b %Y %T %z")))
Addendum (12 May 2013)
Thanks to Asumu Takikawa's recent work on Racket date
and srfi/19
compatibility, starting from Racket 5.3.4 the srfi/19
string->date
function returns a racket/base
compatible date
(apparently provided that the format string has day, month, and year components). Going forward then for code like the above there should be no need to use srfi/19
functions other than string->date
. The above code should still work, though.
Note (updated 13 Oct 2015): In some Racket versions (starting with 5.3.4, but by now fixed) the rfc2822->unix-time
function given above does not work, due to a minor issue with srfi/19
, one that causes an error when string->date
is used with the format directive "~a"
(and possibly some others). If you're using an affected version, one fix is to change the file “srfi/19/time.rkt” (somewhere around line 1470) to say (do-nothing (lambda (val object) object))
, to avoid the complaint about 0 values in a context where 1 is expected.