Skip to content

dimayurkovski/GlobalTimeKit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GlobalTimeKit

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.

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

One-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.now

Custom 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)) / 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.

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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors