Introduction
A datetime.date object represents a calendar date without a time or timezone. Converting it to a UTC timestamp (seconds since 1970-01-01 00:00:00 UTC) requires deciding what time of day and what timezone the date represents. The standard approach is to treat the date as midnight UTC, combine it with time(0, 0) and timezone.utc, then call .timestamp(). Getting this wrong is the most common source of timestamp bugs in Python.
The Direct Approach (Python 3.3+)
1from datetime import date, datetime, timezone
2
3d = date(2025, 9, 24)
4
5# Combine with midnight UTC, then get timestamp
6dt = datetime.combine(d, datetime.min.time(), tzinfo=timezone.utc)
7timestamp = dt.timestamp()
8print(timestamp) # 1758672000.0
9
10# Verify round-trip
11print(datetime.fromtimestamp(timestamp, tz=timezone.utc).date())
12# 2025-09-24
Why date Alone Is Ambiguous
1from datetime import date, datetime, timezone
2
3d = date(2025, 6, 15)
4
5# Without timezone: assumes LOCAL timezone
6naive = datetime.combine(d, datetime.min.time())
7ts_local = naive.timestamp()
8
9# With UTC timezone: midnight UTC
10aware = datetime.combine(d, datetime.min.time(), tzinfo=timezone.utc)
11ts_utc = aware.timestamp()
12
13print(f"Local timestamp: {ts_local}") # Depends on your system timezone
14print(f"UTC timestamp: {ts_utc}") # Always the same everywhere
15
16# The difference equals your UTC offset in seconds
17print(f"Difference: {ts_local - ts_utc} seconds")
18# e.g., -18000.0 for US Eastern (UTC-5)
datetime.timestamp() on a naive datetime assumes local time. This means the same code produces different timestamps on different machines.
Using calendar.timegm()
1import calendar
2from datetime import date
3
4d = date(2025, 9, 24)
5
6# timegm() treats the input as UTC (unlike mktime which uses local time)
7timestamp = calendar.timegm(d.timetuple())
8print(timestamp) # 1758672000
calendar.timegm() is the UTC equivalent of time.mktime(). It always interprets the input as UTC regardless of the system timezone.
Using time.mktime() (Local Time, Usually Wrong)
1import time
2from datetime import date
3
4d = date(2025, 9, 24)
5
6# mktime() interprets the input in the LOCAL timezone
7timestamp = time.mktime(d.timetuple())
8print(timestamp) # Result depends on system timezone
time.mktime() converts local time to a timestamp. If you want UTC, do not use this. Use calendar.timegm() instead.
Integer vs Float Timestamps
1from datetime import date, datetime, timezone
2
3d = date(2025, 9, 24)
4dt = datetime.combine(d, datetime.min.time(), tzinfo=timezone.utc)
5
6# Float timestamp (includes microseconds)
7ts_float = dt.timestamp()
8print(ts_float) # 1758672000.0
9print(type(ts_float)) # <class 'float'>
10
11# Integer timestamp
12ts_int = int(dt.timestamp())
13print(ts_int) # 1758672000
14
15# For millisecond timestamps (JavaScript-style)
16ts_ms = int(dt.timestamp() * 1000)
17print(ts_ms) # 1758672000000
Converting Back (Timestamp to date)
1from datetime import datetime, timezone
2
3timestamp = 1758672000
4
5# Always specify UTC when converting back
6dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
7d = dt.date()
8print(d) # 2025-09-24
9
10# Without tz, fromtimestamp uses LOCAL timezone
11# This may give a different date near midnight!
12dt_local = datetime.fromtimestamp(timestamp)
13print(dt_local.date()) # Could be 2025-09-23 in western timezones
With Timezone-Aware Dates
1from datetime import date, datetime, time
2from zoneinfo import ZoneInfo # Python 3.9+
3
4d = date(2025, 9, 24)
5
6# Midnight in New York
7ny_midnight = datetime.combine(d, time(0, 0), tzinfo=ZoneInfo("America/New_York"))
8print(ny_midnight.timestamp()) # 1758686400.0 (4 hours after UTC midnight)
9
10# Midnight in Tokyo
11tokyo_midnight = datetime.combine(d, time(0, 0), tzinfo=ZoneInfo("Asia/Tokyo"))
12print(tokyo_midnight.timestamp()) # 1758639600.0 (9 hours before UTC midnight)
13
14# Midnight in UTC
15utc_midnight = datetime.combine(d, time(0, 0), tzinfo=ZoneInfo("UTC"))
16print(utc_midnight.timestamp()) # 1758672000.0
The same calendar date corresponds to different UTC timestamps depending on the timezone.
Pandas Integration
1import pandas as pd
2
3# Date to timestamp
4d = pd.Timestamp("2025-09-24", tz="UTC")
5print(d.timestamp()) # 1758672000.0
6
7# Series of dates to timestamps
8dates = pd.to_datetime(["2025-09-24", "2025-09-25", "2025-09-26"])
9dates_utc = dates.tz_localize("UTC")
10timestamps = dates_utc.astype("int64") // 10**9 # Nanoseconds to seconds
11print(timestamps.values) # [1758672000 1758758400 1758844800]
Epoch Reference
1from datetime import date, datetime, timezone
2
3# The Unix epoch
4epoch = date(1970, 1, 1)
5dt = datetime.combine(epoch, datetime.min.time(), tzinfo=timezone.utc)
6print(dt.timestamp()) # 0.0
7
8# Dates before the epoch produce negative timestamps
9old_date = date(1969, 7, 20) # Moon landing
10dt = datetime.combine(old_date, datetime.min.time(), tzinfo=timezone.utc)
11print(dt.timestamp()) # -14182400.0
Common Pitfalls
Naive datetime assumes local time: datetime.combine(d, time()).timestamp() uses the system's local timezone, not UTC. Always pass tzinfo=timezone.utc explicitly.
Date boundary errors: A timestamp of 1758672000 (midnight UTC Sep 24) becomes Sep 23 at 7 PM in US Eastern. Always specify UTC when converting timestamps back to dates.
datetime.utcnow() is not UTC-aware: Despite the name, datetime.utcnow() returns a naive datetime. Use datetime.now(timezone.utc) instead (Python 3.2+).
DST ambiguity: During the fall-back DST transition, the same local time occurs twice. Using mktime() during this period may produce the wrong timestamp. UTC has no DST.
32-bit timestamp overflow: Timestamps stored as 32-bit integers overflow on January 19, 2038. Use 64-bit integers or Python's arbitrary-precision int.
Summary
Use datetime.combine(d, time(), tzinfo=timezone.utc).timestamp() for date-to-UTC-timestamp
Use calendar.timegm(d.timetuple()) as an alternative that always treats input as UTC
Never use time.mktime() for UTC conversions because it uses local time
Always pass tz=timezone.utc to datetime.fromtimestamp() when converting back
The same calendar date maps to different timestamps in different timezones
Use int(ts * 1000) for millisecond timestamps (JavaScript/Java-style)