Developer Tools

10 Date-Time Rules I Wish Every Developer Knew

After years of debugging timezone issues, DST gaps, and timestamp mismatches, these are the rules I follow religiously. Most bugs come from breaking rule #1.

VR
Vikram Rao

Senior Software Engineer

17 de janeiro de 2026·8 min de leitura

Rule 1: Store Everything in UTC

This is the single most important rule. Your database, your API responses, your event logs — all UTC. Convert to local time only at the display layer, as late as possible. The moment you store a local time without offset information, you've created a bug that will surface at the worst possible moment.

I've seen a production system that stored appointment times in "US/Eastern" because "all our users are in New York." Then they expanded to California. Then they got a customer in Arizona (which doesn't observe DST). Three months of debugging followed.

The one exception to the "store UTC" rule — and it's an important one — is future scheduled events in a specific location. If a user books a doctor's appointment for "2:00 PM on March 15, 2027 in New York," you should store the local time plus the IANA timezone identifier, not the UTC equivalent. Why? Because if the US Congress changes DST rules between now and then (as they've attempted with the Sunshine Protection Act), the UTC offset might change. If you stored the UTC time, the appointment would silently shift by an hour. Store the user's intent — "2:00 PM America/New_York" — and compute the UTC equivalent at the time of use.

Rule 2: Convert Only at the Display Layer

All your business logic, comparisons, sorting, and filtering should operate on UTC. Convert to the user's local timezone only when rendering the UI. This means your backend never needs to think about timezones — it's the frontend's job. If you're using React, do the conversion in the component. If you're sending emails, convert right before formatting the body text.

This rule makes debugging dramatically easier. If every log message, database record, and API response is in UTC, you can trace an event through your entire system without converting between zones. The moment you have some components logging in UTC and others in local time, you lose the ability to correlate timestamps at a glance. I've been in incident rooms where 20 minutes were wasted figuring out that two log entries were actually the same event — one was UTC, the other was the server's local time in US/Pacific.

Rule 3: Use IANA Timezone Identifiers

Always store and transmit timezones as IANA identifiers: America/New_York, Europe/Berlin, Asia/Kolkata. Never use abbreviations like EST, CST, or IST. EST is ambiguous (Eastern Standard Time in the US, but also Eastern Standard Time in Australia). IST could be India Standard Time, Israel Standard Time, or Irish Standard Time. IANA identifiers are unambiguous and encode the full history of DST rules.

There are about 594 zone identifiers in the current IANA database (as of 2024a release), though many are aliases. US/Eastern is an alias for America/New_York. Both work, but the canonical form uses the continent/city format. When showing a timezone picker to users, you'll want to filter the list to around 100–150 common zones and group by region. Nobody wants to scroll through 594 options to find their timezone.

Rule 4: Never Hard-Code UTC Offsets

// BAD: will break during DST
const nyTime = new Date(utcTime.getTime() - 5 * 3600000);

// GOOD: handles DST automatically
const nyTime = new Intl.DateTimeFormat("en-US", {
  timeZone: "America/New_York"
}).format(utcTime);

UTC-5 is Eastern Standard Time. UTC-4 is Eastern Daylight Time. If you hard-code -5, your code is wrong for half the year. Let the timezone database handle the offset lookup.

I'll extend this rule further: don't hard-code DST transition dates either. I've seen code that checks "if month is between March and November, use EDT." That breaks for Arizona, it breaks for any year where Congress changes the DST rules, and it breaks for the 2007 change when the US extended DST by several weeks. The IANA database knows all of this history. Use it.

Rule 5: Handle DST Gaps

When clocks spring forward, a gap appears. In the US, at 2:00 AM on the second Sunday of March, clocks jump to 3:00 AM. The time 2:30 AM doesn't exist that day. If a user schedules something for 2:30 AM, your code needs to handle it. Most timezone libraries will push the time forward to 3:00 AM or 3:30 AM, but the behavior varies. Test it.

Here's a real scenario: a cron job is scheduled for 2:30 AM daily. On the spring-forward night, that time never occurs. Does your scheduler skip the run entirely? Does it run at 3:00 AM? Does it run at 3:30 AM? The answer depends on your cron implementation. systemd timers on Linux handle this one way; traditional crontab handles it differently. If that cron job is processing overnight batch data and a missed run means corrupted reports, you need to know the behavior before it happens, not after.

Rule 6: Handle DST Overlaps

When clocks fall back, an overlap appears. At 2:00 AM, clocks go back to 1:00 AM. The time 1:30 AM happens twice — once in daylight time, once in standard time. If you're recording when something happened at 1:30 AM on that day, you need the offset to disambiguate. Without it, you can't reconstruct the actual instant.

This is particularly nasty for event logging and audit trails. If your system logged an action at "1:30 AM on November 1, 2026" without an offset, a compliance auditor can't determine the actual time. Was it the first 1:30 AM (EDT, UTC-4) or the second 1:30 AM (EST, UTC-5)? They're an hour apart. In financial systems, that ambiguity can matter for trade settlement windows and regulatory reporting.

Rule 7: Use ISO 8601 / RFC 3339 for Serialization

When transmitting dates over APIs, use 2026-03-15T14:30:00Z or 2026-03-15T14:30:00+05:30. Not "March 15, 2026". Not "15/03/2026" (is that March 15 or the 3rd of the 15th month?). Not a Unix timestamp (unless your consumers are all machines). ISO 8601 is unambiguous, sortable, and universally parsed.

One thing I'll add: always use 4-digit years. "26-03-15" is technically valid ISO 8601 with a 2-digit year (the standard allows it with mutual agreement), but it's indistinguishable from "2026-03-15" at a glance. And when your date strings are sortable as plain strings — which they are in full ISO format — you get correct ordering for free in databases, filenames, and log files. That's a surprisingly handy property.

Rule 8: Don't Roll Your Own Date Math

"How hard can it be to add 30 days?" Harder than you think. Adding 30 days to January 31 gives you March 2 (or March 3 in leap years). Adding 1 month to January 31 — what's that? February 28? March 1? Different libraries give different answers. Use a tested library. date-fns, Luxon, java.time, Python's dateutil — pick one and trust it over your own arithmetic.

The edge cases in date arithmetic are genuinely surprising. What's the last day of February in a leap year if you add 1 year to February 29? Most libraries give you February 28 of the next year. But add 4 years to February 29, 2024 and you get February 29, 2028 — a leap day landing on a leap day. What if you add 1 month to February 29? Is it March 29? That's what most libraries return, but it means "add 1 month, subtract 1 month" doesn't get you back to where you started (March 29 minus 1 month is February 28, not February 29). Date math doesn't have the nice reversibility properties you'd expect from regular arithmetic.

Rule 9: Test with Edge-Case Dates

Your test suite should include:

  • February 28 and 29 (leap year boundary)
  • DST transition dates (spring forward, fall back)
  • December 31 / January 1 (year boundary)
  • Timezones with non-hour offsets (India UTC+5:30, Nepal UTC+5:45)
  • Dates in the past where timezone rules were different
  • Midnight UTC — a time that falls on a different calendar date in negative-offset zones
  • The 2038 overflow date (January 19, 2038) if you touch any 32-bit timestamp code

I keep a fixture file of known-tricky dates in every project. It's saved me more times than I can count.

Here's a concrete list I use. For 2026: March 8 (US spring forward), March 29 (EU spring forward), November 1 (US fall back), October 25 (EU fall back). For leap year testing: February 28–29, 2028. For year boundaries: December 31, 2026 (which is in ISO Week 53, belonging to 2026). For cross-date bugs: 11:30 PM UTC on any date — it's already tomorrow in Tokyo but still today in Chicago. If your test suite covers all of these and your code passes, you're ahead of 90% of production systems I've audited.

Rule 10: Document Your Timezone Assumptions

Every system makes timezone assumptions somewhere. "Created_at is UTC." "The user's preferred timezone is stored in their profile." "Cron jobs run in the server's local time." Write these down. Put them in your API docs, your README, your database schema comments. Future you (or the next person on the team) will be grateful.

A good documentation practice I've adopted: put a comment at the top of every database migration that creates a timestamp column. Something like -- stored as UTC; converted to user's timezone at display layer. For API endpoints, include the timezone in the field description: "created_at": "UTC timestamp in RFC 3339 format". In configuration files for cron jobs, note which timezone the schedule is expressed in. These five-second documentation habits pay dividends when you're debugging at 2 AM six months later.

Bonus: Three Anti-Patterns I See Constantly

Beyond the ten rules, here are three patterns that cause recurring pain in systems I've reviewed:

Anti-pattern 1: Using the server's local time for anything

Your server's timezone is an accident of deployment. If you deploy to AWS us-east-1, your server is in UTC. If you deploy to a managed hosting provider in Dallas, it might be in US/Central. Code that calls Date.now() or datetime.now() without specifying a timezone is implicitly using the server's timezone, which means its behavior changes depending on where it's deployed. Always pass an explicit timezone.

Anti-pattern 2: Storing "date only" as a datetime at midnight

If your data is genuinely a date — someone's birthday, a reporting date, the date of a contract — store it as a date, not as a datetime at midnight UTC. "March 15, 2026" is a calendar date; "2026-03-15T00:00:00Z" is a specific instant that corresponds to different calendar dates in different timezones. Storing a birthday as midnight UTC means someone in UTC-12 sees the birthday on the 14th. Use your database's DATE type and treat dates as dates.

Anti-pattern 3: Comparing timestamps with string comparison

ISO 8601 strings are sortable... if they're all in the same timezone. "2026-03-15T14:30:00Z" is alphabetically after "2026-03-15T14:30:00+05:30", but it represents a later instant in time (the +05:30 timestamp is actually 09:00 UTC). String sorting only works if all your timestamps use the same offset, preferably Z. If you mix offsets, you must parse to a proper datetime type before comparing.

Frequently Asked Questions

Why should I store timestamps in UTC?

UTC is the only timezone without DST transitions, ambiguous times, or political rule changes. It provides a single, unambiguous point of reference. Storing in local time without offset information means you can't reconstruct the actual instant if DST rules change or if users are in different timezones. UTC is the only safe default.

What happens during a DST gap?

When clocks spring forward (e.g., from 2:00 AM to 3:00 AM), the skipped hour doesn't exist. If you try to create a time like 2:30 AM on that day, timezone libraries will typically adjust it forward to 3:30 AM or the start of the next valid time. The exact behavior depends on your library and language — test it explicitly.

Should I use timezone abbreviations like EST?

No. Abbreviations are ambiguous. "CST" could mean Central Standard Time (UTC-6), China Standard Time (UTC+8), or Cuba Standard Time (UTC-5). Always use IANA identifiers like America/Chicago or Asia/Shanghai. They're unambiguous and carry the full DST rule history.

How do I test for timezone bugs?

Build a test fixture with edge-case dates: DST transitions, leap day, year boundaries, and half-hour timezone offsets. Run your tests with TZ environment variable set to different timezones. Test timestamps near midnight UTC — they fall on different calendar dates in different zones. Use property-based testing to generate random datetimes if your framework supports it.

What is the IANA timezone database?

The IANA timezone database (also called tz or zoneinfo) is the authoritative source of timezone rules used by Linux, macOS, Java, Python, JavaScript, and most other platforms. It contains historical and current UTC offsets, DST transition dates, and abbreviations for every timezone. It is updated several times per year as governments change their timezone rules.

How do I handle dates across multiple timezones in a database?

Store all timestamps in UTC in your database (using a TIMESTAMP WITH TIME ZONE or equivalent column type). Store the user's IANA timezone identifier (e.g., America/New_York) separately in their profile. Convert from UTC to local time at the application or display layer, never in the database query itself.

What is a DST overlap and how do I handle it?

A DST overlap occurs when clocks fall back, causing a period of time to repeat. For example, 1:30 AM happens twice on the fall-back day. To handle it, always store timestamps with UTC offsets or in UTC. When displaying ambiguous local times, include the offset (e.g., "1:30 AM EDT" vs "1:30 AM EST") to disambiguate.

Sources

  • Falsehoods programmers believe about time — Noah Sussman (infiniteundo.com)
  • IANA Time Zone Database (iana.org/time-zones)
  • W3C Note — Date and Time Formats (w3.org/TR/NOTE-datetime)
  • Zach Holman — "UTC is Enough for Everyone, Right?" (zachholman.com)

VR

Sobre o Autor

Vikram Rao

Senior Software Engineer

Vikram Rao has been writing timezone-resilient software for fourteen years, building scheduling infrastructure for distributed teams. He has spoken at multiple developer conferences on the surprisingly difficult topic of handling dates and

Ler biografia completa →
Voltar ao Blog

Artigos Relacionados