How do I generate RFC 7231 HTTP date headers in EK9?

← Date, Time, and Duration · Ref: Q551

EK9 DateTime writes and reads the RFC 7231 HTTP-date used by Date, Last-Modified, Expires and If-Modified-Since headers.

WRITING: rfc7231()

  httpDate <- DateTime().now().rfc7231()

Produces the IMF-fixdate form, e.g. 'Sat, 15 Jun 2024 14:30:00 GMT'. rfc7231() always writes the SAME INSTANT in GMT (UTC), whatever the DateTime's own zone or offset - there is no need to convert first:

  2024-12-25T00:00:00-05:00.rfc7231()   'Wed, 25 Dec 2024 05:00:00 GMT'

Day and month names are always English, whatever the machine's locale, the day always has two digits, and the zone is always the literal GMT - exactly what RFC 7231 section 7.1.1.1 requires. A year that cannot be written in four digits gives an unset String.

READING: fromRfc7231()

  since <- DateTime().fromRfc7231(header)

Accepts all three forms an HTTP recipient must accept, and gives the instant in UTC:

  IMF-fixdate   'Sun, 06 Nov 1994 08:49:37 GMT'
  RFC 850       'Sunday, 06-Nov-94 08:49:37 GMT'   (two-digit year: more than 50 years ahead means the previous century)
  ANSI asctime  'Sun Nov  6 08:49:37 1994'

Anything else - an offset instead of GMT, a one-digit day in IMF-fixdate, a day name that does not match the date, lower-case names, ISO 8601 text - gives an unset DateTime, so test it with ? or a guard. For ISO 8601 text use DateTime(text).

WHERE HTTP DATES ARE USED

  Date: response header (when the response was generated)
  Last-Modified: when the resource was last changed
  Expires: when the cached response becomes stale
  If-Modified-Since: conditional request header
  Retry-After: when to retry a failed request

An HTTP service does not have to write Last-Modified itself: an HTTPResponse that returns lastModified() gets the header, and If-Modified-Since is honoured with a 304, by the runtime.

See Q537 for general date formatting. See Q549 for locale formatting. See Q541 for timezone handling.

Example

defines module qa.rfc7231.http.headers

  defines program
    Rfc7231HttpHeadersDemo()
      stdout <- Stdout()

      // Date header for the current moment - rfc7231() converts to UTC itself
      now <- DateTime().now()
      httpDate <- now.rfc7231()
      stdout.println(`Date header set: ${httpDate?}`)

      event <- 2024-06-15T14:30:00Z
      stdout.println(`Last-Modified: ${event.rfc7231()}`)

      // Any offset or zone: the same instant is written in GMT
      nyEvent <- 2024-12-25T00:00:00-05:00
      stdout.println(`NY event: ${nyEvent.rfc7231()}`)

      // Reading a header: all three HTTP-date forms give the same UTC instant
      for header in ["Sun, 06 Nov 1994 08:49:37 GMT", "Sunday, 06-Nov-94 08:49:37 GMT", "Sun Nov  6 08:49:37 1994"]
        since <- DateTime().fromRfc7231(header)
        stdout.println(`Parsed: ${since}`)

      // Not an HTTP-date: unset
      notHttp <- DateTime().fromRfc7231("2024-06-15T14:30:00Z")
      stdout.println(`ISO text is an HTTP-date: ${notHttp?}`)

      // Is the resource newer than the client's copy?
      clientCopy <- DateTime().fromRfc7231("Sat, 15 Jun 2024 14:30:00 GMT")
      stdout.println(`Modified since: ${event > clientCopy}`)

Common mistakes

E50060 — DateTime has no toRFC1123() method. EK9 uses rfc7231() which produces the HTTP-standard date format. See ek9 -h E50060 for details.

Incorrect:

httpDate <- now.toRFC1123()

Correct:

httpDate <- now.rfc7231()
Other ways to ask this
  • How do I format dates for HTTP headers?
  • How does rfc7231() work in EK9?
  • How do I create an HTTP Date header value?

Coming from another language?

Java: DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)) - which writes a one-digit day and needs the UTC conversion done for it. Python: email.utils.formatdate(usegmt=True) / email.utils.parsedate_to_datetime. JavaScript: new Date().toUTCString() / Date.parse. Go: t.UTC().Format(http.TimeFormat) / http.ParseTime. EK9: dateTime.rfc7231() converts to UTC itself; DateTime().fromRfc7231(text) parses all three HTTP-date forms.

Keywords: fromRfc7231, withSameInstant, utc, header, duration, migrate, api, web, http, if-modified-since, format, parse, date, expires, gmt, timezone, rfc7231, last-modified, time