Time & Date in Programming: The Complete Developer Guide
Time handling is one of the most bug-prone areas in software development. Timezone offsets shift with Daylight Saving Time, Unix timestamps differ between seconds and milliseconds across languages, and cron syntax varies between platforms. This guide covers everything you need to handle time correctly.
Unix Timestamps Explained
A Unix timestamp is the number of seconds (or milliseconds) elapsed since January 1, 1970, 00:00:00 UTC — the Unix epoch.
Seconds vs. Milliseconds
This is the #1 source of timestamp bugs:
| Language | Function | Unit | Digits |
|---|---|---|---|
| JavaScript | Date.now() | Milliseconds | 13 |
| Python | time.time() | Seconds (float) | 10 |
| PHP | time() | Seconds | 10 |
| Java | System.currentTimeMillis() | Milliseconds | 13 |
| Go | time.Now().Unix() | Seconds | 10 |
| Ruby | Time.now.to_i | Seconds | 10 |
If your API returns 1711324800 (10 digits), it’s seconds. If it returns 1711324800000 (13 digits), it’s milliseconds. Confusing the two produces dates in the year 1970 or 56000+.
Convert timestamps instantly with our Unix Timestamp Converter.
Negative Timestamps
Dates before January 1, 1970 have negative timestamps:
0→ January 1, 1970 00:00:00 UTC-86400→ December 31, 1969 00:00:00 UTC2147483647→ January 19, 2038 03:14:07 UTC (32-bit max)
The Year 2038 Problem
32-bit signed integers overflow on January 19, 2038 at 03:14:07 UTC. Systems storing timestamps as 32-bit integers will interpret dates after this as December 1901. Modern 64-bit systems are not affected, but embedded systems, legacy databases, and IoT firmware may be vulnerable.
Timezones — The Hard Part
Timezones are not fixed offsets. They change with Daylight Saving Time, political decisions, and historical adjustments.
UTC vs. GMT vs. Offsets
- UTC (Coordinated Universal Time) — The global reference time. Never changes with DST.
- GMT (Greenwich Mean Time) — Historically equivalent to UTC, but technically a timezone.
- UTC+03:00 — A fixed offset. Does not tell you which timezone.
The IANA Timezone Database
Always use IANA timezone identifiers (e.g., America/New_York, Europe/Istanbul) instead of abbreviations like “EST” or “PST”:
- EST can mean Eastern Standard Time (UTC-5) or Eastern Standard Time in Australia (UTC+10).
America/New_Yorkis unambiguous and automatically handles DST transitions.
Common Timezone Bugs
Bug 1: Storing local time instead of UTC
// ❌ Wrong — stores local time, breaks across timezones
const date = new Date().toISOString(); // This is UTC, actually correct
const local = new Date().toString(); // This includes timezone — don't store this
Rule: Always store timestamps in UTC. Convert to local time only at display time.
Bug 2: DST transition gaps
When clocks spring forward (e.g., 2:00 AM → 3:00 AM), times between 2:00-2:59 AM do not exist. When clocks fall back, times between 1:00-1:59 AM occur twice.
// March 8, 2026 — US DST spring forward
// 2:30 AM does not exist in America/New_York
Bug 3: Assuming UTC offset is constant
// ❌ Istanbul is UTC+3 in summer AND winter (since 2016)
// ❌ But New York switches between UTC-5 (EST) and UTC-4 (EDT)
// Never hardcode offsets — use the IANA database
Date Formatting — ISO 8601
ISO 8601 is the international standard for date and time representation:
| Format | Example | Use case |
|---|---|---|
| Date only | 2026-03-17 | Database date fields |
| Date + Time (UTC) | 2026-03-17T14:30:00Z | API responses, logs |
| Date + Time + Offset | 2026-03-17T14:30:00+03:00 | User-facing timestamps |
| Duration | P1Y2M3DT4H5M6S | Time intervals |
The Z suffix means UTC (Zulu time). Always prefer ISO 8601 in APIs for unambiguous parsing across all languages and platforms.
JavaScript Date Formatting
// ISO 8601
new Date().toISOString(); // "2026-03-17T14:30:00.000Z"
// Localized display
new Date().toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric',
timeZone: 'America/New_York'
}); // "March 17, 2026"
// Intl.DateTimeFormat for full control
new Intl.DateTimeFormat('tr-TR', {
dateStyle: 'full', timeStyle: 'short',
timeZone: 'Europe/Istanbul'
}).format(new Date()); // "17 Mart 2026 Salı 17:30"
Cron Expressions
Cron expressions schedule recurring tasks. The standard format uses 5 fields:
┌───────── minute (0-59)
│ ┌───────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌───────── month (1-12)
│ │ │ │ ┌───────── day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * *
Common Patterns
| Expression | Meaning |
|---|---|
* * * * * | Every minute |
0 * * * * | Every hour (at minute 0) |
0 0 * * * | Every day at midnight |
0 9 * * 1-5 | Weekdays at 9:00 AM |
*/15 * * * * | Every 15 minutes |
0 0 1 * * | First day of every month |
0 */6 * * * | Every 6 hours |
0 0 * * 0 | Every Sunday at midnight |
Special Characters
*— Every value,— List:1,3,5= Monday, Wednesday, Friday-— Range:1-5= Monday through Friday/— Step:*/15= every 15th value?— No specific value (some implementations)
Cron and DST
Cron jobs scheduled during DST transitions can skip or run twice:
- Spring forward (2:00 AM → 3:00 AM): Jobs at 2:30 AM are skipped
- Fall back (3:00 AM → 2:00 AM): Jobs at 2:30 AM may run twice
Solution: Run critical cron jobs in UTC: TZ=UTC 0 2 * * *
Parse any cron expression with our Cron Expression Parser.
Best Practices
Storage
- Always store UTC — Convert to local time only at display time.
- Use ISO 8601 strings or Unix timestamps — never store formatted strings like “March 17, 2026”.
- Store timezone separately — If you need to know the user’s timezone, store the IANA identifier alongside the UTC timestamp.
APIs
- Return ISO 8601 with Z suffix —
"2026-03-17T14:30:00Z"is unambiguous. - Accept flexible input — Parse multiple formats, but always normalize to UTC internally.
- Document your timestamp format — Specify whether you use seconds or milliseconds.
Scheduling
- Use UTC for cron — Avoid DST-related skips and duplicates.
- Test edge cases — February 29, DST transitions, year boundaries, the year 2038.
- Use NTP — Ensure your server clocks are synchronized via Network Time Protocol.
Language-Specific Tips
JavaScript
Date.now()returns milliseconds — divide by 1000 for seconds.new Date(string)parsing is implementation-dependent — always use ISO 8601 format.- Use
Intl.DateTimeFormatfor localized display, not string concatenation. - Consider
TemporalAPI (Stage 3 proposal) for future-proof date handling.
Python
datetime.utcnow()is deprecated in Python 3.12+ — usedatetime.now(timezone.utc).time.time()returns seconds as a float — the decimal part is sub-second precision.- Use
zoneinfomodule (Python 3.9+) instead ofpytzfor timezone handling.
SQL
- Use
TIMESTAMP WITH TIME ZONE(PostgreSQL) orDATETIME(MySQL) — neverVARCHAR. NOW()returns server-local time — useNOW() AT TIME ZONE 'UTC'orUTC_TIMESTAMP().
Further Reading
- Unix Timestamp Converter — Convert between timestamps and human-readable dates
- Cron Expression Parser — Parse and validate cron schedules with next run times