DEV Community

Cover image for The 7 Types of Time Every Developer Must Know
bwi
bwi

Posted on

The 7 Types of Time Every Developer Must Know

Part 2 of 8 in the series Time in Software, Done Right


In the previous article, we established that a date is not a moment. But "date" and "moment" are just two of the many ways time shows up in software.

Most datetime bugs come from mixing up different types of time. Teams argue because one person says "timestamp" and means UTC, while another means local time. Someone stores an offset thinking it captures the timezone. Someone else treats a recurring rule like a single event.

Before we can write correct code, we need a shared vocabulary.

Here are the 7 types of time you'll encounter in software — and when to use each one.


1. Instant (Global Moment)

An Instant is a point in physical time that happens simultaneously everywhere on Earth.

When it's 14:00 in Vienna and 13:00 in London — that's the same instant, just displayed differently.

Technical representations:

  • Unix timestamp (seconds/milliseconds since 1970-01-01 UTC)
  • ISO 8601 with Z: 2026-01-21T13:05:12Z
  • NodaTime: Instant
  • JavaScript Temporal: Temporal.Instant

Use it for:

  • Log entries ("request received at...")
  • Audit trails
  • Token expiry ("expires in 3600 seconds")
  • Event sourcing (occurredAtUtc)
  • Anything where you need to answer: "Did X happen before or after Y?"

Key property: An Instant has no timezone, no calendar — it's pure physics. To display it to a human, you must convert it to a local time in some timezone.


2. Local Date (Calendar Day)

A Local Date is a day on the calendar — nothing more.

  • December 25th
  • October 31st
  • Your birthday
  • A deadline written as "by June 5th"

Technical representations:

  • 2026-06-05 (ISO 8601 date only)
  • NodaTime: LocalDate
  • JavaScript Temporal: Temporal.PlainDate

Use it for:

  • Holidays
  • Birthdays
  • Date-only fields ("date of birth", "release date")
  • Any business concept where time-of-day doesn't matter

Key property: A Local Date is not a moment. It has no timezone. Christmas in Sydney and Christmas in New York are the same date but different instants. You cannot convert a Local Date to UTC without adding a time and a timezone.


3. Local Time (Time of Day)

A Local Time is a time without a date — just hours, minutes, seconds.

  • "Daily standup at 09:00"
  • "Store opens at 10:00"
  • "Alarm at 07:30"

Technical representations:

  • 10:00:00 (ISO 8601 time only)
  • NodaTime: LocalTime
  • JavaScript Temporal: Temporal.PlainTime

Use it for:

  • Opening hours
  • Recurring daily schedules
  • Alarm clocks
  • "Every day at X" patterns

Key property: A Local Time is a rule, not an event. "10:00" happens every day, in every timezone. It's not a moment — it's a pattern. And on Daylight Saving Time (DST) transition days, 02:30 might not exist at all (or exist twice).


4. Local DateTime (Date + Time, No Zone)

A Local DateTime combines a date and a time — but without a timezone.

  • "June 5th, 10:00"
  • "Meeting next Tuesday at 14:30"

Technical representations:

  • 2026-06-05T10:00:00 (no Z, no offset)
  • NodaTime: LocalDateTime
  • JavaScript Temporal: Temporal.PlainDateTime

Use it for:

  • The "raw" input from a user before you know their timezone
  • Intermediate calculations
  • Display purposes (when the timezone is implied by context)

Key property: A Local DateTime is still not a moment. "June 5th 10:00" in Vienna and "June 5th 10:00" in London are different instants. To get a real moment, you need to pair it with a timezone.

This is where the magic happens:

LocalDateTime + Timezone → Instant
Enter fullscreen mode Exit fullscreen mode

5. Zoned DateTime (The Complete Picture)

A Zoned DateTime is a Local DateTime paired with an IANA timezone.

  • "June 5th, 10:00 in Europe/Vienna"
  • "Meeting at 14:30 Europe/London"

Technical representations:

  • NodaTime: ZonedDateTime
  • JavaScript Temporal: Temporal.ZonedDateTime

Use it for:

  • Meetings
  • Appointments
  • Deadlines with a clear location/context
  • Any human-facing time that needs to survive DST changes

Key property: This is what you should store for human-meaningful times. The IANA timezone (like Europe/Vienna) contains all the rules — historical DST, future DST, political changes. From this, you can always compute the correct Instant.

This is the model:

  • Store: LocalDateTime + TimeZoneId
  • Derive: Instant (for queries and sorting)

If timezone rules change (DST abolished, offset shifted), you recalculate the Instant. The human intent ("10:00 in Vienna") stays correct.


6. Offset DateTime (Snapshot, Not Intent)

An Offset DateTime is a date + time + UTC offset.

  • 2026-06-05T10:00:00+02:00

Technical representations:

  • ISO 8601 with offset
  • NodaTime: OffsetDateTime
  • .NET: DateTimeOffset
  • JavaScript Temporal: Temporal.Instant (formatted with offset)

Use it for:

  • Recording "what the clock showed at the moment of capture"
  • Serialization when you don't have full timezone info
  • Interoperability with systems that only support offsets

Key property: An offset is a snapshot, not a meaning. +02:00 could be Vienna in summer, Berlin in summer, Cairo, Johannesburg, or Kaliningrad — completely ambiguous.

The offset tells you the math at that moment, but it doesn't tell you:

  • Which region it was
  • Whether DST was in effect
  • What the offset will be next year

Don't use offsets as a substitute for timezones when storing human-facing times. If DST rules change, you can't recalculate — you've lost the intent.

⚠️ Never use timezone abbreviations like "EST", "CST", or "GMT" — they're ambiguous. "EST" means Eastern Standard Time in both the US and Australia. "CST" is used in the US, Australia, China, and Cuba. Always use IANA timezone IDs like Europe/London or Europe/Vienna.


7. Recurring Rule (Pattern, Not Event)

A Recurring Rule is not a timestamp — it's a pattern that generates timestamps.

  • "Every Monday at 09:00"
  • "First day of every month"
  • "Annually on December 25th"
  • "Every 15 minutes"
  • Cron: 0 9 * * 1

Technical representations:

  • iCalendar RRULE
  • Cron expressions
  • Custom domain models

Use it for:

  • Recurring meetings
  • Scheduled jobs
  • Subscription billing cycles
  • Holidays with complex rules (Easter, Ramadan)

Key property: A recurring rule produces Local DateTimes. To get actual Instants, you need to apply a timezone — and handle edge cases like DST gaps (what happens to "02:30 every day" when the clock skips from 02:00 to 03:00?).


The Cheat Sheet

Type Has Date? Has Time? Has Zone? Is a Moment? Example
Instant No No No (UTC) ✅ Yes Log timestamp
Local Date ✅ Yes No No ❌ No Birthday
Local Time No ✅ Yes No ❌ No "Opens at 10:00"
Local DateTime ✅ Yes ✅ Yes No ❌ No User input
Zoned DateTime ✅ Yes ✅ Yes ✅ Yes ✅ Yes Meeting
Offset DateTime ✅ Yes ✅ Yes Partial ✅ Yes Snapshot
Recurring Rule Pattern Pattern Needs one ❌ No "Every Monday"

📝 Other types you might need: Some domains use YearMonth (credit card expiry: "03/2027", financial periods) and MonthDay (annual events: "March 15th" without a year). NodaTime has YearMonth; Temporal has Temporal.PlainYearMonth and Temporal.PlainMonthDay.


Why This Matters

When you can name the type of time you're dealing with, you can:

  1. Choose the right storage format — don't store a Local Date as a timestamp
  2. Avoid silent conversions — don't let the framework "helpfully" add a timezone
  3. Communicate clearly — say "we need a Zoned DateTime here" instead of arguing about "timestamps"
  4. Catch bugs early — if someone passes a Local DateTime where you need an Instant, that's a type error

Most datetime bugs are category errors — treating one type of time as another. A shared vocabulary prevents that.


Bonus: Two Types of Time Amounts

The 7 types above describe when something happens. But you'll also need to express how long — and there are two very different ways:

Duration (Physical Elapsed Time)

A Duration is an exact, fixed amount of time — measured in hours, minutes, seconds, or smaller units.

  • 90 minutes
  • 3600 seconds
  • 2.5 hours

Technical representations:

  • NodaTime: Duration
  • JavaScript Temporal: Temporal.Duration (with time units)
  • ISO 8601: PT1H30M (1 hour 30 minutes)

Key property: A Duration is always the same length, no matter when you apply it. 90 minutes is 90 minutes — in January, in June, in any timezone.

Period (Calendrical Amount)

A Period is a calendrical amount — measured in days, months, or years.

  • 1 month
  • 2 weeks
  • 1 year

Technical representations:

  • NodaTime: Period
  • JavaScript Temporal: Temporal.Duration (with date units)
  • ISO 8601: P1M (1 month), P1Y (1 year)

Key property: A Period's actual length depends on when you apply it:

January 31 + 1 month = February 28 (or 29)  → 28-29 days
March 31 + 1 month = April 30              → 30 days
July 31 + 1 month = August 31              → 31 days
Enter fullscreen mode Exit fullscreen mode

"Add 1 month" and "add 30 days" are not the same operation.

Why This Distinction Matters

// These look similar but behave differently:
var in30Days = today.PlusDays(30);           // Always exactly 30 days
var nextMonth = today.Plus(Period.FromMonths(1));  // Depends on the month

// "Renew subscription in 1 month" vs "Renew in 30 days"
// — different business meanings, different results
Enter fullscreen mode Exit fullscreen mode

When someone says "add a month," ask: do they mean a calendar month (Period) or 30 days (Duration)? The answer matters for billing cycles, contract renewals, and any date arithmetic involving months or years.


Key Takeaways

  • There are (at least) 7 distinct types of time in software
  • Instant, Zoned DateTime, and Offset DateTime represent actual moments — but only Zoned DateTime preserves the human intent
  • Offsets are snapshots, not intent — prefer IANA timezones for human-facing times
  • Recurring rules generate times, they aren't times themselves
  • If you can't name the type of time you're working with, you're probably about to introduce a bug

💡 What about leap seconds? We're ignoring them — intentionally. Unix time ignores them, NodaTime ignores them by default, and databases ignore them. For 99.9% of applications, this is correct. If you're building satellite navigation or scientific instrumentation, you already know who you are.


Next up: Deadlines Are Hard — why "Submit by June 5th" is a broken requirement, and what to do about it.

Top comments (0)