dimayurkovski/globaltimekit
Lightweight NTP client for Swift. Get accurate server time on Apple platforms, immune to manual clock changes.
Features
- 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
Requirements
- iOS 16.0+
- macOS 13.0+
- tvOS 16.0+
- watchOS 9.0+
Installation
Swift Package Manager
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.
CocoaPods
Add GlobalTimeKit to your Podfile:
pod 'GlobalTimeKit'Usage
Quick Start
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.nowOne-Shot Query
If you just need the server time once without caching:
let serverTime = try await GlobalTimeClient().fetchTime()Custom Server
let config = GlobalTimeConfig(
server: "time.google.com",
timeout: .seconds(10),
samples: 6
)
let client = GlobalTimeClient(config: config)
try await client.sync()Check Sync Status
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")
}Auto-Syncing Client
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()Completion Handler API
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)")
}
}Error Handling
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")
}
}GMT Formatting
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.
How It Works
GlobalTimeKit sends a UDP packet to an NTP server and calculates the clock offset using the standard NTP formula:
offset = ((T2 - T1) + (T3 - T4)) / 2Where 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.
API Reference
GlobalTimeClientProtocol
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 |
GlobalTimeAutoClient
Takes a single GlobalTimeAutoConfig parameter (defaults to .init()).
GlobalTimeAutoConfig
| 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 |
GlobalTimeClient
| 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 |
GlobalTimeConfig
| 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 |
GlobalTimeError
| 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 |
License
GlobalTimeKit is available under the MIT license. See the LICENSE file for more information.
Package Metadata
Repository: dimayurkovski/globaltimekit
Default branch: main
README: README.md