116

All I need is a way to query an NTP Server using C# to get the Date Time of the NTP Server returned as either a string or as a DateTime.

How is this possible in its simplest form?

dodgy_coder
  • 12,407
  • 10
  • 54
  • 67
JL.
  • 78,954
  • 126
  • 311
  • 459

6 Answers6

182

Since the old accepted answer got deleted (It was a link to a Google code search results that no longer exist), I figured I could answer this question for future reference :

public static DateTime GetNetworkTime()
{
    //default Windows time server
    const string ntpServer = "time.windows.com";

    // NTP message size - 16 bytes of the digest (RFC 2030)
    var ntpData = new byte[48];

    //Setting the Leap Indicator, Version Number and Mode values
    ntpData[0] = 0x1B; //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)

    var addresses = Dns.GetHostEntry(ntpServer).AddressList;

    //The UDP port number assigned to NTP is 123
    var ipEndPoint = new IPEndPoint(addresses[0], 123);
    //NTP uses UDP

    using(var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
    {
        socket.Connect(ipEndPoint);

        //Stops code hang if NTP is blocked
        socket.ReceiveTimeout = 3000;     

        socket.Send(ntpData);
        socket.Receive(ntpData);
        socket.Close();
    }

    //Offset to get to the "Transmit Timestamp" field (time at which the reply 
    //departed the server for the client, in 64-bit timestamp format."
    const byte serverReplyTime = 40;

    //Get the seconds part
    ulong intPart = BitConverter.ToUInt32(ntpData, serverReplyTime);

    //Get the seconds fraction
    ulong fractPart = BitConverter.ToUInt32(ntpData, serverReplyTime + 4);

    //Convert From big-endian to little-endian
    intPart = SwapEndianness(intPart);
    fractPart = SwapEndianness(fractPart);

    var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);

    //**UTC** time
    var networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);

    return networkDateTime.ToLocalTime();
}

// stackoverflow.com/a/3294698/162671
static uint SwapEndianness(ulong x)
{
    return (uint) (((x & 0x000000ff) << 24) +
                   ((x & 0x0000ff00) << 8) +
                   ((x & 0x00ff0000) >> 8) +
                   ((x & 0xff000000) >> 24));
}

Note: You will have to add the following namespaces

using System.Net;
using System.Net.Sockets;
Community
  • 1
  • 1
Nasreddine
  • 36,610
  • 17
  • 75
  • 94
  • This is nice code and provides at least an idea of what time it is... but when i run the code 5 times within a fraction of a second, the resulting times differ allmost a minute... so something is definitly wrong here (no matter which time server i use). – Martin Booka Weser Sep 06 '12 at 08:59
  • @MartinBookaWeser Do you use different threads for each call ? because the the `Socket.Send` is a synchronous operation (blocking). – Nasreddine Sep 06 '12 at 12:23
  • nop, same thread, just two calls immediately after each other - result in signifficantly different times... – Martin Booka Weser Sep 06 '12 at 12:43
  • this code just seems to hang and do nothing in windows 8, any ideas? – cvocvo Jun 10 '13 at 21:57
  • @cvocvo It works just fine (I have just tested it under Windows 8 x64). Are you sure it's not a problem on your end (such as a firewall, etc). Try stepping through the code to find out where the problem is. – Nasreddine Jun 10 '13 at 22:18
  • Got it working; needed to compile for x64..dumb mistake. How can I query with respect to the locally set timezone on the PC? Trying to get the offset for the timezone to account for things like daylight savings time--which aren't reflected in UTC. – cvocvo Jun 10 '13 at 22:24
  • 2
    @cvocvo For that you can use [`DateTime.ToLocalTime()`](http://msdn.microsoft.com/en-us/library/system.datetime.tolocaltime.aspx) – Nasreddine Jun 10 '13 at 22:30
  • 2
    Under situations where NTP is blocked this code hangs and never returns. How can I add a timeout or something to ensure this code returns? – cvocvo Oct 28 '13 at 16:27
  • @Nasreddine - in some cases I got `DateTime` 1900 as result. I would recommend to add sanity check for `networkDateTime` value before returning. @cvocvo - try using inside different thread with timeout. – itsho May 27 '14 at 08:30
  • @cvocvo I believe someone added a timeout to this code after your comment, see the line `socket.ReceiveTimeout = 3000;` - this will cause a timeout after 3 seconds. – dodgy_coder Sep 23 '14 at 05:08
  • 4
    This is one of the few pieces of code that are good enough to be cut-and-pasted directly from the Internet into production code (after testing and review of course). – dodgy_coder Sep 23 '14 at 05:33
  • Nice One. And, you have to use these namespaces here. using System.Net; using System.Net.Sockets; – Thilina Sandunsiri Oct 02 '15 at 07:21
  • var networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds); why convert networkDateTime from ulong to long? – Zhenguo Yang Oct 18 '15 at 10:00
  • 2
    Why I get LAN win7 time,(use the code posision: `socket.Receive(ntpData);`) it will throw exception: `An existing connection was forcibly closed by the remote host`. But all fine If I use command line `net time` to get the time. – qakmak Nov 25 '15 at 12:04
  • I found out, n the server PC: 1. Must open windows time service. 2. must open ntp service option in `gpedit.msc`......I think command line 'net time` easy than that... – qakmak Nov 25 '15 at 13:47
  • 3
    Line 17: var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); Should be var socket = new Socket(addresses[0].AddressFamily, SocketType.Dgram, ProtocolType.Udp); That way it will work if the 1st address if IP6 – Duane McKinney Feb 15 '17 at 19:18
  • 1
    Note that `socket.Close();` is not necessary in this example because it is within `using` which will close and dispose it at the end. – LukAss741 Nov 19 '18 at 10:46
  • No need for parenthesis if we remember our order of operations: var milliseconds = intPart * 1000 + fractPart * 1000 / 0x100000000L; – BlueSky May 22 '19 at 00:14
  • If I try to access an IPv6 address, I get the error SocketException telling that the address is not compatible with the protocol. What can I do? – Daniel Marschall Jul 09 '20 at 08:40
  • @DanielMarschall did you use `AddressFamily.InterNetworkV6` ? – Nasreddine Jul 09 '20 at 12:19
  • @Nasreddine Yes, I even created a small function that checks if the address is IPv6 and then use InterNetworkV6 or InterNetwork. However, there seem to be more issues. For example, `Dns.GetHostEntry(ntpServer).AddressList` cannot resolve the IPv6 address correctly. `GetHostEntry('2001:7b8:3:32:213:136::252').AddressList` returns `213.136.0.252`, which is wrong. Also, the code mentions `VN = 3 (IPv4 only)`, so I guess the NTP protocol is not able to handle IPv6 at all? – Daniel Marschall Jul 14 '20 at 09:07
  • "time.windows.com" today not work. i use "pool.ntp.org" and all ok – Ricko.. Oct 18 '22 at 06:57
40

This is a optimized version of the function which removes dependency on BitConverter function and makes it compatible with NETMF (.NET Micro Framework)

public static DateTime GetNetworkTime()
{
    const string ntpServer = "pool.ntp.org";
    var ntpData = new byte[48];
    ntpData[0] = 0x1B; //LeapIndicator = 0 (no warning), VersionNum = 3 (IPv4 only), Mode = 3 (Client Mode)

    var addresses = Dns.GetHostEntry(ntpServer).AddressList;
    var ipEndPoint = new IPEndPoint(addresses[0], 123);
    var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

    socket.Connect(ipEndPoint);
    socket.Send(ntpData);
    socket.Receive(ntpData);
    socket.Close();

    ulong intPart = (ulong)ntpData[40] << 24 | (ulong)ntpData[41] << 16 | (ulong)ntpData[42] << 8 | (ulong)ntpData[43];
    ulong fractPart = (ulong)ntpData[44] << 24 | (ulong)ntpData[45] << 16 | (ulong)ntpData[46] << 8 | (ulong)ntpData[47];

    var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
    var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);

    return networkDateTime;
}
GonzaloG
  • 409
  • 4
  • 3
  • 11
    Are you missing a timeout ... `socket.ReceiveTimeout = 3000;` ... this prevents it hanging if there's a network issue. Value is in milliseconds. – dodgy_coder Sep 23 '14 at 05:03
  • 3
    Take care of UTC: var networkDateTime = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(milliseconds); return networkDateTime.ToLocalTime(); – Daniel Fisher lennybacon Sep 01 '15 at 16:21
9

The .NET Micro Framework Toolkit found in the CodePlex has an NTPClient. I have never used it myself but it looks good.

There is also another example located here.

Nasreddine
  • 36,610
  • 17
  • 75
  • 94
