简体   繁体   中英

DateTime Conversion to Unix Epoch Adding Phantom Hour

I have the following conversion methods for converting to and from Unix Epoch timestamps

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

The problem is (boy this seems like basic stuff), I set a DateTime I want and then try to convert to Unix-Time but the returned milliscond timestamp is +01:00 hour, I want to know why?

The code I am using is

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

long startMillis = DateTimeHelpers.ToMillisecondsSinceUnixEpoch(startDate);

this gives startMillis = 1443657600000 which is "Thursday October 01, 2015 01:00:00 (am) in time zone Europe/London (BST)". I want a timestamp back from ToMillisecondsSinceUnixEpoch that is "2015/10/01 00:00:00", what am I missing here?

Thanks for your time.


Edit. I want to do the equivelant of some Java code. This produced the right timestamp. Why can I do this in Java and not C#? Anyway the code

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

I use it like this

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

this gives timestamp = 1443654000000 which is "Thursday October 01, 2015 00:00:00 (am) in time zone Europe/London (BST)". What am I missing with C#? I have tried

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;

This ADDS an hour!?

If I follow what you are doing, your test code starts with UTC time:

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

But in FromMillisecondsSinceUnixEpoch you return LocalTime. Using the code as is, it wont make a roundtrip:

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

If I change FromMillisecondsSinceUnixEpoch :

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

Now it will make the roundtrip:

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

Note than the MilliSecondsSince is the same for each. You cant just look at that because there is no context.

I could not find it in the Ref Source, but surely DateTime is smart enough to adjust when the TZs are different before subtracting. (Otherwise it is hard to explain how 1443675600000 can represent 3 TimeSpans for the same date (2 for me and one for you).

A few things:

  • If possible, you should work with DateTimeOffset instead of DateTime for this type of operation. DateTimeOffset is always a specific moment in time, while DateTime might be, or might not be, depending on Kind and how well you adhere to the subtleties of how Kind is interpreted by various methods.

  • If you do use DateTimeOffset , and you are targeting .NET 4.6 or greater (or .NET Core), then you can use the built in DateTimeOffset.FromUnixTimeMilliseconds and ToUnixTimeMilliseconds methods, rather than creating your own.

  • You might consider using the Noda Time open source library, as it adds significant value to most applications working with date and time.

    • For instance, if you want to work with tzdb time zones like the "Europe/London" zone you mentioned, then you can use DateTimeZoneProviders.Tzdb["Europe/London"] .

Now the rest of my answer assumes you don't take any of the above recommendations, and pertains to the code you provided in the question.

  • You have UnixEpoch implemented as a static method. Since its value never changes, it should probably be implemented as a static property, with a private readonly backing field. It could also be implemented as a public static readonly field, though most people prefer exposing those via properties. (These are just coding guidelines, but don't introduce any error.)

  • In your FromMillisecondsSinceUnixEpoch method, you are calling .ToLocalTime() . That should be omitted. You don't need to call .ToUniversalTime() either. Just return the result of adding the milliseconds. The Kind will be Utc . If you need to work with local time, do the conversion later - not inside this function.

  • Recognize that the ID "GMT Standard Time" is for London, not UTC. London is either GMT (UTC+00:00) or BST (UTC+01:00) depending on the date and time in question.

  • Recognize that DateTime.ToLocalTime and DateTime.ToUniversalTime convert between UTC and the current local time zone on the machine where the code is running. That might be London, or might be something else depending on your use case. If you're running on a server , such as in an ASP.Net web application, then it's not a good practice to rely on the system local time zone.

  • In the code you showed with TimeZoneInfo.ConvertTime , since you didn't assign DateTimeKind.Utc to the input, that value will have DateTimeKind.Unspecified . ConvertTime will interpret that as already belonging to the source time zone. Since you've given the same destination time zone, then in most cases this will be a no-op.

  • In that same function, it's invalid to define unixEpoch in terms of London time, for the reasons specified earlier. Also note that on 1970-01-01, London was not on GMT, but actually on BST (known as "British Standard Time" back then, rather than "British Summer time"). TZDB knows about this, but it's too far back for Windows time zones and TimeZoneInfo . The "GMT Standard Time" Windows zone only reflects the current rules around BST/GMT, not those that were in effect at the time.

As far as converting the Java code you supplied, that function reads a string in ISO 8601 format with millisecond precision, interprets it in the London time zone, converts it to UTC, and gives the time in milliseconds since the Unix epoch. There are a few ways you could do that:

  • .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(); } 
  • Noda Time

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM