Time & Date in Programming: The Complete Developer Guide
Time handling is one of the most bug-prone areas in software, and it’s deceptively complex.
Consider a Node.js cron script that runs a report “every morning at 8:00 AM.” It deploys fine and works for six months. Then one Sunday in November it runs twice and double-charges a customer. In March it doesn’t run at all. Users in London get the report an hour late, and users in Tokyo get the wrong day’s data.
The root cause: human time is a political construct, not a mathematical constant.
Timezone offsets shift with Daylight Saving Time. Governments change their timezone rules with little warning. Leap seconds get inserted to account for the Earth’s slowing rotation. In 2011, Samoa skipped Friday, December 30th entirely — jumping from Thursday to Saturday — to align its trading week with Australia.
To work in backend APIs, distributed databases, or frontend localization, you need to understand how systems actually track time. This guide covers the Unix epoch, the Year 2038 problem, the IANA timezone database, ISO 8601, and scheduling with cron.
Stop doing timezone math in your head. Convert epochs, inspect ISO 8601 strings, and calculate offsets with our local Unix Timestamp Converter.
1. The Unix Epoch
Before you format a date for a user, you need to know how the system stores it. Almost all modern systems (Linux servers, iOS phones) track time with the Unix timestamp (also called POSIX time or the Unix epoch).
A Unix timestamp is simple: it’s an integer counting the seconds elapsed since January 1, 1970, 00:00:00 UTC.
- It has no timezone.
- It ignores Daylight Saving Time.
- It only increases.
The seconds vs milliseconds trap
The most common timestamp bug in distributed systems is a unit mismatch. Languages default to different units for the epoch.
| Language | Function | Unit | Digits |
|---|---|---|---|
| JavaScript / Node.js | Date.now() | Milliseconds | 13 |
| Java | System.currentTimeMillis() | Milliseconds | 13 |
| Python | time.time() | Seconds (float) | 10 (before the decimal) |
| PHP | time() | Seconds | 10 |
| Go | time.Now().Unix() | Seconds | 10 |
| Ruby | Time.now.to_i | Seconds | 10 |
| C / C++ | time(NULL) | Seconds | 10 |
The bug: if a Python backend sends 1711324800 (10 digits, March 2024) to a React frontend and JavaScript parses it as milliseconds with new Date(1711324800), the user sees January 20, 1970.
The fix: document your API contracts. When passing a 10-digit second-based timestamp into JavaScript, multiply by 1,000 first: new Date(timestamp * 1000).
Negative timestamps and the Year 2038 problem
Because the epoch starts in 1970, earlier dates are negative integers.
0→ January 1, 1970 00:00:00 UTC-86400→ December 31, 1969 00:00:00 UTC
More importantly, many legacy systems (especially embedded hardware) store the timestamp as a 32-bit signed integer, which maxes out at 2,147,483,647.
On January 19, 2038 at 03:14:07 UTC, that integer overflows. Systems using 32-bit storage roll over to a large negative number and read the date as December 13, 1901.
This is the Y2K38 problem (the “Epochalypse”). It will cause failures in legacy databases, IoT devices, embedded firmware, and 32-bit routers.
The fix: use 64-bit integers (BIGINT in PostgreSQL/MySQL) for timestamp columns. A 64-bit integer pushes the overflow billions of years out.
2. Timezones
Timezones aren’t fixed offsets — they’re geopolitical regions that change with politics.
The IANA timezone database (tzdata)
Never use three-letter abbreviations like EST or CST in your code.
ESTcan mean UTC-5 (New York) but is also Eastern Standard Time in Australia (UTC+10).- New York is only
ESTfor half the year — in summer it’sEDT(UTC-4). - Hardcoding
-05:00is just as wrong, since the offset changes twice a year.
The safe way to handle timezones is with IANA timezone identifiers (America/New_York, Europe/London, Asia/Tokyo). These map to the constantly-updated open-source tzdata database, which tracks every historical, current, and planned offset change for each region.
Daylight Saving Time anomalies
DST advances clocks by an hour in warmer months, and it breaks naive time arithmetic.
- The missing hour (spring forward): when clocks jump from 2:00 AM to 3:00 AM, the 2:00–2:59 AM hour doesn’t exist that day. Schedule something for 2:30 AM and your code either errors or silently shifts to 3:30 AM.
- The doubled hour (fall back): when clocks fall from 3:00 AM to 2:00 AM, the 2:00–2:59 AM hour happens twice. Group analytics by hour and that hour holds twice the data, skewing your metrics.
The solution: never store or compute in local time. All backend logic, databases, and cron schedules should run in UTC. UTC doesn’t observe DST — it’s predictable. Convert to the user’s local timezone only at the last moment, during UI rendering.
3. ISO 8601 Formatting
When sending dates over JSON or GraphQL, use an unambiguous, machine-readable format. The standard is ISO 8601.
| Type | Example | Use case |
|---|---|---|
| Date only | 2026-03-17 | Birthdays, card expirations, billing cycles — when time of day doesn’t matter. |
| DateTime (UTC) | 2026-03-17T14:30:00.000Z | The default. Use for API payloads and DB storage. The Z means Zulu time (UTC). |
| DateTime (offset) | 2026-03-17T09:30:00-05:00 | When you need the user’s wall-clock time and their offset. |
| Duration | P1Y2M3DT4H5M6S | Intervals (1 year, 2 months, 3 days, 4 hours, 5 min, 6 sec). |
Formatting for users (the Intl API)
Frontend developers used to import 300KB+ libraries like Moment.js just to format dates. Moment.js is now deprecated.
Don’t concatenate strings (month + "/" + day + "/" + year) — different countries expect different formats (MM/DD/YYYY in the US, DD/MM/YYYY in the EU). Use the native Intl (Internationalization) API, which is built into modern browsers and Node.
const rawDatabaseTimestamp = "2026-03-17T14:30:00.000Z"; // Stored in UTC
// The browser translates the UTC string to the user's language and timezone
const formattedUIString = new Intl.DateTimeFormat('fr-FR', {
dateStyle: 'full',
timeStyle: 'short',
timeZone: 'Europe/Paris' // Force the Paris timezone
}).format(new Date(rawDatabaseTimestamp));
console.log(formattedUIString);
// Output: "mardi 17 mars 2026 à 15:30"
4. Scheduling with Cron
Cron is the Unix daemon for scheduling recurring tasks. It uses a 5-field, space-delimited syntax.
┌───────── minute (0-59)
│ ┌───────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌───────── month (1-12)
│ │ │ │ ┌───────── day of week (0-7, where 0 and 7 are Sunday)
│ │ │ │ │
* * * * *
Common patterns
* * * * *— every minute.*/15 * * * *— every 15 minutes (/is a step interval).0 * * * *— at the top of every hour.0 0 * * *— every day at midnight.0 9 * * 1-5— 9:00 AM, Monday through Friday (1-5is a range).
Cron and local timezones
By default, the cron daemon evaluates expressions against the server’s system clock. If your EC2 instance or dev machine is set to a local timezone (America/New_York), your cron jobs are exposed to the DST anomalies above.
Schedule a backup with 30 2 * * * (2:30 AM) and it won’t run on the spring-forward day in March, because 2:30 AM doesn’t exist.
The fix: set all your infrastructure to UTC (sudo timedatectl set-timezone UTC) and write your cron schedules in UTC.
Validate before it hits production: a typo in a cron expression can run a heavy query every minute instead of every month. Paste your expression into our Cron Expression Parser to read it in plain English and see the next 5 run times.
5. Storing Dates in SQL
Use the column types built for temporal data — never store dates as VARCHAR text.
- PostgreSQL: use
TIMESTAMPTZ(timestamp with time zone). Despite the name, Postgres doesn’t store the zone — it converts the incoming value to UTC, stores it as an 8-byte integer, and converts back to your session’s timezone onSELECT. - MySQL: use
DATETIME(stores exactly what you give it, so you must enforce UTC in your application) orTIMESTAMP(auto-converts to UTC internally, but is subject to the Year 2038 32-bit limit).
Further Reading
- Database Normalization: Indexing UUIDs vs Sequential ULIDs
- Environment Variables: Preventing Configuration Disasters
- The Complete Developer’s Guide to Encoding and Hashing
- Regex Mastery: Validating Dates and Emails
Handle temporal programming with confidence. Convert epochs and validate ISO strings with our Timestamp Converter, and debug recurring schedules with the visual Cron Parser.