Tony Borf
  • 4,579
  • 8
  • 43
  • 51
  • The .NET Micro Framework Toolkit was moved to [github.com/michaelschwarz/NETMF-Toolkit](https://github.com/michaelschwarz/NETMF-Toolkit) – stomy Aug 03 '17 at 17:35
7

I know the topic is quite old, but such tools are always handy. I've used the resources above and created a version of NtpClient which allows asynchronously to acquire accurate time, instead of event based.

 /// <summary>
/// Represents a client which can obtain accurate time via NTP protocol.
/// </summary>
public class NtpClient
{
    private readonly TaskCompletionSource<DateTime> _resultCompletionSource;

    /// <summary>
    /// Creates a new instance of <see cref="NtpClient"/> class.
    /// </summary>
    public NtpClient()
    {
        _resultCompletionSource = new TaskCompletionSource<DateTime>();
    }

    /// <summary>
    /// Gets accurate time using the NTP protocol with default timeout of 45 seconds.
    /// </summary>
    /// <returns>Network accurate <see cref="DateTime"/> value.</returns>
    public async Task<DateTime> GetNetworkTimeAsync()
    {
        return await GetNetworkTimeAsync(TimeSpan.FromSeconds(45));
    }

    /// <summary>
    /// Gets accurate time using the NTP protocol with default timeout of 45 seconds.
    /// </summary>
    /// <param name="timeoutMs">Operation timeout in milliseconds.</param>
    /// <returns>Network accurate <see cref="DateTime"/> value.</returns>
    public async Task<DateTime> GetNetworkTimeAsync(int timeoutMs)
    {
        return await GetNetworkTimeAsync(TimeSpan.FromMilliseconds(timeoutMs));
    }

    /// <summary>
    /// Gets accurate time using the NTP protocol with default timeout of 45 seconds.
    /// </summary>
    /// <param name="timeout">Operation timeout.</param>
    /// <returns>Network accurate <see cref="DateTime"/> value.</returns>
    public async Task<DateTime> GetNetworkTimeAsync(TimeSpan timeout)
    {
        using (var socket = new DatagramSocket())
        using (var ct = new CancellationTokenSource(timeout))
        {
            ct.Token.Register(() => _resultCompletionSource.TrySetCanceled());

            socket.MessageReceived += OnSocketMessageReceived;
            //The UDP port number assigned to NTP is 123
            await socket.ConnectAsync(new HostName("pool.ntp.org"), "123");
            using (var writer = new DataWriter(socket.OutputStream))
            {
                // NTP message size is 16 bytes of the digest (RFC 2030)
                var ntpBuffer = new byte[48];

                // Setting the Leap Indicator, 
                // Version Number and Mode values
                // LI = 0 (no warning)
                // VN = 3 (IPv4 only)
                // Mode = 3 (Client Mode)
                ntpBuffer[0] = 0x1B;

                writer.WriteBytes(ntpBuffer);
                await writer.StoreAsync();
                var result = await _resultCompletionSource.Task;
                return result;
            }
        }
    }

    private void OnSocketMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
    {
        try
        {
            using (var reader = args.GetDataReader())
            {
                byte[] response = new byte[48];
                reader.ReadBytes(response);
                _resultCompletionSource.TrySetResult(ParseNetworkTime(response));
            }
        }
        catch (Exception ex)
        {
            _resultCompletionSource.TrySetException(ex);
        }
    }

    private static DateTime ParseNetworkTime(byte[] rawData)
    {
        //Offset to get to the "Transmit Timestamp" field (time at which the reply 
        //departed the server for the client, in 64-bit timestamp format."
        const byte serverReplyTime = 40;

        //Get the seconds part
        ulong intPart = BitConverter.ToUInt32(rawData, serverReplyTime);

        //Get the seconds fraction
        ulong fractPart = BitConverter.ToUInt32(rawData, serverReplyTime + 4);

        //Convert From big-endian to little-endian
        intPart = SwapEndianness(intPart);
        fractPart = SwapEndianness(fractPart);

        var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);

        //**UTC** time
        DateTime networkDateTime = (new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).AddMilliseconds((long)milliseconds);
        return networkDateTime;
    }

    // stackoverflow.com/a/3294698/162671
    private static uint SwapEndianness(ulong x)
    {
        return (uint)(((x & 0x000000ff) << 24) +
                       ((x & 0x0000ff00) << 8) +
                       ((x & 0x00ff0000) >> 8) +
                       ((x & 0xff000000) >> 24));
    }
}

