Read UTC timestamp from NTP time server

Fri, 12 November 2021

php
NtpServer.php
<?php
/**
 * NtpServer
 * @example `$utcTimestamp = NtpServer::get();`
 */
class NtpServer
{
    /**
     * @var string[]
     */
    public static array $servers = [
        'time1.google.com',
        'time2.google.com',
        'time3.google.com',
        'time4.google.com'
    ];

    /**
     * Get the time from an NTP server. The timestamp returned is always in UTC.
     *
     * @link https://en.wikipedia.org/wiki/Network_Time_Protocol
     * @param array|null $servers
     * @param int $secondsTimeout
     * @param float $millisecondTimeout
     * @return int|null
     */
    public static function get(?array $servers = null, int $secondsTimeout = 1, float $millisecondTimeout = 0): ?int
    {
        if (null === $servers) {
            $servers = self::$servers;
        }

        $time = null;
        foreach ($servers as $server) {
            $time = self::attempt($server, $secondsTimeout, $millisecondTimeout);
            if ($time !== null) {
                break;
            }
        }

        return $time;
    }

    /**
     * Attempt to get the time from an NTP server.
     *
     * @param string $server
     * @param int $secondsTimeout
     * @param float $millisecondTimeout
     * @return int|mixed|null
     */
    protected static function attempt(string $server, int $secondsTimeout = 1, float $millisecondTimeout = 0)
    {
        $socket = @stream_socket_client(
            sprintf('udp://%s:123', $server),
            $errorCode,
            $errorMessage,
            $millisecondTimeout
        );
        if (false === $socket || $errorCode !== 0) {
            return null;
        }
        @stream_set_timeout($socket, $secondsTimeout, $millisecondTimeout);
        if (false === @fwrite($socket, "\010\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")) {
            return null;
        }

        if (false === $binary = @fread($socket, 48)) {
            return null;
        }

        if (false === @fclose($socket)) {
            return null;
        }
        if (false === $data = @unpack('N12', $binary)) {
            return null;
        }
        return $data[9] - 2208988800;
    }
}
php