hexogen/timesync

A simple time synchronization library for PHP.

Maintainers

Package info

github.com/hexogen/timesync

pkg:composer/hexogen/timesync

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.3.0 2026-03-11 20:24 UTC

This package is auto-updated.

Last update: 2026-03-11 20:30:33 UTC


README

Package Version Tests Code Coverage PHP Version License

A simple and elegant time synchronization library for PHP that allows you to synchronize your application's clock with a remote time source based on IP geolocation.

Features

  • ๐Ÿ• PSR-20 Clock Interface - Fully compliant with the PSR-20 Clock standard
  • ๐ŸŒ IP Geolocation - Fetch time data for an IPv4 address via ipgeolocation.io
  • ๐Ÿ” Server IP Detection - Automatically detect the current server IP via SyncService
  • โšก Microsecond Precision - Maintains microsecond-level time accuracy
  • ๐Ÿ”Œ PSR-18 HTTP Client - Works with any PSR-18 compatible HTTP client
  • ๐Ÿงช Fully Tested - Covered by PHPUnit tests
  • ๐ŸŽฏ Modern PHP - Requires PHP 8.4+

Installation

Install via Composer:

composer require hexogen/timesync

Requirements

  • PHP 8.4 or higher
  • A PSR-18 compatible HTTP client (for example Guzzle or Symfony HttpClient)

Upgrade Guide for 0.3.0

Version 0.3.0 contains a breaking API change:

  • IPGeolocationClient, SyncService, and IpifyIPDetector now expose dedicated exception classes as part of the public contract
  • Catch InvalidIpAddressException, GeolocationServiceException, and ServerIpDetectionException for precise failure handling
  • All library-specific exceptions now implement TimesyncException, so you can catch a single domain-level type when preferred
  • 0.2.x already introduced the SyncService split for automatic server IP detection; that API remains the current usage model

Before (0.2.x)

use Psr\Http\Client\ClientExceptionInterface;

try {
    $clock = $syncService->getCurrentTime();
} catch (\InvalidArgumentException $e) {
    // Invalid IPv4 address format
} catch (\RuntimeException $e) {
    // API error, malformed response, or detector failure
} catch (ClientExceptionInterface $e) {
    // HTTP client error
}

After (0.3.0)

use Hexogen\Timesync\GeolocationServiceException;
use Hexogen\Timesync\InvalidIpAddressException;
use Hexogen\Timesync\ServerIpDetectionException;
use Psr\Http\Client\ClientExceptionInterface;

try {
    $clock = $syncService->getCurrentTime();
} catch (InvalidIpAddressException $e) {
    // Invalid IPv4 address format
} catch (GeolocationServiceException $e) {
    // ipgeolocation.io returned an error or malformed payload
} catch (ServerIpDetectionException $e) {
    // The current server IP could not be determined
} catch (ClientExceptionInterface $e) {
    // HTTP client transport error
}

You can also catch Hexogen\Timesync\TimesyncException to handle all library-specific failures with one catch block.

Quick Start

<?php

use GuzzleHttp\Client;
use Hexogen\Timesync\IPGeolocationClient;
use Hexogen\Timesync\IpifyIPDetector;
use Hexogen\Timesync\SyncService;

$httpClient = new Client();
$ipDetector = new IpifyIPDetector($httpClient);
$syncClient = new IPGeolocationClient(
    apiKey: 'your-ipgeolocation-api-key',
    httpClient: $httpClient,
);
$syncService = new SyncService($syncClient, $ipDetector);

$clock = $syncService->getCurrentTime();
echo $clock->now()->format('Y-m-d H:i:s.u');

Usage

Basic Clock Usage

The Clock class provides a synchronized time based on a reference timestamp:

use Hexogen\Timesync\Clock;

$referenceTime = microtime(true) + 5.0;
$clock = new Clock($referenceTime, 'Europe/Kyiv');

$now = $clock->now();

Explicit IP Lookup with IPGeolocationClient

Use IPGeolocationClient when you already know which IPv4 address you want to resolve:

use GuzzleHttp\Client;
use Hexogen\Timesync\IPGeolocationClient;

