Epoch Tools Blog

Handling Timezones with Epoch Time: UTC, IST, and Beyond

When it comes to handling time, things can get a little ugly, especially if you’re dealing with different time zones and epoch time. Epoch time, the universal timestamp superhero, is counting seconds from January 1, 1970, endlessly. Sounds fun, right?

While this may sound easy, in reality, it’s quite the opposite. While epoch time might be straightforward and timezone-free, humans live in a world of local clocks, like PST in California or BJT in China. This complex standard makes it difficult to reliably convert one universal timestamp into the “right” local time without chaos.

Instead of just adding or subtracting a fixed number of hours, you suddenly need timezone databases, well-architected libraries, and a clear and accurate strategy for how your app stores, converts, and displays time so that “9 AM” always means what the user thinks it is.

Epoch Time’s Local Time Tango: From Universal to “Your Time”

Epoch time, although wrapped up in a steady UTC standard, becomes complex when converting to a specific locale (locale- definition link) like PST or BJT. Let’s understand this with an example, a timestamp like 1732950000, which corresponds to November 30, 2024, 7:00 AM UTC.

In JavaScript, new Date(1732950000 * 1000) instantly adjusts it according to the browser’s local timezone, showing something like “Sat Nov 30 2024 12:30:00 GMT+0530 (India Standard Time).” For greater precision across zones, offsets can be added manually; for example, IST is UTC+5:30, so 7:00 AM UST plus 5 hours 30 minutes becomes 12:30 PM IST.

This calculation, as quick and simple as it sounds, breaks down in regions that observe daylight saving time, where the offset might not be the same throughout the year.

Let’s come to the part where things get truly tricky. Daylight saving time does make things look pretty scarier. If you don’t know what Daylight saving time means, it is the practice of temporarily setting the clocks forward (usually by one hour) to shift more usable daylight into the evening during part of the year, then setting them back later.

Let’s understand with examples how daylight saving time makes those zones truly tricky:

  • Pacific Time (PST/PDT): Pacific Daylight Time is generally used by Los Angeles in winter, which is generally UTC - 8, i.e., 8 hours behind the UTC clock. So, 2025‑01‑15 12:00 UTC becomes 2025‑01‑15 04:00 PST while in summer it switches to Pacific Daylight Time, which is UTC-7, so the same date in summer becomes 2025‑01‑15 05:00 PDT.

  • Central Europe (CET/CEST): In winter, Berlin (Germany) uses Central European Time or CET, which is UTC+1, which makes 2025‑01‑15 12:00 UTC to 2025‑01‑15 13:00 CET quite easy, right? While this may sound simple, in summer Berlin uses Central European Summer Time (CEST), which is UTC+2 (1 hour ahead of CET), making 2025‑07‑15 12:00 UTC to 2025‑07‑15 14:00 CEST as if we were not confused by the name itself.

    The offset flips between +1 and +2 during the year, so “UTC+1” is only correct for part of the year, while the other part requires the correct offset.

The key point here is that while with some timezones, such as IST, you can always add +5:30 and get the correct conversion from UTC while with some (like PST/PDT, CET/CEST), the offset itself is a moving target which makes the need for a timezone database or library mandatory instead of simple “epoch + fixed offset” math.

UTC or Bust: Why ‘Just Add 5:30’ is a Trap

If you think the complexity is over yet, you’re wrong. It has just started. Although we do have some simple time zones like IST, some time zones like PST/PDT or CET/CEST start acting weird due to offset from UTC, literally changing during the year.

For example, Los Angeles is UTC-8 in winter (PST) and UTC-7 in summer (PDT) (as we saw above), while Berlin is UTC+1 in winter (CET) and UTC+2 in summer (CEST). Naively storing “UTC-8” for Pacific Time and doing -8 hours to every timestamp, all your summer dates will go crazy by being wrong by one full hour.

That’s what we already know, but the problem arises during the daylight saving time transitions, where the clock changes create two kinds of “weird” local times that break the simple mental model of time, confusing everyone in the chaos.

Let’s understand this with an example. In most of the US, clocks spring forward from 01:59:59 to 03:00:00 on DST (Daylight Saving Time), which means that as if the local time between 02:00 and 02:59 on that date simply doesn’t exist at all.

So, if you’ve a meeting scheduled at “02:30 AM local time” (we know it’s an odd time for a meeting, it’s just an example, so bear with us), it might behave differently depending on the date: one day it never happens, another day it happens twice.

So, a 02:30 AM, the meeting “never runs” on a spring-forward day while it can happen twice on the fall-back day. So the question arises, do you not attend a meeting on one day & attend it twice on some other day?

Of course not! The only sane way to survive this without banging your head around is to store everything in epoch/UTC while using real timezone databases and libraries to match that universal moment with the correct offset, and wall-clock time for that particular region.

UTC as the Single Source of Time: Store Once, Display Everywhere

Seeing this chaos, one might think to outsmart every timezone rule by himself/herself. But thinking is one thing, and acting on it is another. While the idea may sound fascinating, you rarely have to think about all the time zones to design your own system regarding the same. The key idea is very simple: treat UTC (or the epoch time) as your single source of truth.

Internally, every event, i.e, an order placed, a cron job fired, a log line written, should be stored precisely in UTC, as an epoch timestamp, something like 1764563400. Then, only when you need to show it to a human, you convert that universal timestamp to their local timezone based on the required settings, something like 2025‑12‑01 10:00:00 AM IST.

This lets you keep two different and important concerns cleanly separated. All the use cases, like storage, querying, analytics, and cross-region comparisons, all work in a single, consistent timeline, with no DST surprises or offset drift left to worry about.

While you can use mature libraries like Moment.js can help you with properly formatting and displaying the right local time, while labelling it with the right abbreviation (like PST vs PDT or CET vs CEST), while handling the edge cases like missing or repeated hours.

In practice, that usually means storing created_at as UTC/epoch with timezone as an identifier like “America/Los_Angeles” instead of a generic numeric offset while converting it at the last moment, right before showing it in the UI to the end user.

From Timestamps To Time Travel: How Apps Actually Use UTC

Storing a precise UTC (Coordinated Universal Time) timestamp is only half the story; the real complexity starts when different stacks start converting the same epoch time into different locales.

Let’s understand this with an example: a backend service like MongoDB saves a value named created_at with the value 1764563400 (which is 2025-12-01T04:30:00Z in UTC) whenever a new order is placed on your website.

Later on, while fetching the values for your admin panel, your frontend fetches that same number and converts it to Nov 30, 2025, 8:30 PM for someone America/Los_Angeles timezone setting and Dec 1, 2025, 10:00 AM for someone in theAsia/Kolkata, all without actually changing the originally stored value.

This pattern is completely normal for the frontend, creates chaos and confusion for the users who see different data in different time zones without actually knowing what caused the anomaly in the first place.

So, what’s the solution? One simple thing to do is to store all the timestamps as UTC (or epoch seconds/milliseconds) in your database, logs, and messages along with the user’s timezone as an IANA ID like “Asia/Kolkata” or “America/Los_Angeles”, probably not as a numeric offset in a separate variable.

While converting UTC in the frontend, always use the user’s timezone with a real timezone library like Moment.js or Luxon, which uses the IANA timezone database, ensuring the conversion is accurate for anyone viewing it.

Wrapping Up the Time Circus: Be Boring, Use UTC

If there’s one thing this whole timezone roller coaster proves, it’s that “just adding a few hours” is a great way to end up with angry users, broken reports, and meetings that either vanish or happen twice. By now, you’ve seen how fixed-offset zones like IST stay chill while shape-shifting zones like PST/PDT and CET/CEST keep changing their minds, and how DST happily deletes or duplicates whole hours from the calendar.

The common thread across all the sane best practices out there is simple: normalize everything to UTC or epoch internally, and let a real timezone database plus good libraries handle the messy parts at the edges.

So the real power move isn’t to memorize every offset or hand-roll clever conversion logic—it’s to make time in your system gloriously boring. Store events as precise instants in UTC, store user and region preferences as IANA timezone IDs, and convert for display only when a human actually needs to read the time.

Normalize all timestamps to UTC or epoch for storage, and convert to local time only for display so your data stays consistent across timezones. To make this even easier in practice, you can use a dedicated helper like your own epoch converter as the first stop whenever you need to inspect, debug, or explain timestamps in your apps.