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

Swift
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

Swift
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.

Swift
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
ISO 8601 (iOS/macOS)
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.

Swift
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

Swift
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.