Search code examples
c#.netdatetimeutcnodatime

.NET Noda Time Converting Local DateTime To UTC for a specific Timezone


I am using Noda Time to convert local datetime to UTC.

This is what I have so far:

static string LocalTimeToUTC(string timeZone, string localDateTime)
{
    var pattern = LocalDateTimePattern.CreateWithInvariantCulture("dd/MM/yyyy HH:mm:ss");
    LocalDateTime ldt = pattern.Parse(localDateTime).Value;
    ZonedDateTime zdt = ldt.InZoneLeniently(DateTimeZoneProviders.Tzdb[timeZone]);
    Instant instant = zdt.ToInstant();
    ZonedDateTime utc = instant.InUtc();
    string output = utc.ToString("dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture);

    return output;
}

static void Main(string[] args)
{
    foreach (DateTime d in myDates)
    {
        DateTime utcTime = Convert.ToDateTime(LocalTimeToUTC("Europe/Amsterdam", d.ToString()));
        Console.WriteLine(utcTime);
    }

    Console.ReadKey();
}

The problem is with Daylight Savings: When the clock goes 1 hour backwards. There is a missing hour, see the results below.

   25/10/2014 19:00:00
   25/10/2014 20:00:00
   25/10/2014 21:00:00
   25/10/2014 22:00:00
   25/10/2014 23:00:00
   26/10/2014 01:00:00
   26/10/2014 02:00:00

As you can see, 26/10/2014 00:00:00 is missing due to the clock change.

My question is that is there a way to handle this so that there are no missing hours? Populate the missing hours?


Solution

  • You're missing an hour because there are two UTC instants which both correspond to the same local time, due to the clock going back. The only way of avoiding "missing hours" is to emit two entries for the ambiguous local time.

    Let's look at a table the other way round, from UTC to local time, with one entry per elapsed hour:

    UTC                        Local time
    2014-10-25 21:00:00Z       2014-10-25 23:00:00 +02
    2014-10-25 22:00:00Z       2014-10-26 00:00:00 +02
    2014-10-25 23:00:00Z       2014-10-26 01:00:00 +02
    2014-10-26 00:00:00Z       2014-10-26 02:00:00 +02
    2014-10-26 01:00:00Z       2014-10-26 02:00:00 +01 (clocks back!)
    2014-10-26 02:00:00Z       2014-10-26 03:00:00 +01
    

    So there are 6 distinct values on the left hand side, but only 5 distinct values on the right hand side.

    You can use DateTimeZone.MapLocal(LocalDateTime) to get a ZoneLocalMapping which will include 0, 1 or 2 values depending on whether the local date/time was skipped (due to a jump forward), unambiguous, or repeated (due to a jump back). That may give you what you want, but it's not really clear what you're trying to achieve or what your input data represents.