6

次のコードを使用して、NTP と C# の DateTime を変換します。前方への転回は正しいと思いますが、後方への転回は間違っています。

8 バイトを DatTime に変換するには、次のコードを参照してください。

NTP を DateTime に変換する

public static ulong GetMilliSeconds(byte[] ntpTime)
{
    ulong intpart = 0, fractpart = 0;

    for (var i = 0; i <= 3; i++)
        intpart = 256 * intpart + ntpTime[i];
    for (var i = 4; i <= 7; i++)
        fractpart = 256 * fractpart + ntpTime[i];

    var milliseconds = intpart * 1000 + ((fractpart * 1000) / 0x100000000L);

    Debug.WriteLine("intpart:      " + intpart);
    Debug.WriteLine("fractpart:    " + fractpart);
    Debug.WriteLine("milliseconds: " + milliseconds);
    return milliseconds;
}

public static DateTime ConvertToDateTime(byte[] ntpTime)
{
    var span = TimeSpan.FromMilliseconds(GetMilliSeconds(ntpTime));
    var time = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    time += span;
    return time;
}

DateTime から NTP への変換

public static byte[] ConvertToNtp(ulong milliseconds)
{
    ulong intpart = 0, fractpart = 0;
    var ntpData = new byte[8];

    intpart = milliseconds / 1000;
    fractpart = ((milliseconds % 1000) * 0x100000000L) / 1000;

    Debug.WriteLine("intpart:      " + intpart);
    Debug.WriteLine("fractpart:    " + fractpart);
    Debug.WriteLine("milliseconds: " + milliseconds);

    var temp = intpart;
    for (var i = 3; i >= 0; i--)
    {
        ntpData[i] = (byte)(temp % 256);
        temp = temp / 256;
    }

    temp = fractpart;
    for (var i = 7; i >= 4; i--)
    {
        ntpData[i] = (byte)(temp % 256);
        temp = temp / 256;
    }
    return ntpData;
}

次の入力により、出力が生成されます。

bytes = { 131, 170, 126, 128,
           46, 197, 205, 234 }

var ms = GetMilliSeconds(bytes );
var ntp = ConvertToNtp(ms)

//GetMilliSeconds output
milliseconds: 2208988800182
intpart:      2208988800
fractpart:    784715242

//ConvertToNtp output
milliseconds: 2208988800182
intpart:      2208988800
fractpart:    781684047

ミリ秒から小数部分への変換が間違っていることに注意してください。なんで?

アップデート:

Jonathan S. が指摘するように、それは分数の損失です。そのため、前後に変換する代わりに、NTP タイムスタンプを直接操作したいと考えています。より具体的には、ミリ秒を追加します。次の関数がまさにそれを行うと思いますが、検証するのに苦労しています。小数部についてはよくわかりません。

public static void AddMilliSeconds(ref byte[] ntpTime, ulong millis)
{
    ulong intpart = 0, fractpart = 0;

    for (var i = 0; i < 4; i++)
        intpart = 256 * intpart + ntpTime[i];
    for (var i = 4; i <= 7; i++)
        fractpart = 256 * fractpart + ntpTime[i];

    intpart += millis / 1000;
    fractpart += millis % 1000;

    var newIntpart = BitConverter.GetBytes(SwapEndianness(intpart));
    var newFractpart = BitConverter.GetBytes(SwapEndianness(fractpart));

    for (var i = 0; i < 8; i++)
    {
        if (i < 4)
            ntpTime[i] = newIntpart[i];
        if (i >= 4)
            ntpTime[i] = newFractpart[i - 4];
    }
}
4

4 に答える 4

5

ここで発生しているのは、NTP タイムスタンプからミリ秒への変換で精度が失われることです。NTP からミリ秒に変換すると、分数の一部が削除されます。その後、その値を元に変換しようとすると、わずかに異なる値が得られます。このテストのように、ulong値を値に変更すると、これをより明確に確認できます。decimal

