What does a negative Unix timestamp mean, and where does code break on one?
By TechCompare · Updated · How we check
A negative Unix timestamp counts seconds before the Unix epoch, and the number itself is valid. The bugs come from the code around it: fixed-width fields, MySQL returning 0 for out of range dates, and division that truncates toward zero.
Timestamp -14182940 is July 20, 1969 at 20:17:40 UTC, a Sunday, the moment the Apollo 11 lunar module landed. The minus sign is all it takes to put a timestamp before the Unix epoch. Problems show up when a column, a library or a calculation can't handle a value below zero.
How this is calculated
The example is the Apollo 11 landing. NASA's National Space Science Data Center says "The LM landed at 20:17:40 UT", and NASA's mission timeline puts the same event within a second of that, so the exact second depends on rounding. This page uses that rounded second. To build the timestamp in JavaScript, use Date.UTC, which returns milliseconds since the epoch, so divide by 1000. Its month argument starts at 0, so 6 is July.
const seconds = Date.UTC(1969, 6, 20, 20, 17, 40) / 1000
console.log(seconds) // -14182940JavaScript dates sit on both sides of the epoch. MDN says a Date can represent "±8,640,000,000,000,000 milliseconds, or ±100,000,000 (one hundred million) days, relative to the epoch." Plenty of other systems are less forgiving.
Fixed width types are the first trap. An unsigned type can't hold a negative number at all. A signed 32-bit count starts at -2,147,483,648, which is December 13, 1901 at 20:45:52 UTC, and nothing earlier fits. A 64-bit count reaches about 292 billion years in either direction. The other end of the 32-bit range has its own page: Year 2038 problem.
Python is the next place it breaks. The Python docs say fromtimestamp() may raise OverflowError when the value is outside what the platform's C functions support, and that it's common for this to be restricted to years 1970 through 2038. Starting from the epoch and adding a timedelta avoids that. The code below builds a datetime with a UTC time zone and prints the same moment as the example:
from datetime import datetime, timedelta, timezone
moment = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=-14182940)
print(moment.strftime('%Y-%m-%dT%H:%M:%SZ')) # 1969-07-20T20:17:40ZMySQL fails quietly. `UNIX_TIMESTAMP()` only accepts dates starting at 00:00:01 UTC on January 1, 1970, and it returns 0 for any out of range date. That 0 reads as January 1, 1970, so a bad value can pass for a real one. PostgreSQL handles it: `EXTRACT(EPOCH ...)` returns negative numbers for timestamps before the epoch.
The last trap is division. The Go spec says integer division is "truncated towards zero", so dividing negative milliseconds by 1000 rounds toward zero instead of down. This Go code prints both results, in seconds:
package main
import (
"fmt"
"math"
)
func main() {
ms := int64(-1500)
fmt.Println(ms / 1000) // -1, which is 1969-12-31T23:59:59.000Z
fmt.Println(int64(math.Floor(float64(ms) / 1000))) // -2, which is 1969-12-31T23:59:58.000Z
}The first line prints -1, one second after the second that holds the value. The second line prints -2, the second that starts at 1969-12-31T23:59:58.000Z and contains -1500 milliseconds. Python's // and JavaScript's Math.floor round down like the second line.
Fractions follow the same rule. A timestamp of -1.5 is half a second after December 31, 1969 at 23:59:58 UTC, so truncating toward zero gives 23:59:59 instead. For the spreadsheet version of this conversion, see Unix timestamp in Excel and Google Sheets.
Verdict
Test one negative value in every system the timestamp passes through, from the database to the front end, before you ship. Store it in a 64-bit integer, check that the database returns a real date instead of 0, and floor negative millisecond values before you divide them. In Python, build the date from the epoch with a timedelta rather than relying on fromtimestamp.
More Timestamp scenarios
Related guides
Frequently asked questions
Can a Unix timestamp be negative?
getTime() returns negative values for earlier times, so JavaScript dates can sit on either side of the epoch.What is the earliest date a 32-bit Unix timestamp can store?
How do I get the Unix timestamp for a date before 1970?
Date.UTC(1969, 6, 20, 20, 17, 40) / 1000 gives -14182940. In Python, build an aware datetime like the moment above and call int(moment.timestamp()), which also gives -14182940.Why does my negative timestamp fail or return 0?
UNIX_TIMESTAMP(), check for 0. That function returns 0 for an out of range date, and that 0 reads as January 1, 1970, so the bad value looks like a real date.Why is my negative millisecond timestamp one second off?
ms / 1000 with ms set to -1500 gives -1, not -2. Use a floor instead: math.Floor in Go, Math.floor in JavaScript or // in Python. Each of those gives -2, the second that contains the value.Related tools
JSON Formatter
Validate, format, and minify JSON. Browse it as a tree, run JSONPath queries, compare two documents, or validate against a JSON Schema.
Use toolCron Generator
Visually build standard 5-part cron expressions or translate them into readable schedules.
Use toolText Encoding Converter
Convert text to Base64, binary, hex, URL encoding and HTML entities, and back again.
Use tool