Usage:

var ntp = new NtpClient();
var accurateTime = await ntp.GetNetworkTimeAsync(TimeSpan.FromSeconds(10));
  • 1
    Doesn't work in Windows 7, only in Windows 10. See [windows.networking.sockets.datagramsocket](https://learn.microsoft.com/en-us/uwp/api/windows.networking.sockets.datagramsocket) – stomy Aug 03 '17 at 19:12
  • I have used this inWind 10 IoT Core projects with Raspberry Pi 3, works a treat given that time stops when the pi is turned off. (no real time clock) – Chris Schaller May 14 '18 at 00:39
7

A modified version to compensate network times and calculate with DateTime-Ticks (more precise than milliseconds)

public static DateTime GetNetworkTime()
{
  const string NtpServer = "pool.ntp.org";

  const int DaysTo1900 = 1900 * 365 + 95; // 95 = offset for leap-years etc.
  const long TicksPerSecond = 10000000L;
  const long TicksPerDay = 24 * 60 * 60 * TicksPerSecond;
  const long TicksTo1900 = DaysTo1900 * TicksPerDay;

  var ntpData = new byte[48];
  ntpData[0] = 0x1B; // LeapIndicator = 0 (no warning), VersionNum = 3 (IPv4 only), Mode = 3 (Client Mode)

  var addresses = Dns.GetHostEntry(NtpServer).AddressList;
  var ipEndPoint = new IPEndPoint(addresses[0], 123);
  long pingDuration = Stopwatch.GetTimestamp(); // temp access (JIT-Compiler need some time at first call)
  using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
  {
    socket.Connect(ipEndPoint);
    socket.ReceiveTimeout = 5000;
    socket.Send(ntpData);
    pingDuration = Stopwatch.GetTimestamp(); // after Send-Method to reduce WinSocket API-Call time

    socket.Receive(ntpData);
    pingDuration = Stopwatch.GetTimestamp() - pingDuration;
  }

  long pingTicks = pingDuration * TicksPerSecond / Stopwatch.Frequency;

  // optional: display response-time
  // Console.WriteLine("{0:N2} ms", new TimeSpan(pingTicks).TotalMilliseconds);

  long intPart = (long)ntpData[40] << 24 | (long)ntpData[41] << 16 | (long)ntpData[42] << 8 | ntpData[43];
  long fractPart = (long)ntpData[44] << 24 | (long)ntpData[45] << 16 | (long)ntpData[46] << 8 | ntpData[47];
  long netTicks = intPart * TicksPerSecond + (fractPart * TicksPerSecond >> 32);

  var networkDateTime = new DateTime(TicksTo1900 + netTicks + pingTicks / 2);

  return networkDateTime.ToLocalTime(); // without ToLocalTime() = faster
}
MaxKlaxx
  • 713
  • 7
  • 9
  • I recommand to change line var networkDateTime = new DateTime(TicksTo1900 + netTicks + pingTicks / 2); by var networkDateTime = new DateTime(ticksTo1900 + netTicks + pingTicks / 2, DateTimeKind.Utc); – Bestter Nov 29 '18 at 15:52
  • 1
    Hello Besster, thanks for your hint. For .ToLocalTime() the UTC-Kind is not mandatory. If you need the timestamp with the optional UTC marker, you could alternatively call .ToUniversalTime() at return. Reason: I used the constructor without DateTimeKind, because this is 2-3 processor ticks faster. :D – MaxKlaxx Dec 01 '18 at 00:00
-1

http://www.codeproject.com/Articles/237501/Windows-Phone-NTP-Client is going to work well for Windows Phone .

Adding the relevant code

/// <summary>
/// Class for acquiring time via Ntp. Useful for applications in which correct world time must be used and the 
/// clock on the device isn't "trusted."
/// </summary>
public class NtpClient
{
    /// <summary>
    /// Contains the time returned from the Ntp request
    /// </summary>
    public class TimeReceivedEventArgs : EventArgs
    {
        public DateTime CurrentTime { get; internal set; }
    }

    /// <summary>
    /// Subscribe to this event to receive the time acquired by the NTP requests
    /// </summary>
    public event EventHandler<TimeReceivedEventArgs> TimeReceived;

    protected void OnTimeReceived(DateTime time)
    {
        if (TimeReceived != null)
        {
            TimeReceived(this, new TimeReceivedEventArgs() { CurrentTime = time });
        }
    }


