Convert Unix Timestamp in Swift
Swift uses Foundation's Date type for timestamps. A Date is an absolute point in time; timezone only matters when you format a date for display or when you construct a date from date components.
Unix Timestamp to Date
import Foundation // From seconds (standard Unix timestamp) let timestampSeconds: TimeInterval = 1_704_067_200 let date = Date(timeIntervalSince1970: timestampSeconds) print(date) // Absolute time (prints in a timezone-dependent way) // From milliseconds (common in JavaScript) let timestampMs: TimeInterval = 1_704_067_200_000 let dateFromMs = Date(timeIntervalSince1970: timestampMs / 1000) print(dateFromMs)
Date to Unix Timestamp
import Foundation // Current timestamp (seconds, Double) let nowSeconds = Date().timeIntervalSince1970 print(nowSeconds) // e.g., 1704067200.123 // Current timestamp (integer seconds) let nowIntSeconds = Int(Date().timeIntervalSince1970) print(nowIntSeconds) // Current timestamp (milliseconds) let nowMs = Int(Date().timeIntervalSince1970 * 1000) print(nowMs) // From a specific date let utc = TimeZone(secondsFromGMT: 0)! var cal = Calendar(identifier: .gregorian) cal.timeZone = utc let d = cal.date(from: DateComponents(year: 2024, month: 1, day: 1, hour: 0, minute: 0, second: 0))! print(Int(d.timeIntervalSince1970)) // 1704067200
Formatting and Parsing with DateFormatter
DateFormatter is flexible for custom formats, but be explicit about locale and timeZone for predictable results.
import Foundation let date = Date(timeIntervalSince1970: 1_704_067_200) // 2024-01-01T00:00:00Z let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(secondsFromGMT: 0) formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" let str = formatter.string(from: date) print(str) // "2024-01-01 00:00:00" let parsed = formatter.date(from: "2024-01-01 00:00:00") print(Int(parsed!.timeIntervalSince1970)) // 1704067200
import Foundation let iso = ISO8601DateFormatter() iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds] iso.timeZone = TimeZone(secondsFromGMT: 0) let date = Date(timeIntervalSince1970: 1_704_067_200) print(iso.string(from: date)) // "2024-01-01T00:00:00.000Z"
Calendar and DateComponents
Use Calendar and DateComponents when working with human units (year/month/day) or when constructing a date in a specific timezone.
import Foundation let utc = TimeZone(secondsFromGMT: 0)! var cal = Calendar(identifier: .gregorian) cal.timeZone = utc // Build a Date from components in UTC let components = DateComponents(year: 2024, month: 1, day: 1, hour: 0, minute: 0, second: 0) let date = cal.date(from: components)! print(Int(date.timeIntervalSince1970)) // 1704067200 // Extract components (also in UTC here) let parts = cal.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date) print(parts.year!, parts.month!, parts.day!)
Timezone Handling
import Foundation let date = Date(timeIntervalSince1970: 1_704_067_200) // Same Date, different display timezones let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZZ" formatter.timeZone = TimeZone(secondsFromGMT: 0) print(formatter.string(from: date)) // "2024-01-01 00:00:00 GMT" formatter.timeZone = TimeZone(identifier: "America/New_York") print(formatter.string(from: date)) // "2023-12-31 19:00:00 GMT-05:00" formatter.timeZone = TimeZone(identifier: "Asia/Tokyo") print(formatter.string(from: date)) // "2024-01-01 09:00:00 GMT+09:00"
Best Practice
Store and transmit timestamps in UTC (seconds since epoch). Apply timezones only at the edges (UI and reports).
iOS/macOS Considerations
DateFormatter Performance and Thread Safety
DateFormatter is relatively expensive to create and is not thread-safe. Cache formatters per thread/actor (or use ISO8601DateFormatter / Date.FormatStyle where appropriate).
User Settings Affect Formatting
Calendar.current, TimeZone.current, andLocale.current depend on device settings. For logs and network protocols, set locale/timezone explicitly.
Common Pitfalls
Seconds vs Milliseconds
Swift expects seconds in Date(timeIntervalSince1970:). If you pass milliseconds directly, you'll end up thousands of years in the future.
import Foundation let ms: TimeInterval = 1_704_067_200_000 // ❌ Wrong (treating ms as seconds) let wrong = Date(timeIntervalSince1970: ms) // ✅ Correct let correct = Date(timeIntervalSince1970: ms / 1000)
Parsing Without en_US_POSIX
If you parse fixed-format strings without a stable locale, parsing can break on devices with different regional settings.
let f = DateFormatter() // ❌ Risky for fixed formats // f.locale = .current // ✅ Stable for machine-readable formats f.locale = Locale(identifier: "en_US_POSIX")
DST and "Midnight" Bugs
Constructing dates at midnight in a local timezone can behave unexpectedly around DST transitions. If you need day-based calculations, do them with a Calendar in a known timezone (often UTC) and store results as timestamps.
Frequently Asked Questions
Does Swift use seconds or milliseconds for Unix timestamps?
Swift's Date APIs use seconds. Date().timeIntervalSince1970 returns seconds as a Double. If your timestamp is in milliseconds (common in JavaScript), divide by 1000 before converting to Date.
Why is my converted date off by a few hours?
You are likely formatting in the device's local timezone. Date represents an absolute moment; the timezone is applied when formatting or when you build a Date from date components. Set DateFormatter.timeZone or Calendar.timeZone explicitly to avoid surprises.
Should I use DateFormatter or ISO8601DateFormatter?
Use ISO8601DateFormatter for ISO 8601 timestamps (e.g., 2024-01-01T00:00:00Z). Use DateFormatter for custom formats. Always set a stable locale (en_US_POSIX) when parsing fixed-format strings.
Is DateFormatter thread-safe on iOS/macOS?
No. DateFormatter is not thread-safe. Create separate instances per thread/task, or use an actor/serial queue, or prefer ISO8601DateFormatter/FormatStyle where it fits.