kyle halleman home

Localizing dates in Eleventy (and using the bundle plugin)

June 13, 2025

It bugged me for the longest time that I would write a blog post in Eleventy and put a date in the frontmatter of something like 2025-06-12 and no matter what time I published it at, it would show up in my RSS feed as publishing at 10:00 p.m.

So I went about fixing that by adding a full ISO date time string in the frontmatter, complete with timezone and everything: 2025-06-12T12:18:36.693Z. Locally, this worked great! But then I would publish the site and—depending on the time of day—the published date would show something like “June 13, 2025.” Which, as someone sitting here in the United States at 9:00 p.m. was highly frustrating.

If you’ve been in software engineering long enough, you already know the issue here. My local server is my computer, in the United States, and the server building my site uses UTC time. If I publish later in the day, then time seemingly jumps forward for me. At least, the date does.

How does one solve this? I could possible force everyone into my ethnocentric view and adjust the dates to reflect my desired time zone. But that’s not right either. I want everyone to feel comfortable knowing that the date shown for a published post is when it happened, localized to their part of the world.

And now we add the first bit of client-side JavaScript to this site.

Adding JavaScript using 11ty’s Bundle Plugin

This one threw me for a loop at first. Eleventy version 3 added a handy plugin that will take all the JavaScript (or CSS) you use on a page and bundle that into one file, with only the stuff you use loading on that page. With CSS it goes a step further and can inline that CSS so there’s not even an additional HTTP request.

The problem was, that template I used only included a CSS example, and I wasn’t sure how to do the same with JavaScript. I threw a main.js in the public folder and tried including it like you would any old script: <script src="/js/main.js"></script>. That worked in that the script showed up on the page and ran fine, but it wasn’t loading into the script bundle here.

Then I realized my folly. The same template file gave me the answer, if I just looked at the CSS bundle implementation:

<style>{% include "css/index.css" %}</style>

Aha, so maybe this would work?

<script type="module">
	{% include "js/main.js" %}
</script>

Indeed, that was the trick. Now, onto actual functionality.

Localizing dates

The publish dates are wrapped in a time element, which has a datetime attribute that should be a machine-readable date string, such as an ISO string. I updated the base blog template’s htmlDateString filter to return an ISO string instead of the simple yyyy-LL-dd:

eleventyConfig.addFilter("htmlDateString", (dateObj) => {
	return dateObj.toISOString();
});

Now a post in my posts page looks like:

<li>
	<a href="/posts/eleventy-from-almost-scratch/"
		>Eleventy from almost scratch</a
	>
	<time datetime="2025-06-08T02:57:48.452Z">June 8, 2025</time>
</li>

And with a light sprinkling of JavaScript:

function localizeDates() {
	// check to see if the browser supports Intl.DateTimePFormat
	if (typeof Intl !== "undefined" && "DateTimeFormat" in Intl) {
		// use the user's language with window.navigator.language
		const dateTimeFormat = new Intl.DateTimeFormat(window.navigator.language, {
			// 2025
			year: "numeric",
			// June
			month: "long",
			// 8
			day: "numeric",
			// get the user's timezone: https://stackoverflow.com/a/34602679
			timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
		});
		// get all the `time` elements on the page
		const dates = document.querySelectorAll("time");
		// loop through them and use the value of the `datetime` attribute to localize the dates
		dates.forEach((el) => {
			el.textContent = dateTimeFormat.format(new Date(el.dateTime));
		});
	}
}

Now you have the publish date as it was where you live.

To get it to run on page load, just need to recreate the old jQuery $(document).ready() in vanilla JavaScript:

if (document.readyState === "loading") {
	// Loading hasn't finished yet
	document.addEventListener("DOMContentLoaded", init);
} else {
	// `DOMContentLoaded` has already fired
	init(); // (fires localizeDates)
}

This is what JavaScript should be used for. Simple scripts like this. I could take it a step further and turn it into a custom element that encapsulates this logic. Yeah. I’ll do that. Next time.