    /// <summary>
    /// Not reallu used. I put this here so that I had a list of other NTP servers that could be used. I'll integrate this
    /// information later and will provide method to allow some one to choose an NTP server.
    /// </summary>
    public string[] NtpServerList = new string[]
    {
        "pool.ntp.org ",
        "asia.pool.ntp.org",
        "europe.pool.ntp.org",
        "north-america.pool.ntp.org",
        "oceania.pool.ntp.org",
        "south-america.pool.ntp.org",
        "time-a.nist.gov"
    };

    string _serverName;
    private Socket _socket;

    /// <summary>
    /// Constructor allowing an NTP server to be specified
    /// </summary>
    /// <param name="serverName">the name of the NTP server to be used</param>
    public NtpClient(string serverName)
    {
        _serverName = serverName;
    }


    /// <summary>
    /// 
    /// </summary>
    public NtpClient()
        : this("time-a.nist.gov")
    { }

    /// <summary>
    /// Begins the network communication required to retrieve the time from the NTP server
    /// </summary>
    public void RequestTime()
    {
        byte[] buffer = new byte[48];
        buffer[0] = 0x1B;
        for (var i = 1; i < buffer.Length; ++i)
            buffer[i] = 0;
        DnsEndPoint _endPoint = new DnsEndPoint(_serverName, 123);

        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        SocketAsyncEventArgs sArgsConnect = new SocketAsyncEventArgs() { RemoteEndPoint = _endPoint };
        sArgsConnect.Completed += (o, e) =>
        {
            if (e.SocketError == SocketError.Success)
            {
                SocketAsyncEventArgs sArgs = new SocketAsyncEventArgs() { RemoteEndPoint = _endPoint };
                sArgs.Completed +=
                    new EventHandler<SocketAsyncEventArgs>(sArgs_Completed);
                sArgs.SetBuffer(buffer, 0, buffer.Length);
                sArgs.UserToken = buffer;
                _socket.SendAsync(sArgs);
            }
        };
        _socket.ConnectAsync(sArgsConnect);

    }

    void sArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            byte[] buffer = (byte[])e.Buffer;
            SocketAsyncEventArgs sArgs = new SocketAsyncEventArgs();
            sArgs.RemoteEndPoint = e.RemoteEndPoint;

            sArgs.SetBuffer(buffer, 0, buffer.Length);
            sArgs.Completed += (o, a) =>
            {
                if (a.SocketError == SocketError.Success)
                {
                    byte[] timeData = a.Buffer;

                    ulong hTime = 0;
                    ulong lTime = 0;

                    for (var i = 40; i <= 43; ++i)
                        hTime = hTime << 8 | buffer[i];
                    for (var i = 44; i <= 47; ++i)
                        lTime = lTime << 8 | buffer[i];
                    ulong milliseconds = (hTime * 1000 + (lTime * 1000) / 0x100000000L);

                    TimeSpan timeSpan =
                        TimeSpan.FromTicks((long)milliseconds * TimeSpan.TicksPerMillisecond);
                    var currentTime = new DateTime(1900, 1, 1) + timeSpan;
                    OnTimeReceived(currentTime);

                }
            };
            _socket.ReceiveAsync(sArgs);
        }
    }
}

Usage :

public partial class MainPage : PhoneApplicationPage
{
    private NtpClient _ntpClient;
    public MainPage()
    {
        InitializeComponent();
        _ntpClient = new NtpClient();
        _ntpClient.TimeReceived += new EventHandler<NtpClient.TimeReceivedEventArgs>(_ntpClient_TimeReceived);
    }

    void _ntpClient_TimeReceived(object sender, NtpClient.TimeReceivedEventArgs e)
    {
        this.Dispatcher.BeginInvoke(() =>
                                        {
                                            txtCurrentTime.Text = e.CurrentTime.ToLongTimeString();
                                            txtSystemTime.Text = DateTime.Now.ToUniversalTime().ToLongTimeString();
                                        });
    }

    private void UpdateTimeButton_Click(object sender, RoutedEventArgs e)
    {
        _ntpClient.RequestTime();
    }
}
  • 1
    `Socket` is a `IDisposable`. You should design your class with that in mind and provide a way to release the socket both on normal use and whenever an exception is raised. This code does cause memory leaks – gfache Jan 26 '18 at 14:44