Time zones and the <time>
element
When you construct a JavaScript Date
object, the resulting Date
represents the time you intended. Most of the time. But sometimes it doesn’t.
Consider the following examples, which I ran on a computer set to Pacific Daylight Time (UTC-07:00):
% jsc >>> new Date("1979-08-22T08:43:00-04:00") Wed Aug 22 1979 05:43:00 GMT-0700 (Pacific Daylight Time)
In this first example, I explicitly provided a time in Eastern Daylight Time (UTC-04:00). My computer’s time zone is three hours off of that, so when the Date
gets printed, we see that 8:43 in the morning in Eastern Time is 5:43 in the morning, Pacific Time.
>>> new Date("1979-08-22T08:43:00") Wed Aug 22 1979 08:43:00 GMT-0700 (Pacific Daylight Time)
If we provide the same time but omit the time zone, the Date
constructor assumes you mean the computer’s local time zone.
>>> new Date(1979, 8-1, 22, 5, 43, 0) Wed Aug 22 1979 05:43:00 GMT-0700 (Pacific Daylight Time) >>> new Date(1979, 8-1, 22) Wed Aug 22 1979 00:00:00 GMT-0700 (Pacific Daylight Time) >>> new Date("1979-08-22") Tue Aug 21 1979 17:00:00 GMT-0700 (Pacific Daylight Time)
Here you can see that the Date
constructor takes a number of different input formats. None of these cases contain an explicitly-set time zone. And, as in the previous example, all but one of these uses the computer’s local time zone. But one of them doesn’t! In the last case, we provide a simple date—with no time at all—in ISO 8601 format. In this case, and only this case, the Date
constructor assumes you mean UTC.
I really wouldn’t mind—the web has accumulated many other, far worse, inconsistencies over time—except this one trips me up all the goddamn time on my own simple, static web site. How?
Well, when I write a blog post, I use the <time>
element (the HTML element that augments a human-readable date or time with a machine-readable representation of the same time) to mark up the date the post was published. Since I write these by hand, and I rarely post more than once a day, I generally don’t bother to get more specific than just the date.
For instance, consider the post you’re reading right now. I published it on . The markup for that looks like this:
<time datetime=2024-10-27>October 27<sup>th</sup>, 2024</time>
I’m sure you see where this is going. When I pass the contents of the <time>
element’s datetime
attribute to the Date
constructor for use inside my static site generator, my SSG thinks the post got published the day before it actually went out. (Midnight UTC is the previous evening in California.)
I have been meaning to fix this for ages, knowing full well it’d only take a few lines of JavaScript, but I didn’t get around to it until the other day. Assuming you have elTiempo
, a <time>
element whose datetime
attribute looks like YYYY-MM-DD
, and
maybePrependZero
,
an Intl
configured to format single digit integers with a leading zero, here’s the fix:
<time>
element’s time zoneconst tzOffset = new Date(elTiempo.dateTime).getTimezoneOffset();
const tzHours = -Math.floor(tzOffset / 60);
const tzMinutes = tzOffset % 60;
let fixedDateTime = elTiempo.dateTime + "T00:00:00";
if (tzHours > 0) {
fixedDateTime += "+";
}
fixedDateTime += maybePrependZero.format(tzHours) + ":";
fixedDateTime += maybePrependZero.format(tzMinutes);
elTiempo.dateTime = fixedDateTime;
But we want to fix every <time>
element on the page, of course, not just one of them.
<time>
element on the pageCreate a number formatter for ±HH:MM time zone offsets
for (const elTiempo of document.querySelectorAll("time")) {
Skip irrelevant <time> elements
Fix a <time> element’s time zone
}
We only need to fix <time>
elements whose datetime
attribute matches RFC 3339’s full-date
production, YYYY-MM-DD
.
<time>
elementsif (! /^\d{4}-\d{2}-\d{2}$/.test(elTiempo.dateTime)) {
continue;
}
All of the above code assumes maybePrependZero
exists.
const maybePrependZero = Intl.NumberFormat("en-US", {
style: "decimal",
minimumIntegerDigits: 2
});