DateTime转换为Unix纪元添加幻影小时

时间:2015-10-31 12:59:42

标签: c# datetime timezone unix-timestamp epoch

我有以下用于转换为Unix Epoch时间戳的转换方法

public static class DateTimeHelpers
{
    public static DateTime UnixEpoch()
    {
        return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    }

    public static DateTime FromMillisecondsSinceUnixEpoch(long milliseconds)
    {
        return UnixEpoch().AddMilliseconds(milliseconds).ToLocalTime();
    }

    public static long ToMillisecondsSinceUnixEpoch(DateTime dateTime)
    {
        return (long)(dateTime - UnixEpoch()).TotalMilliseconds;
    }
}

问题是(男孩这似乎是基本的东西),我设置了一个DateTime我想要然后尝试转换为Unix-Time但返回的毫秒时间戳是+01:00小时,我想知道为什么呢?

我正在使用的代码是

DateTime startDate = new DateTime(2015, 10, 1, 0, 0, 0, 0, DateTimeKind.Utc);

long startMillis = DateTimeHelpers.ToMillisecondsSinceUnixEpoch(startDate);

这给出了startMillis = 1443657600000,即“2015年10月1日星期四01:00:00(am)在欧洲/伦敦(BST)时区”。我想从ToMillisecondsSinceUnixEpoch返回时间戳“2015/10/01 00:00:00”,我在这里缺少什么?

感谢您的时间。

编辑。我想做一些Java代码的等价。这产生了正确的时间戳。为什么我可以用Java而不是C#来做这个?无论如何代码

private static long ukTimeStringToUtcMillis(String s) {
    SimpleDateFormat sdf = makeSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
    try {
        return sdf.parse(s).getTime();
    } catch (ParseException e) {
        throw new RuntimeException(e);
    }
}

private static SimpleDateFormat makeSimpleDateFormat(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat(s);
    sdf.setTimeZone(TimeZone.getTimeZone("Europe/London"));
    return sdf;
}

我像这样使用它

long timestamp = ukTimeStringToUtcMillis("2015-10-01T00:00:00.000");

这给出了timestamp = 1443654000000,即“2015年10月1日星期四00:00:00(am)在欧洲/伦敦(BST)时区”。我对C#缺少什么?我试过了

var ukTimeZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTime unixEpoch = TimeZoneInfo.ConvertTime(
    new DateTime(1970, 1, 1, 0, 0, 0), ukTimeZone, ukTimeZone);

long startMillis = (long)(startDate - unixEpoch).TotalMilliseconds;
long endMillis = (long)(endDate - unixEpoch).TotalMilliseconds;

这个ADDS一个小时!?

2 个答案:

答案 0 :(得分:3)

如果我按照您的操作行事,您的测试代码将以UTC时间开始:

DateTime startDate = new DateTime(2015, 10, 1, 0, 0, 0, 0, DateTimeKind.Utc);

但是在FromMillisecondsSinceUnixEpoch中你返回LocalTime。按原样使用代码,它不会进行往返:

Console.WriteLine(dt.ToUniversalTime());
Console.WriteLine("{0}  {1}", ToMillisecondsSinceUnixEpoch(dt), 
    FromMillisecondsSinceUnixEpoch(ToMillisecondsSinceUnixEpoch(dt)));
  

10/1/2015 5:00:00 AM   1443657600000 10/1/2015 12:00:00 AM

如果我更改FromMillisecondsSinceUnixEpoch

public static DateTime FromMillisecondsSinceUnixEpoch(long milliseconds)
{
    return UnixEpoch().AddMilliseconds(milliseconds).ToUniversalTime();
}

现在它将进行往返:

  

10/1/2015 5:00:00 AM   1443675600000 10/1/2015 5:00:00 AM

注意MilliSecondsSince对每个都是相同的。你不能只看那个,因为没有上下文。

