What is "a date"?
A calendar day — birthday, civil date, contextual — and why it isn’t a point in time.
A date is a civil calendar day: a (year, month, day) triple in some calendar system, such as 2026-06-05 in the Gregorian calendar. It is emphatically NOT a point in time. June 5 begins at midnight, but midnight happens at different absolute instants in different time zones — so the same date corresponds to a 48-hour-wide band of possible instants when you span the entire globe.
This matters for any date that is inherently human-centric rather than physics-centric. A birthday, an anniversary, a public holiday, or a contract expiry date is a civil date: it belongs to a calendar, not to an absolute position on the timeline. If you fly from Tokyo to Los Angeles, you do not retroactively shift your birthday by -17 hours.
A type that models this correctly stores only the calendar fields — no time, no zone:
// Temporal has a real date-only type — no time, no zone:
const d = Temporal.PlainDate.from('2026-06-17');
d.toString(); // '2026-06-17'
// Legacy Date has no date-only type — it's always an instant.
// Best effort: build AND read in one zone (UTC) so the day can't drift:
const legacy = new Date(Date.UTC(2026, 5, 17)); // month is 0-indexed
legacy.getUTCDate(); // 17 — always read with getUTC*()JS examples use Temporal — standardized in ES2026, available via a polyfill today.
from datetime import date
d = date(2026, 6, 17) # a real date-only type — no time, no zone
d.isoformat() # '2026-06-17'import java.time.LocalDate;
LocalDate d = LocalDate.parse("2026-06-17"); // a real date-only type — no time, no zone
d.toString(); // '2026-06-17'
// Legacy java.util.Date has no date-only type — it's always an instant, and
// its deprecated constructor counts years from 1900 and months from 0:
java.util.Date legacy = new java.util.Date(126, 5, 17); // "126, 5" means 2026-06 — in the host's zoneJava examples use java.time (JSR-310) — see the Java page for the legacy java.util.Date minefield.
import kotlinx.datetime.LocalDate
val d = LocalDate.parse("2026-06-17") // a real date-only type — no time, no zone
d.toString() // '2026-06-17'
// Inherited java.util.Date has no date-only type — it's always an instant, and
// its deprecated constructor counts years from 1900 and months from 0:
@Suppress("DEPRECATION")
val legacy = java.util.Date(126, 5, 17) // "126, 5" means 2026-06 — in the host's zoneKotlin examples use kotlinx-datetime, JetBrains’ library — separate from the stdlib.
import Foundation
// Foundation has no date-only type — Date is always an instant. The calendar
// fields live in DateComponents; realizing them invents a midnight:
let fields = DateComponents(year: 2026, month: 6, day: 17) // just (y, m, d)
var cal = Calendar(identifier: .gregorian)
cal.timeZone = TimeZone(identifier: "UTC")!
cal.date(from: fields)! // the instant 2026-06-17T00:00:00Z — zone chosen by youSwift examples use Foundation’s Calendar/DateComponents — see the Swift page.
import (
"fmt"
"time"
)
// Go has no date-only type — time.Time is always an instant. The convention
// is midnight in a zone YOU choose (here UTC):
d := time.Date(2026, time.June, 17, 0, 0, 0, 0, time.UTC)
fmt.Println(d.Format(time.DateOnly)) // 2026-06-17Go examples use the standard library’s time package — see the Go page.
use jiff::civil::Date;
let d: Date = "2026-06-17".parse()?; // a real date-only type — no time, no zone
println!("{d}"); // 2026-06-17
// std has no calendar types at all — std::time::SystemTime is always an instant.Rust examples use jiff, a third-party crate — Rust’s std has no calendar types at all.
#include <stdio.h>
#include <time.h>
// C has no date-only type. struct tm is a full civil breakdown whose year
// counts from 1900 and month from 0 — the origin of the legacy quirks that
// JavaScript's Date and java.util.Date inherited:
struct tm d = {0};
d.tm_year = 2026 - 1900; // 126 == the year 2026
d.tm_mon = 6 - 1; // 5 == June (0 == January)
d.tm_mday = 17;
char buf[16];
strftime(buf, sizeof buf, "%Y-%m-%d", &d);
printf("%s\n", buf); // 2026-06-17C examples use <time.h> — see the C page. Standard C has no zone-aware or date-only type; the zone is a process-global set by TZ.
#include <chrono>
#include <format>
#include <iostream>
using namespace std::chrono;
// C++20 has a real date-only type. It's a field struct, so it can hold an
// invalid date — .ok() reports validity:
year_month_day d{June/17/2026y};
std::cout << std::format("{} ok={}", d, d.ok()) << "\n"; // 2026-06-17 ok=trueC++ examples use std::chrono (C++20) — see the C++ page. The pre-C++20 world lived on C’s <time.h>.
“Today” depends on who is asking
“Today” is observer-relative. A server running in UTC may report a different calendar date than the user’s browser sitting in UTC−11. Any feature that displays or operates on “today’s date” must use the user’s local date, not the server’s UTC date.
Pitfall: Storing a date as a UTC midnight timestamp (e.g. 2026-06-05T00:00:00Z) and then deriving the calendar day by converting to local time. In a timezone west of UTC, midnight UTC on June 5 is still June 4 locally, so your stored “date” and your displayed date diverge.
See also: Instant vs civil time and Points vs periods.
Go deeper: date arithmetic vs instant arithmetic
Subtracting two dates gives a count of whole calendar days, which is entirely distinct from subtracting two instants.
2026-06-06 − 2026-06-05 = 1 day— always, regardless of DST.2026-06-06T00:00:00 Europe/London − 2026-06-05T00:00:00 Europe/London— this is also 24 hours in summer, because no DST transition falls between these two midnights — but see DST & the edges for how DST can make a calendar day only 23 or 25 hours long.
Languages and databases expose separate types for these two operations. Python’s datetime.date vs datetime.datetime, PostgreSQL’s date vs timestamp with time zone, and JavaScript’s legacy Date (always an instant) vs Temporal.PlainDate are all expressions of this distinction.
For the deeper reason a date is not an instant, see Instant vs civil time and Time zones vs offsets.