FreeToGenerate.com

Reads a Date, Expires, Last-Modified or Retry-After value the way RFC 9110 says to, and shows you where your own browser disagrees. Runs in your browser; nothing is uploaded.

Try one:

Retry-After

The fifty-year rule

The obsolete format writes the year with two digits, and RFC 9110 says a recipient must read a value more than fifty years in the future as the most recent past year ending in those digits. The answer therefore changes over time: the boundary moves forward by one every January, and a value that means 2076 today will mean 1976 in fifty-one years.

Also available in: Español · Português · Français · العربية

HTTP date format parser

The three formats HTTP allows, what each one means, and the six ways Date.parse gets them wrong.

What is an HTTP date?

Every timestamp in an HTTP header — Date, Expires, Last-Modified, If-Modified-Since, and the timestamp form of Retry-After — is written in a format RFC 9110 calls HTTP-date. There are three of them, because before 1995 servers used three, and the specification defines all three for compatibility.

The rule about them is asymmetric, and that asymmetry is the thing worth remembering. A recipient that parses a timestamp "MUST accept all three HTTP-date formats"; a sender "MUST generate those timestamps in the IMF-fixdate format". So there is exactly one spelling you are allowed to produce — Sun, 06 Nov 1994 08:49:37 GMT — and three you have to be able to read.

All three represent an instant in UTC. The first two say so with the letters GMT; the third, inherited from C's asctime function, has no zone at all and the specification simply states that its values are assumed to be in UTC. That sentence is where most of the trouble starts.

How to use it

  1. Paste the header value. Just the value, without the field name. The three sample buttons cover one of each format, and the fourth writes out this moment the way a sender is required to.
  2. Read the format line and the notes under it. You get the instant in UTC and in your own time zone, which of the three formats it is, whether a sender is allowed to send it, and every way it departs from the grammar.
  3. Compare the last row with the one above it. That row is what your browser's own Date.parse makes of the same string. Where the two differ, the difference is shown in seconds.

Six ways Date.parse gets this wrong

The most damaging is asctime. That format has no time zone and RFC 9110 says its values are assumed to be UTC, but Date.parse reads it as local time — so the answer is wrong by the reader's own UTC offset, and right only for people in Greenwich. It is the worst kind of bug: silent, plausible, and different on every machine. The comparison row on this page runs in your browser precisely so you can see what yours does.

Then there are two-digit years. The obsolete RFC 850 format writes the year with two digits, and the specification's rule is relative to the present: a recipient must read a value that appears more than fifty years in the future as the most recent past year ending in those digits. JavaScript engines use a fixed pivot at 50 instead. Today those two rules disagree about 27 of the 100 possible years — every value from 50 to 76 — and because the specification's boundary moves forward each January, that set grows by one every year. A value of 68 means 2068 by the rule and 1968 by the pivot: a full century apart.

A four-digit year below 100 goes the same way. The grammar says the year is four digits, so 0094 is a legal way to write the year 94, and Date.parse maps it into 1994 anyway.

The day name is redundant, and that makes it useful twice over. If it contradicts the date, something assembled the value wrongly — Mon, 06 Nov 1994 is a Sunday, and Date.parse accepts it without a word. It also settles the century: the sample above, Tuesday, 06-Nov-68, is only consistent if the year is 2068, because 6 November 1968 was a Wednesday. The redundant field disambiguates the ambiguous one, which is a nice accident of a format that was never designed for it.

The grammar permits second 60 for a leap second. Date.parse turns 23:59:60 into 23:59:00, throwing the seconds away and losing a minute instead of rolling forward by one second.

Finally, impossible dates. 31 November is not a date, and Date.parse returns 1 December for it rather than NaN — a value that will look entirely reasonable in a log.

Honest limits

This page checks the value, not the message it came from. Whether a Date header should have been sent at all, whether a cache may use it, and whether the sender's clock was right are all outside what a string can tell you — and RFC 9110 explicitly allows a recipient with a clock to replace an invalid Date value with the time the response arrived.

It also reports the fifty-year rule as it applies today. That is the correct behaviour and it is worth saying out loud: the same RFC 850 timestamp genuinely means different years depending on when it is read, so a value cached in a log now and re-read decades later can change meaning. There is no way to fix that in a parser; the format is simply short two digits.

The strictness verdict is about the grammar, not about whether the value will work. A lower-case gmt, a one-digit day and an extra space are all things the specification forbids a sender from producing, and are also things every real parser accepts. The page tells you which side of that line you are on rather than pretending it is pass or fail.

The Retry-After box distinguishes its two forms — a whole number of seconds, or a timestamp — because that field is the one place HTTP allows either, and it is a common source of confusion. A negative number and a fractional one are neither.

Why is it free?

Everything runs in your browser. The header value is never sent anywhere, which matters for a page like this: a Last-Modified or an If-Modified-Since value from a real system says something about that system.

There is no server cost to recover, so there is no account, no limit and no watermark.