SNTP サーバーとの時刻比較(その3)

SNTP サーバーとの時刻比較(その2)の続きです。前回までで、INtpMessage インターフェイス、NtpMessage クラス、INtpClient インターフェイス、NtpClient クラスを書いたので、残りは NtpTimeStampUtil クラス、NtpMessageException クラス、Program クラスです。

まずは NtpTimeStampUtil クラスから。いっきに書くには長いので分割します。

using System;
using System.Runtime.CompilerServices;

// TestTagManage からのアクセスだけは受け入れる
[assembly: InternalsVisibleTo("NtpGetTimestamp.Test")]

namespace Sntp
{
    internal static class NtpTimeStampUtil
    {
        internal const long UNIT_RATING32 = 0x100000000L;    // (2 ** 32)
        internal const long UNIT_RATING16 = 0x10000L;        // (2 ** 16)

        internal static readonly byte TIMESTAMP_LENGTH = 8;
        internal static readonly DateTime START_DATETIME = new DateTime(1900, 1, 1, 0, 0, 0);    // 2036年の桁あふれまで
        internal static readonly DateTime PASSED_START_DATETIME = START_DATETIME.AddSeconds(uint.MaxValue); // 2036年以降

        /// <summary>
        /// NTP タイムスタンプの先頭 bit から NTP タイムスタンプの始点秒数を取得する
        /// </summary>
        /// <param name="seconds"></param>
        /// <returns></returns>
        internal static DateTime GetStartingDatetime(ulong timestamp)
        {
            return (timestamp & 0x8000000000000000) == 0 ? PASSED_START_DATETIME : START_DATETIME;
        }

        /// <summary>
        /// DateTime(UTC) を使用して NTP タイムスタンプの始点秒数を取得する
        /// </summary>
        /// <param name="datetime"></param>
        /// <returns></returns>
        internal static DateTime GetStartingDatetime(DateTime datetime)
        {
            return PASSED_START_DATETIME <= datetime ? PASSED_START_DATETIME : START_DATETIME;
        }
//
        /// <summary>
        /// NTP 64ビット固定小数点数(タイムスタンプ)形式から DateTime(UTC) 型へ変換する
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <returns></returns>
        internal static DateTime NtpTimeStamp2Datetime(ulong timeStamp)
        {
            return GetStartingDatetime(timeStamp) +
                TimeSpan.FromMilliseconds(NtpTimeStamp2Milliseconds(timeStamp));
        }

        /// <summary>
        /// NTP 64ビット固定小数点数(タイムスタンプ)形式から ミリ秒へ変換する
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <returns></returns>
        internal static ulong NtpTimeStamp2Milliseconds(ulong timeStamp)
        {
            var seconds = (uint)(timeStamp >> 32);
            var fraction = (uint)(timeStamp & uint.MaxValue);

            return (ulong)seconds * 1000 + ((ulong)fraction * 1000) / UNIT_RATING32;
        }

        /// <summary>
        /// NTP 64ビット固定小数点数(タイムスタンプ)形式から DateTime(Local Time) 型へ変換する
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <returns></returns>
        internal static DateTime NtpTimeStamp2LocalTime(ulong timeStamp)
        {
            return Utc2LocalTime(NtpTimeStamp2Datetime(timeStamp));
        }
//
        /// <summary>
        /// DateTime(UTC) 型から NTP 64ビット固定小数点数(タイムスタンプ)形式へ変換する
        /// </summary>
        /// <param name="datetime"></param>
        /// <returns></returns>
        internal static ulong Datetime2NtpTimeStamp(DateTime datetime)
        {
            var startDatetime = GetStartingDatetime(datetime);
            var milliseconds = (ulong)(datetime - startDatetime).TotalMilliseconds;
            var seconds = (uint)(milliseconds / 1000);
            var fraction = (uint)((milliseconds % 1000) * UNIT_RATING32 / 1000);

            return (ulong)(((ulong)seconds << 32) | fraction);
        }

        /// <summary>
        /// UTC から ローカルタイムへ変換する
        /// </summary>
        /// <param name="utc"></param>
        /// <returns></returns>
        internal static DateTime Utc2LocalTime(DateTime utc)
        {
            // タイムゾーンのオフセットを計算
            var offset = TimeZone.CurrentTimeZone.GetUtcOffset(DateTime.Now);
            return utc + offset;
        }
    }
}

次は NtpMessageException クラス。

using System;

namespace Sntp
{
    public class NtpMessageException : ApplicationException
    {
        public NtpMessageException(string message)
            : base(message)
        {
        }
    }
}

そして最後に Program クラス。
《SNTP サーバーの IP アドレス》の処には、ネットワーク的にお近くの SNTP サーバー(ISP の SNTP サーバーなど)の IP アドレス(xxx.xxx.xxx.xxx)を記述してください。

using System;

namespace Sntp
{
    class Program
    {
        static void Main(string[] args)
        {
            var remoteIpAddr = "《SNTP サーバーの IP アドレス》";

            INtpClient ntpClient = null;

            ntpClient = new NtpClient();
            ntpClient.ServerIpAddr = remoteIpAddr;
            ntpClient.Connect();

            Console.WriteLine("取得した NTP メッセージ -- (ここから)");
            var message = ntpClient.Message;
            foreach (var n in message)
            {
                Console.Write("{0:X2}", n);
            }
            Console.Write("\r\n");
            Console.WriteLine("取得した NTP メッセージ -- (ここまで)");

            Console.WriteLine("SNTP server: {0}, stratum: {1}, SNTP Verion: {2}",
                ntpClient.ServerIpAddr.ToString(), ntpClient.StratumNumber, ntpClient.VersionNumber);
            Console.WriteLine("Leap indicator: {0}", ntpClient.LeapIndicator);
            Console.WriteLine("Originate Timestamp: {0:yyyy/MM/dd HH:mm:ss:fff}", ntpClient.OriginateTimestamp);
            Console.WriteLine("Receive Timestamp: {0:yyyy/MM/dd HH:mm:ss:fff}", ntpClient.ReceiveTimestamp);
            Console.WriteLine("Receive Timestamp: {0:yyyy/MM/dd HH:mm:ss:fff}", ntpClient.TransmitTimestamp);
            Console.WriteLine("Destination timestamp: {0:yyyy/MM/dd HH:mm:ss:fff}", ntpClient.DestinationTimestamp);
            Console.WriteLine("往復の遅延時間: {0}ms", ntpClient.DelayTime);
            Console.WriteLine("ローカル時計の誤差: {0}", ntpClient.LocalClockOffset);

            Console.Write("Enter キーを押下すると終了します。");
            Console.ReadLine();
        }

        // NTP メッセージを作成するためのヘルパークラス
        private static byte[] createNtpMessage(string source)
        {
            var length = source.Length / 2;
            var bytes = new byte[length];
            for (var i = 0; i < length; i++)
            {
                bytes[i] = Convert.ToByte(source.Substring(i * 2, 2), 16);
            }

            return bytes;
        }
    }
}

3回にわたるシリーズみたいになってしまいましたが、とりあえずこれで SNTP サーバとの時刻比較のプログラムが動くようになります 😉


コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です