$httpClient = new Client();
$client = new IPGeolocationClient('your-api-key', $httpClient);

$clock = $client->getCurrentTime('8.8.8.8');
echo $clock->now()->getTimezone()->getName();

Automatic Server IP Detection with SyncService

Use SyncService when you want the library to detect the current server IP for you:

use GuzzleHttp\Client;
use Hexogen\Timesync\IPGeolocationClient;
use Hexogen\Timesync\IpifyIPDetector;
use Hexogen\Timesync\SyncService;

$httpClient = new Client();
$ipDetector = new IpifyIPDetector($httpClient);
$syncClient = new IPGeolocationClient('your-api-key', $httpClient);
$syncService = new SyncService($syncClient, $ipDetector);

$clock = $syncService->getCurrentTime();

You can still override the detected IP explicitly:

$clock = $syncService->getCurrentTime('37.17.245.123');

Custom IP Detection

Implement your own IP detector:

use Hexogen\Timesync\ServerIPDetectorInterface;

class CustomIPDetector implements ServerIPDetectorInterface
{
    public function getCurrentServerIP(): string
    {
        return $this->someService->fetchServerIP();
    }
}

API Reference

Clock

Implements Psr\Clock\ClockInterface

public function __construct(
    float $realTimestamp,
    string $timeZone = 'UTC'
)

public function now(): DateTimeImmutable

IPGeolocationClient

Implements SyncClientInterface

public function __construct(
    string $apiKey,
    ClientInterface $httpClient
)

public function getCurrentTime(string $ip): ClockInterface

Throws InvalidIpAddressException, GeolocationServiceException, and ClientExceptionInterface.

SyncService

Implements SyncServiceInterface

public function __construct(
    SyncClientInterface $syncClient,
    ServerIPDetectorInterface $serverIPDetector
)

public function getCurrentTime(?string $ip = null): ClockInterface

Throws InvalidIpAddressException, GeolocationServiceException, ServerIpDetectionException, and ClientExceptionInterface.

IpifyIPDetector

Implements ServerIPDetectorInterface

public function __construct(ClientInterface $httpClient)

public function getCurrentServerIP(): string

Throws ServerIpDetectionException and ClientExceptionInterface.

Exception Types

  • TimesyncException โ€” marker interface implemented by all library-specific exceptions
  • InvalidIpAddressException โ€” invalid IPv4 input passed to the library
  • GeolocationServiceException โ€” geolocation API returned an error or malformed payload
  • ServerIpDetectionException โ€” public server IP lookup failed or returned an invalid payload

Configuration

Get an ipgeolocation.io API Key

  1. Sign up at ipgeolocation.io
  2. Get your API key from the dashboard
  3. Pass it to IPGeolocationClient

Time Zones

The library automatically uses the timezone returned by the geolocation API. You can also manually specify a timezone when creating a Clock:

$clock = new Clock(microtime(true), 'America/New_York');

Testing

Run the test suite:

composer test

Run tests with coverage:

./vendor/bin/phpunit tests/ --coverage-html coverage

Check code style:

composer lint

Fix code style:

composer fix

Code Quality

This library follows:

  • PER-CS (PHP Evolving Recommendations for Code Style)
  • PSR-12 Extended Coding Style Guide
  • Strict types declaration in all files
  • Strong typing with explicit interfaces and return types

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   SyncService                       โ”‚
โ”‚   (SyncServiceInterface)            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
            โ”‚
            โ”œโ”€โ”€โ–บ IpifyIPDetector
            โ”‚    (ServerIPDetectorInterface)
            |     โ””โ”€โ”€โ–บ PSR-18 HTTP Client
            โ”‚
            โ””โ”€โ”€โ–บ IPGeolocationClient
                 (SyncClientInterface)
                      โ”‚
                      โ”œโ”€โ”€โ–บ PSR-18 HTTP Client
                      โ”‚
                      โ””โ”€โ”€โ–บ Clock (PSR-20)
                           โ””โ”€โ”€โ–บ DateTimeImmutable

