Difference Between DateTime (UTC) Based on a Local

2019-09-21 08:47发布

问题:

I have two DateTime objects which contain two UTC date/times and a users TimezoneId (tzdb) as a string. I'm trying to write a method that takes these three parameters and returns the total seconds (or Duration) between the two datetimes relative to the timezone.

public static double GetDurationForTimezone(DateTime startUtc, DateTime endUtc, string timezoneId)
{
    var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timezoneId);

    // convert UTC to timezone
    var startInstantUtc = Instant.FromDateTimeUtc(startUtc);
    var startZonedDateTime = startInstantUtc.InZone(timezone);

    var endInstantUtc = Instant.FromDateTimeUtc(endUtc);
    var endZonedDateTime = endInstantUtc.InZone(timezone);

    return endZonedDateTime.ToInstant().Minus(startZonedDateTime.ToInstant()).ToTimeSpan().TotalSeconds;
}

I want to do it w.r.t. the timezone, to ensure it takes into account any possible Daylight Saving changes that may occur throughout this period.

Example test:

// DST starts (25h day -- DST starts: 10/4 @ 2am local time)
var result = GetDurationForTimezone(
    new DateTime(2015, 10, 3, 15, 0, 0, DateTimeKind.Utc),
    new DateTime(2015, 10, 4, 15, 0, 0, DateTimeKind.Utc),
    "Australia/Sydney");
Assert.Equal(TimeSpan.FromHours(25).TotalSeconds, result);

But when running this test, it seems like the calls to .ToInstant() are not adhering to the ZonedDateTime versions, but rather the original UTC DateTime objects. Thus I'm seeing the result be 24 hours.

回答1:

Switching to utilize the LocalDateTime property of the ZonedDateTime allows for comparing the date/times relative to the timezone. This works for both prime test cases (23h and 25h days):

public static double GetDurationForTimezone(DateTime startUtc, DateTime endUtc, string timezoneId)
{
    var timezone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timezoneId);

    // convert UTC to timezone
    var startInstantUtc = Instant.FromDateTimeUtc(startUtc);
    var startZonedDateTime = startInstantUtc.InZone(timezone);
    var startLocalDateTime = startZonedDateTime.LocalDateTime;

    var endInstantUtc = Instant.FromDateTimeUtc(endUtc);
    var endZonedDateTime = endInstantUtc.InZone(timezone);
    var endLocalDateTime = endZonedDateTime.LocalDateTime;

    return Period.Between(startLocalDateTime, endLocalDateTime, PeriodUnits.Seconds).Seconds;
}


回答2:

When determining the duration between UTC-based timestamps, the time zone is irrelevant.

UTC is Coordinated Universal Time. It is the same for everyone on the planet. It does not have daylight saving time, and it's offset is always zero (UTC+00:00).

Since you have already asserted that the input values are in UTC, you do not necessarily need to use Noda Time for this operation. Just subtract the two values.

TimeSpan duration = endUtc - startUtc;

If you do use Noda Time, a UTC value is best represented by an Instant, which makes it very easy to obtain a Duration.

Instant start = Instant.FromDateTimeUtc(startUtc);
Instant end = Instant.FromDateTimeUtc(endUtc);
Duration duration = end - start;

You could also represent them using ZonedDateTime values that happen to be "in UTC", however you'd quickly find that the API requires you convert them back to Instant values to obtain a Duration anyway.

ZonedDateTime start = LocalDateTime.FromDateTime(startUtc).InUtc();
ZonedDateTime end = LocalDateTime.FromDateTime(endUtc).InUtc();
Duration duration = end.ToInstant() - start.ToInstant();

You might think that just using LocalDateTime would be an option, but that structure represents a wall time, without any time zone information. You can't obtain a Duration between two of them. You could obtain a Period by using Period.Between, but that would represent the calendar/clock-value difference between the two representations - which is not the same as the actual amount of time that has elapsed.

As a thought exercise that will help understand the difference, consider these two values:

2015-11-01 00:30
2015-11-01 01:30

If I tell you that the values are in UTC, then there is one hour difference. However, if I tell you these are wall-clock values and they are in the US Eastern Time zone, then they might be one hour apart, or they might be two hours apart. It depends on whether or not the 01:30 is the one before the DST transition, or the one after - as there are two on this day.

Now if instead I gave you these values:

2015-11-01 00:30
2015-11-01 02:30

Again, if you interpret them as UTC they are exactly two hours apart. But if you interpret them in the same US Eastern time zone, then they are exactly three hours apart, because the range is inclusive of the DST transition. If you just subtract the local wall-time values then you'd get two hours, which would be incorrect.



回答3:

Studying this page: ZonedDateTime.Comparer Members

it seems like you have to use property Local and not Instant to reflect the local daylight savings.



标签: c# nodatime