c# - 在 NTP 和 C# DateTime 之间转换

标签 c# ntp

我使用以下代码在 NTP 和 C# DateTime 之间进行转换。我认为前向翻转是正确的,向后翻转是错误的。

看下面的代码将8个字节转换成一个DatTime:

将 NTP 转换为日期时间

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];
    }
}

最佳答案

您在这里遇到的是从 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 位小数秒 ( "a theoretical resolution of 2^-32 seconds or 233 picoseconds" ),转换为整数毫秒将导致精度损失。

响应更新:

将毫秒添加到 NTP 时间戳并不像添加整数部分和小数部分那么简单。考虑将小数点 1.75 和 2.75 相加。 0.75 + 0.75 = 1.5,您需要将 1 带入整数部分。此外,NTP 时间戳中的小数部分不是以 10 为底,因此您不能只添加毫秒。需要进行一些转换,使用类似 ms/1000 = ntpfrac/0x100000000 的比例。

这完全未经测试,但我认为您想要替换 AddMilliSeconds 中的 intpart +=fracpart += 行> 更像这样:

intpart += millis / 1000;

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

intpart += fractsum / 0x100000000L;
fractpart = fractsum % 0x100000000L;

关于c# - 在 NTP 和 C# DateTime 之间转换,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16763300/

相关文章:

c# - 关于定时器和可变间隔的问题

c# - 字节溢出评估为零而不是异常?

c# - 获取 C# mysql 中单个列的值并将其放入文本框中

python - 如何使用 Python ntplib 将 ntp 服务器时间精确到毫秒?

linux - 如何自动检查时钟?

c# - 使用 webkitbrowser 调用按钮提交

C# MVC 上传文件而不覆盖以前的上传

docker - 如何确保 docker 的时间与主机的时间同步?

regex - Go:处理多行命令输出并解析