This is why we don't write date libraries

Published 2025-09-12, About 8 minute read.

I was assigned a pretty straightforward bug last week: we were miscalculating the end of day datetime. When a date was 2025-09-13T12:24:11.181+03:00, we needed to check to see if that datetime was the "end of day"

Why would need this? We needed to represent a date range up to and including a particular day. So the date filter that needed to be created was basically this: time <= endOfDay(date). And when we're parsing queries that include this "end of day" value, we need to be able to recognize it.

Also, before you ask, we couldn't support strict inequalities yet. We had to do less than (or greater than) and equal to. So we did indeed need to calculate the end point of that inequality- the end of day.

We just needed a isEndOfDay() function. How hard can it be?

Lesson learnt 1: Javascript datetimes are local

I knew this, but I didn't really know this, you know?

I figured that I could just write a function that would check the hour, minute, seconds, and milliseconds and see that it is indeed the last millisecond of the day. So my function would take in a date and then check if the time is 23:59:59.999.

Interesting....

What's funny is that someone else in my company started this logic, and the above worked for date times even in UTC (the third date in the above example). The example above will actually work for a subset of internet peoples: the ones in +00:00 time zones! And this dev was from London. So their local time calculates locally and happens to line up with UTC. During daylight saving's time I'm not so sure this would align.

It's important to note too that when you're dealing with datetime inputs in javascript, they are all considered based on your locale. When you pick a date and time from a datetime-local input, the Date object you receive will be from your locale automatically.

Check it out:

Javascript toString():
UTC time:

Lesson Learnt 2: Time zones are super duper hard

So what I thought was we could amend this function. What if I found the date-time offset and used that to get the hours in UTC? That way I could check both the local time and the UTC time and see if either is end of day.

So, I boldy moved on to get the "UTC hour" by getting the offset for the date and adjusting the hours. I quickly found that the offset came back as a number of minutes. "Weird," I thought. I vaguely remembered there might be a time zone that was a half an hour off in India, but I thought I could do a quick adjustment for that later...

"Too easy," I thought to myself. And, indeed, things were not alright. In the example above you may be wondering why I thought I was done, because the function wasn't working in your browser. I live in -05:00, and so I did the same thing my colleague did in London. I happened to use an offset that matched my computer timezone offset.

I knew there might be edge cases in different time zones, so I tested a whole bunch of "end of day" date time strings. Some were locally "end of day," and some were "end of day" in UTC.

Indeed, when asking for the offset, it's always the offset of your machine's local versus 00:00. When a Date is parsed in javascript, the date instance you have in hand does not retain the offset it used to parse the date. So when I tested many different offsets in the beginning string, they all did not calculate correctly.

What I was looking for was a way to keep the timezone along with the date, so that I could inspect the time as if my computer was in that time zone. This isn't something that we can do natively yet.

Another thing I was about to encounter along with this hour offset issue was that offsets are in minutes because some timezones are a half-an-hour off, and some are even 45 minutes off. We were going to start going into murky waters just thinking about those offsets, and we hadn't yet considered daylight savings 😱

J. Walter Weatherman from arrested development teaching the Bluth family 'that's why you don't make your own date utilities'

Lesson Learnt 3: Most likely someone has solved this problem before

Trust me, I knew this intellectually. But I still went down this path.

For me to make up for this loss of timezone information, we'd have to store the local information separately. This means I'd have to parse the offset off of the string and store the locale/timezone along with the date in my own data structure. This is something I could implement... but there are libraries for this.

I know that moment.js can do this. I haven't used it yet, but I know people who swear by the library. Alternatively, there's date-fns. This is lighter than moment.js, and I prefer it when I need to do date-intensive stuff.

More specifically, there is date-fns/tz which provides a way to store locale along with your time instance in a TZDate instance. This is a very thin wrapper that is compatible with date-fns, and this way you can use date-fns functions with a TZDate seamlessly.

date-fns also provides really handy utilities, such as endOfDay(), which I could have used from the beginning to do these calculations. There might still be a way to retain the locale, use date-fns/tz and endOfDay together, but I later found out that we do no locale storing when dates are coming from user UI. More on that later.

Ulitmately, there's a new native feature I'm sure all of us are waiting for that allows us to handle date times in different timezones as if our machine was in those zones:

There is a specific API called Zoned Datetime that I eagerly look forward to using some day. In the meantime, we need to use some sort of library.

I basically when down the road of this famous video, despite knowing this was probably foolish! If you haven't seen it, please, I beg you, watch this video!

Lesson Learnt 4: Be aware of when a problem is not well defined

Most importantly, I learned that I had not defined the problem sufficiently to understand that there wasn't a solution based on the architecture we currently had and the functionality provided from javascript.

I was in a situation where dates could come from two sources. One from developers manually writing date strings in docuemnts. In those cases we could instruct those developers to write their date times with an offset of +00:00. But the other source was users using the inputs we provided on the page, and they were native datetime-local inputs. We didn't make a concerted effort to make sure all times were calculated in UTC (I found out later this indeed was a technical requirement for the project.)

There came a point where we realized we had one set of logic to parse either of those situations. And to make matters worse, we had a concept of a user's preferential locale. It might be that the user is on the west coast, but wants to see all date times in central time.

We will eventually get this all organized and sorted, but at the time we found that we had a parsing situation that wasn't soluble in the near term. It would have been simpler had I asked a few questions up front. Do we store all our information in UTC? Do we do all our calculations in UTC? Does the user need to see their local datetime formatted in UTC to give the user feedback about their selections? When we select something like "end of day," which time zone do we mean that for? What if a user selects end of day in a timezone with offset "+02:00" but a user opens the same page and they're in a locale with offset "+12:45" becuase they're in New Zealand but they prefer a locale offset of "+08:00" to match with Singapore?

In closing...

Dates are famously not fun in Javascript. Check out this quiz which demonstrates how crazy dates can get: https://jsdate.wtf/

I hope you were able to have a chuckle over this, and maybe you could relate the path that I travelled down. Let me know what you think. Find me on Bluesky or Mastodon. I also have an RSS feed here

⬅️ Previous Post
Back to top