public static decimal GetMilliSeconds(byte[] ntpTime)
{
    decimal intpart = 0, fractpart = 0;

    for (var i = 0; i <= 3; i++)
        intpart = 256 * intpart + ntpTime[i];
    for (var i = 4; i <= 7; i++)
        fractpart = 256 * fractpart + ntpTime[i];

    var milliseconds = intpart * 1000 + ((fractpart * 1000) / 0x100000000L);

    Console.WriteLine("milliseconds: " + milliseconds);
    Console.WriteLine("intpart:      " + intpart);
    Console.WriteLine("fractpart:    " + fractpart);
    return milliseconds;
}

public static byte[] ConvertToNtp(decimal milliseconds)
{
    decimal intpart = 0, fractpart = 0;
    var ntpData = new byte[8];

    intpart = milliseconds / 1000;
    fractpart = ((milliseconds % 1000) * 0x100000000L) / 1000m;

    Console.WriteLine("milliseconds: " + milliseconds);
    Console.WriteLine("intpart:      " + intpart);
    Console.WriteLine("fractpart:    " + fractpart);

    var temp = intpart;
    for (var i = 3; i >= 0; i--)
    {
        ntpData[i] = (byte)(temp % 256);
        temp = temp / 256;
    }

    temp = fractpart;
    for (var i = 7; i >= 4; i--)
    {
        ntpData[i] = (byte)(temp % 256);
        temp = temp / 256;
    }
    return ntpData;
}

public static void Main(string[] args)
{
    byte[] bytes = { 131, 170, 126, 128,
           46, 197, 205, 234 };

    var ms = GetMilliSeconds(bytes);
    Console.WriteLine();
    var ntp = ConvertToNtp(ms);
}

これにより、次の結果が得られます。

milliseconds: 2208988800182.7057548798620701
intpart:      2208988800
fractpart:    784715242

milliseconds: 2208988800182.7057548798620701
intpart:      2208988800.1827057548798620701
fractpart:    784715242.0000000000703594496

ここで物事を台無しにしているのは〜0.7ミリ秒です。

NTP タイムスタンプには 32 ビットの小数秒 ( 「2^-32 秒または 233 ピコ秒の理論上の分解能」 ) が含まれているため、整数ミリ秒に変換すると精度が失われます。

更新への対応:

NTP タイムスタンプにミリ秒を追加することは、整数部分と小数部分を追加するほど単純ではありません。小数の 1.75 と 2.75 を足すことを考えてみてください。0.75 + 0.75 = 1.5 であり、1 を整数部分に繰り越す必要があります。また、NTP タイムスタンプの小数部分は 10 進法ではないため、ミリ秒を追加することはできません。のような比率を使用して、何らかの変換が必要ms / 1000 = ntpfrac / 0x100000000です。

intpart +=これは完全にテストされていませんが、あなたのandfracpart +=行を次のように置き換えたいと思うでしょうAddMilliSeconds:

intpart += millis / 1000;

ulong fractsum = fractpart + (millis % 1000) / 1000 * 0x100000000L);

intpart += fractsum / 0x100000000L;
fractpart = fractsum % 0x100000000L;
于 2013-05-26T21:53:21.567 に答える
2

キャメロンの解決策への提案:使用

ntpEpoch = (new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).Ticks;

現地時間から計算しないようにする

于 2014-12-10T18:06:55.737 に答える
1

DateTime は NTP と逆方向にティックします。

static long ntpEpoch = (new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).Ticks;
static public long Ntp2Ticks(UInt64 a)
{
  var b = (decimal)a * 1e7m / (1UL << 32);
  return (long)b + ntpEpoch;
}
static public UInt64 Ticks2Ntp(long a)
{
    decimal b = a - ntpEpoch;
    b = (decimal)b / 1e7m * (1UL << 32);
    return (UInt64)b;
}
于 2014-04-18T18:45:07.747 に答える