Swift
Foundation’s Date, Calendar/DateComponents, and the en_US_POSIX trap.
Swift’s standard library has no date and time types — they live in Foundation (import Foundation, now also available through the open-source swift-foundation). The cornerstone is Date: a single point in time stored as a TimeInterval (a Double of seconds) relative to a fixed reference instant, 2001-01-01T00:00:00 UTC. Crucially, a Date carries no time zone — it is an absolute instant, the equivalent of an “aware UTC” value, much like a Unix timestamp. Date() (or Date.now) gives you the current moment.
Because Date is purely an instant, the civil side — “what does a human read off the wall clock?” — is a separate concern handled by DateComponents (a bag of year/month/day/hour/… fields) together with a Calendar, which itself holds a TimeZone. You never convert between an instant and civil fields without naming a calendar and zone. See Instant vs civil time and Time zones vs offsets.
A Date names a point; a calendar day names a period. Foundation models the period explicitly with DateInterval (a start Date plus a duration), and calendar.dateInterval(of: .day, for: date) gives you the whole day a moment falls in. See Points vs periods.
The single biggest source of Swift date bugs is DateFormatter and the user’s locale. A formatter with a fixed format string ("yyyy-MM-dd") still uses the device’s locale and calendar by default — so for a user on a Buddhist or Japanese calendar, or a 12-hour clock, parsing and formatting silently produce wrong results. For machine-readable, fixed formats you must pin the locale to en_US_POSIX, or better, use ISO8601DateFormatter.
Date vs DateComponents
Since Date is an instant and DateComponents is the civil reading, the naive/aware confusion shows up at the conversion boundary rather than in arithmetic on a single value. Two hazards dominate: using Calendar.current / TimeZone.current when you meant a fixed business zone (results drift with device settings), and treating print(date) output as local — Date.description is always rendered in UTC, which routinely surprises people debugging in a non-UTC zone.
Pitfall: A DateFormatter with a fixed format string but no explicit locale uses the device locale and calendar. On a device set to a non-Gregorian calendar or 12-hour clock, parsing an ISO-style string fails or yields the wrong date. Set formatter.locale = Locale(identifier: "en_US_POSIX") for any fixed-format parsing, or use ISO8601DateFormatter. See parsing user input.
Pitfall: date.addingTimeInterval(86400) advances exact elapsed seconds, not civil time — across a spring-forward night it lands an hour off “same time tomorrow”. For DST-aware calendar arithmetic use calendar.date(byAdding: .day, value: 1, to: date) instead.
See also: Instant vs civil time and Parsing user input.
Go deeper: Calendar conversions, formatting, and DST-safe arithmetic
Instant ⇄ civil fields go through a Calendar + TimeZone:
import Foundation
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "Europe/Paris")!
let now = Date() // an absolute instant
// instant -> civil reading
let parts = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: now)
// civil reading -> instant (a nonexistent DST-gap time is silently resolved
// forward, not rejected — see the caveat below)
var c = DateComponents()
c.year = 2026; c.month = 6; c.day = 5; c.hour = 14
let instant = calendar.date(from: c)
Parsing and formatting. Prefer ISO8601DateFormatter for interchange and the modern Date.FormatStyle API for display:
// Machine interchange — fixed, locale-independent
let iso = ISO8601DateFormatter()
let d = iso.date(from: "2026-06-05T14:00:00Z")
// Legacy fixed-format parsing — MUST pin the locale
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
df.timeZone = TimeZone(identifier: "Europe/Paris")
// Display in the user's locale (iOS 15+ / recent Foundation)
let shown = Date().formatted(date: .long, time: .shortened)
Exact vs calendar arithmetic — the distinction behind pitfall #2:
let exact = now.addingTimeInterval(24 * 60 * 60) // exactly 24h later
let nextDay = calendar.date(byAdding: .day, value: 1, to: now) // "same time tomorrow", DST-aware
// Across the spring-forward night these are different instants.
(One caveat shared by every language: “same wall-clock time tomorrow” can land in a DST gap or fold, where the civil reading doesn’t exist or is ambiguous. calendar.date(from:) does not fail for a nonexistent time — it silently shifts forward past the gap, e.g. 02:30 on a US spring-forward night resolves to 03:30. Observed on both Apple’s Foundation and the open-source swift-foundation.)
If you want the compiler to hold the instant/civil line for you, the third-party Time library (Dave DeLong) models calendar values as distinct generic types — conceptually the closest thing Swift has to Temporal or kotlinx-datetime. Foundation used correctly covers most applications; reach for Time when a codebase keeps mixing up instants and civil values.
Note that Swift’s standard library Duration type (Swift 5.7+) is for high-precision elapsed-time measurement, not calendar arithmetic — reach for Calendar and DateComponents for anything civil.
See also Time zones vs offsets and DST.