How It Works

  1. Optional IP Detection: SyncService fetches the current server IP when you do not provide one
  2. Geolocation: IPGeolocationClient queries the ipgeolocation.io API with an IPv4 address
  3. Time Extraction: The API returns the current time and timezone for that IP's location
  4. Clock Creation: A Clock instance stores the time delta and timezone
  5. Synchronized Time: The clock provides synchronized time adjusted for the delta

Error Handling

All library-specific exceptions implement Hexogen\Timesync\TimesyncException.

use Hexogen\Timesync\GeolocationServiceException;
use Hexogen\Timesync\InvalidIpAddressException;
use Hexogen\Timesync\ServerIpDetectionException;
use Hexogen\Timesync\TimesyncException;
use Psr\Http\Client\ClientExceptionInterface;

try {
    $clock = $syncService->getCurrentTime();
} catch (InvalidIpAddressException $e) {
    // Invalid IPv4 address format supplied to the library
    echo $e->getMessage();
} catch (GeolocationServiceException $e) {
    // ipgeolocation.io returned an error or unexpected payload
    echo $e->getMessage();
} catch (ServerIpDetectionException $e) {
    // The current server IP could not be determined
    echo $e->getMessage();
} catch (ClientExceptionInterface $e) {
    // HTTP client transport error
    echo $e->getMessage();
} catch (TimesyncException $e) {
    // Optional single catch for any other library-specific exception type
    echo $e->getMessage();
}

Performance

  • Delta calculation: The clock calculates the time offset once per instance
  • Lightweight: Minimal overhead, no background processes
  • Composable: Cache Clock or SyncService results in your DI container if needed

Examples

Laravel Integration

// In a service provider
$this->app->singleton(ClockInterface::class, function () {
    $httpClient = new \GuzzleHttp\Client();
    $ipDetector = new IpifyIPDetector($httpClient);
    $syncClient = new IPGeolocationClient(
        config('services.ipgeolocation.key'),
        $httpClient,
    );
    $syncService = new SyncService($syncClient, $ipDetector);

    return $syncService->getCurrentTime();
});

Symfony Integration

# config/services.yaml
services:
    Hexogen\Timesync\IpifyIPDetector:
        arguments:
            - '@Psr\Http\Client\ClientInterface'

    Hexogen\Timesync\IPGeolocationClient:
        arguments:
            - '%env(IPGEOLOCATION_API_KEY)%'
            - '@Psr\Http\Client\ClientInterface'

    Hexogen\Timesync\SyncService:
        arguments:
            - '@Hexogen\Timesync\IPGeolocationClient'
            - '@Hexogen\Timesync\IpifyIPDetector'

Contributing

Contributions are welcome. Feel free to submit a Pull Request.

Development Setup

# Clone the repository
git clone https://github.com/hexogen/timesync.git
cd timesync

# Install dependencies
composer install

# Run tests
composer test

# Check code style
composer lint

# Fix code style
composer fix

License

This library is licensed under the MIT License. See LICENSE for details.

Credits

Changelog

0.3.0 (2026-03-11)

  • Breaking: introduced dedicated domain exceptions as part of the public API: InvalidIpAddressException, GeolocationServiceException, and ServerIpDetectionException
  • Breaking: added TimesyncException as a marker interface for all library-specific exceptions
  • Updated interface contracts and README examples to document the new exception handling model
  • Corrected the README PHP requirement to match composer.json (PHP 8.3+)

0.2.0 (2026-03-11)

  • Breaking: moved optional server IP detection from IPGeolocationClient to SyncService
  • Breaking: IPGeolocationClient::__construct() now accepts only apiKey and httpClient
  • Breaking: IPGeolocationClient::getCurrentTime() now requires an explicit IPv4 string
  • Updated documentation and examples to reflect the new API split

0.1.1 (2026-03-10)

  • Added MIT LICENSE file
  • Improved workflow with coverage reporting
  • README fixes and consistency improvements

0.1.0 (2026-03-09)

  • Initial release
  • PSR-20 Clock implementation
  • IP geolocation time synchronization
  • Microsecond precision support
  • Full test coverage

Support

Related Projects