Skip to tool
ecech.
🕑 Date, Time & Timezone

HTTP Date Headers Are Not ISO 8601, and Getting It Wrong Just Disables Caching

Expires and Last-Modified want \u201cMon, 10 Aug 2026 12:34:56 GMT\u201d \u2014 English month names, literal GMT, no milliseconds. Anything else is silently ignored.

HTTP header format



ISO 8601

Epoch seconds

Your local time

From now


Cache headers for a given lifetime


Advertisement

How the calculation works

Same instant, and only one of these works in a header ISO 8601 Expires: 2026-08-10T12:34:56Z cache cannot parse it treated as already expired RFC 7231 Expires: Mon, 10 Aug 2026 12:34:56 GMT parsed correctly cached until that moment No error either way. The only symptom is that caching quietly stops working.

How to Use This Tool

Paste any date to get the header format, or paste a header to read it back. The lower box builds a matching pair of cache headers for a lifetime you choose.

The format, exactly

Mon, 10 Aug 2026 12:34:56 GMT. Every part is constrained:

  • Day and month names in English, three letters, regardless of the server's locale. A server formatting dates in the local language produces a header no client can read.
  • Two-digit day, with a leading zero where needed.
  • Four-digit year.
  • The literal string GMT — not UTC, not +00:00, not an offset. The specification requires those three characters.
  • Seconds resolution. There is no place for milliseconds.

In JavaScript, Date.prototype.toUTCString() produces exactly this, which makes it the one-line answer. toISOString() does not, and using it is the usual mistake.

Why the failure is silent

A cache that cannot parse a date header does not report an error — it treats the value as being in the past. So an Expires header in the wrong format means the response is considered stale immediately, and everything is re-fetched every time.

Nothing breaks. Pages still work. The only symptom is that your caching does not happen, which generally goes unnoticed until someone looks at the origin traffic and wonders why it is so high.

One-second resolution, and what it misses 12:34:56.100file saved 12:34:56.400client fetches it, records 12:34:56 12:34:56.900file saved again — same second The next If-Modified-Since says 12:34:56, the server agrees nothing is newer, and the client keeps a stale copy.
ETags avoid this, because they compare content rather than timestamps.

Prefer Cache-Control and ETag

Expires is an absolute moment, which means it depends on the client's clock being right. A machine with a wrong clock ignores your caching entirely. Cache-Control: max-age=3600 is a duration instead, so it works regardless, and it takes precedence when both are present.

Similarly, Last-Modified only has second resolution, so a file changed twice within one second will not be detected as modified. An ETag compares content rather than time and does not have that problem. Send both if you like — clients use whichever they support — but the modern pair is Cache-Control and ETag.

Where you still meet this format

  • Date — when the response was generated. Required on most responses.
  • Last-Modified and If-Modified-Since — the conditional request pair.
  • Expires — legacy absolute expiry, still widely sent alongside Cache-Control.
  • Set-Cookie's Expires attribute — same format, and a frequent source of cookies that vanish immediately because the date was written another way.
  • Email headers, which use a close relative from RFC 5322 that permits numeric offsets like +0000 as well.
Advertisement

Frequently Asked Questions

What format do HTTP date headers use?
RFC 7231 format: Mon, 10 Aug 2026 12:34:56 GMT. English three-letter day and month names, a two-digit day, a four-digit year, and the literal string GMT rather than an offset. In JavaScript, toUTCString() produces exactly this.
Can I use ISO 8601 in an Expires header?
No, and the failure is silent. A cache that cannot parse a date header treats it as being in the past, so the response is considered stale immediately and re-fetched every time. Nothing errors — your caching just never happens.
Why does it have to say GMT and not UTC?
Because the specification requires those three characters. Writing UTC, or +00:00, or Z makes the header invalid even though the instant is identical. This is one of the places where the format is genuinely fussier than it looks.
Should I use Expires or Cache-Control?
Cache-Control, because max-age is a duration and works regardless of whether the client's clock is correct. Expires is an absolute moment, so a machine with a wrong clock ignores it. Cache-Control also takes precedence when both are present, so sending both is harmless.
Why does If-Modified-Since sometimes miss a change?
Because the format only has one-second resolution. A file changed twice within the same second looks unchanged, so the client keeps a stale copy. An ETag compares content rather than timestamps and does not have that limitation.
Why does my cookie expire immediately?
Usually because the Expires attribute was written in a different date format. Set-Cookie uses the same RFC 7231 format as the headers, and a value the browser cannot parse is treated as being in the past, deleting the cookie instantly.

Related tools in Date, Time & Timezone

Browse all Date, Time & Timezone tools
The desk where ecech. tools get written: a laptop, a notebook of to-dos and a whiteboard listing the tools on the site.

Made by one person

ecech. is not a content farm. Every tool here is written and checked by hand, one at a time, by someone who wanted the tool to exist and could not find a version that showed its working.

No accounts and no sign-in, and nothing you type reaches a server — every calculation on this page runs inside your browser. The ads are served by Google and do set their own cookies, which is set out in full on the privacy page. More about the site.