Lightweight NTP client for Swift. Get accurate server time on Apple platforms, immune to manual clock changes.
In many applications it's critical to have a reliable timestamp — for token generation, certificate validation, or time-sensitive logic. Date() returns the device's system clock, which users can change at any time. GlobalTimeKit solves this by querying NTP servers and caching the offset, so you always get the real time.
- Zero dependencies — only Foundation + Network.framework
- async/await as the primary API, with completion handler wrappers
- Thread-safe — fully
Sendable, safe to use from any thread or actor - Monotonic clock — cached offset is immune to manual clock changes
- Multi-sample sync — collects multiple NTP samples, picks the most accurate one
- NTP v4 — implements RFC 5905 client mode
- Swift 5 & 6 — strict concurrency safe
- iOS 16.0+
- macOS 13.0+
- tvOS 16.0+
- watchOS 9.0+
Add GlobalTimeKit to your Package.swift:
dependencies: [
.package(url: "https://github.com/dimayurkovski/GlobalTimeKit.git", from: "1.0.0")
]Or in Xcode: File → Add Package Dependencies and paste the repository URL.
Add GlobalTimeKit to your Podfile:
pod 'GlobalTimeKit'import GlobalTimeKit
let client = GlobalTimeClient()
// Sync with NTP server (collects 4 samples by default)
try await client.sync()
// Get corrected time instantly — no await, no network
let now = client.nowIf you just need the server time once without caching:
let serverTime = try await GlobalTimeClient().fetchTime()let config = GlobalTimeConfig(
server: "time.google.com",
timeout: .seconds(10),
samples: 6
)
let client = GlobalTimeClient(config: config)
try await client.sync()if client.isSynced {
print("Offset: \(client.offset) seconds")
print("Last sync: \(client.lastSyncDate!)")
print("Server time: \(client.now)")
} else {
print("Not synced, using system time")
}GlobalTimeAutoClient wraps GlobalTimeClient and automatically re-syncs when the network is restored or the app returns to the foreground:
let client = GlobalTimeAutoClient()
// First sync is explicit, as usual
try await client.sync()
// From now on, re-syncs happen automatically:
// — when network connectivity is restored
// — when the app becomes active (foreground)
// — with exponential backoff retry on failure
let now = client.nowCustom configuration:
let client = GlobalTimeAutoClient(config: GlobalTimeAutoConfig(
server: "time.google.com",
maxRetries: 5,
retryBaseDelay: .seconds(2),
alwaysReactToEvents: true
))By default, auto re-sync only triggers after the first successful sync. Set alwaysReactToEvents: true to react to network and foreground events even before the first sync.
Both GlobalTimeClient and GlobalTimeAutoClient conform to GlobalTimeClientProtocol, so they can be used interchangeably:
let client: any GlobalTimeClientProtocol = GlobalTimeAutoClient()For projects that don't use async/await:
client.sync { result in
switch result {
case .success:
print("Synced! Offset: \(client.offset)")
case .failure(let error):
print("Error: \(error)")
}
}
client.fetchTime { result in
switch result {
case .success(let date):
print("Server time: \(date)")
case .failure(let error):
print("Error: \(error)")
}
}do {
try await client.sync()
} catch let error as GlobalTimeError {
switch error {
case .timeout:
print("Request timed out")
case .invalidResponse:
print("Invalid NTP response")
case .dnsResolutionFailed:
print("Could not resolve server hostname")
case .networkUnavailable:
print("No network connection")
case .serverUnreachable:
print("NTP server is unreachable")
}
}Get corrected time in GMT timezone, perfect for API requests and JWT tokens:
// Unix timestamp (seconds since 1970-01-01 UTC)
let timestamp = client.unixTimestamp
// Example: 1710598800.0
// ISO 8601 format
let iso = client.iso8601GMT
// Example: "2026-03-16T14:30:00Z"
// Custom format
let custom = client.formattedGMT("dd/MM/yyyy HH:mm")
// Example: "16/03/2026 14:30"All formatting methods use en_US_POSIX locale and Gregorian calendar for consistent, locale-independent results.
GlobalTimeKit sends a UDP packet to an NTP server and calculates the clock offset using the standard NTP formula:
offset = ((T2 - T1) + (T3 - T4)) / 2
Where T1–T4 are the four timestamps from the NTP exchange. The offset is cached locally, and client.now returns Date() + offset — no network call needed after sync.
Multiple samples are collected and the one with the lowest round-trip delay is selected for maximum accuracy.
Both GlobalTimeClient and GlobalTimeAutoClient conform to this protocol.
| Property / Method | Description |
|---|---|
sync() |
Sync with NTP server, cache the offset |
fetchTime() |
One-shot query, returns server time without caching |
now |
Corrected time using cached offset (falls back to Date()) |
unixTimestamp |
Unix timestamp in GMT (seconds since 1970-01-01 UTC) |
iso8601GMT |
ISO 8601 formatted time in GMT timezone |
formattedGMT(_:) |
Custom formatted time in GMT timezone |
isSynced |
Whether the client has synced at least once |
offset |
Cached NTP offset in seconds |
lastSyncDate |
Date of last successful sync |
Takes a single GlobalTimeAutoConfig parameter (defaults to .init()).
| Parameter | Default | Description |
|---|---|---|
server |
"time.apple.com" |
NTP server hostname |
timeout |
.seconds(5) |
Timeout for a single NTP request |
samples |
4 |
Number of NTP samples to collect |
logLevel |
.info |
Diagnostic log verbosity |
maxRetries |
3 |
Max retry attempts after a failed auto re-sync |
retryBaseDelay |
.seconds(2) |
Initial retry delay; doubles with each attempt |
alwaysReactToEvents |
false |
Re-sync on events even before the first successful sync |
| Property / Method | Description |
|---|---|
init(config:) |
Create a client with optional configuration |
sync() |
Sync with NTP server, cache the offset |
fetchTime() |
One-shot query, returns server time without caching |
now |
Corrected time using cached offset (falls back to Date()) |
unixTimestamp |
Unix timestamp in GMT (seconds since 1970-01-01 UTC) |
iso8601GMT |
ISO 8601 formatted time in GMT timezone |
formattedGMT(_:) |
Custom formatted time in GMT timezone |
isSynced |
Whether the client has synced at least once |
offset |
Cached NTP offset in seconds |
lastSyncDate |
Date of last successful sync |
| Parameter | Default | Description |
|---|---|---|
server |
"time.apple.com" |
NTP server hostname |
timeout |
.seconds(5) |
Timeout for a single NTP request |
samples |
4 |
Number of NTP samples to collect |
logLevel |
.info |
Diagnostic log verbosity |
| Case | Description |
|---|---|
.timeout |
Request timed out |
.invalidResponse |
Server returned an invalid NTP packet |
.dnsResolutionFailed |
Could not resolve server hostname |
.networkUnavailable |
No network connection |
.serverUnreachable |
NTP server is unreachable |
GlobalTimeKit is available under the MIT license. See the LICENSE file for more information.