我在Ref Source中找不到它,但是在减去之前,当TZ不同时,DateTime足够聪明。 (否则,很难解释1443675600000如何在同一日期代表3个TimeSpans(2个对我而且一个对你而言)。

答案 1 :(得分:1)

一些事情:

  • 如果可能,您应该使用DateTimeOffset代替DateTime进行此类操作。 DateTimeOffset始终是一个特定的时刻,而DateTime 可能是,或者可能不是,取决于Kind以及您如何坚持微妙的如何通过各种方法解释Kind

  • 如果您使用DateTimeOffset,并且您的目标是.NET 4.6或更高版本(或.NET Core),那么您可以使用内置的DateTimeOffset.FromUnixTimeMillisecondsToUnixTimeMilliseconds方法,而不是创建自己的方法。

  • 您可以考虑使用Noda Time开源库,因为它为使用日期和时间的大多数应用程序增加了重要价值。

    • 例如,如果您想使用tzdb时区,例如您提到的"Europe/London"区域,那么您可以使用DateTimeZoneProviders.Tzdb["Europe/London"]

现在,我的其余部分假设您不接受上述任何建议,并且与您在问题中提供的代码相关。

  • 您已将UnixEpoch实施为静态方法。由于它的值永远不会改变,因此它应该可以实现为静态属性,具有私有只读备份字段。它也可以实现为公共静态只读字段,但大多数人更喜欢通过属性公开它们。 (这些只是编码指南,但不要引入任何错误。)

  • FromMillisecondsSinceUnixEpoch方法中,您正在呼叫.ToLocalTime()。应该省略。您也不需要致电.ToUniversalTime()。只需返回添加毫秒的结果。 Kind将为Utc。如果您需要使用当地时间,请稍后进行转换 - 不在此功能内。

  • 确认ID "GMT Standard Time"适用于伦敦,而不是UTC。伦敦格林威治标准时间(UTC + 00:00)或BST(UTC + 01:00),具体取决于相关日期和时间。

  • 认识到DateTime.ToLocalTimeDateTime.ToUniversalTime在UTC与运行代码的计算机上的当前本地时区之间进行转换。这可能是伦敦,或者可能是其他东西,具体取决于您的用例。如果您在服务器上运行,例如在ASP.Net Web应用程序中运行,那么依赖系统本地时区并不是一个好习惯。

  • 在您使用TimeZoneInfo.ConvertTime显示的代码中,由于您没有将DateTimeKind.Utc分配给输入,因此该值将为DateTimeKind.UnspecifiedConvertTime会将其解释为已属于源时区。由于您已经给出了相同的目的地时区,因此在大多数情况下,这将是一个无操作。

  • 在同一个功能中,由于之前指定的原因,根据伦敦时间定义unixEpoch无效。另请注意,在1970-01-01赛季,伦敦不是格林威治标准时间,而是实际上是在BST(被称为"英国标准时间"当时,而不是"英国夏令时")。 TZDB知道这一点,但它对于Windows时区和TimeZoneInfo来说太过分了。 "GMT Standard Time" Windows区域仅反映当前BST / GMT的规则,而不是当时生效的规则。

就转换您提供的Java代码而言,该函数以毫秒精度读取ISO 8601格式的字符串,在伦敦时区解释它,将其转换为UTC,并提供自Unix纪元以来的毫秒时间。有几种方法可以做到这一点:

  • .Net 3.5 +

    public static long UkTimeStringToUtcMillis(string s)
    {
        string format = "yyyy-MM-dd'T'HH:mm:ss.FFF";
        DateTime dt = DateTime.ParseExact(s, format, CultureInfo.InvariantCulture);
        TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
        DateTime utc = TimeZoneInfo.ConvertTimeToUtc(dt, tz);
        DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        return (long) (utc - epoch).TotalMilliseconds;
    }
    
  • .Net 4.6+ / .Net CoreCLR

    public static long UkTimeStringToUtcMillis(string s)
    {
        string format = "yyyy-MM-dd'T'HH:mm:ss.FFF";
        DateTime dt = DateTime.ParseExact(s, format, CultureInfo.InvariantCulture);
        TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
        TimeSpan offset = tz.GetUtcOffset(dt);
        DateTimeOffset dto = new DateTimeOffset(dt, offset);
        return dto.ToUnixTimeMilliseconds();
    }
    
  • 野田时间

    public static long UkTimeStringToUtcMillis(string s)
    {
        LocalDateTimePattern pattern = LocalDateTimePattern.ExtendedIsoPattern;
        LocalDateTime dt = pattern.Parse(s).Value;
        DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/London"];
        Instant i = dt.InZoneLeniently(tz).ToInstant();
        return i.Ticks / NodaConstants.TicksPerMillisecond;
    }