Tom Wesson Tom Wesson - 2 months ago 29
C# Question

Is this the proper way to convert between time zones in Nodatime?

The goal is to create a function that converts a time from one timezone to another properly using Nodatime. I'm not just looking for feedback on whether the result appears correct, but feedback specific to "how" I'm doing this (is it truly correct, or are there subtle holes).

Here is the function:

static ZonedDateTime ConvertDateTimeToDifferentTimeZone(DateTime p_from_datetime, string p_from_zone, string p_to_zone)
{
DateTimeZone from_zone = DateTimeZoneProviders.Tzdb[p_from_zone];
LocalDateTime from_local = new LocalDateTime(p_from_datetime.Year, p_from_datetime.Month, p_from_datetime.Day, p_from_datetime.Hour, p_from_datetime.Minute);
ZonedDateTime from_datetime = from_zone.AtStrictly(from_local);

var from_instant = from_datetime.ToInstant();

DateTimeZone to_zone = DateTimeZoneProviders.Tzdb[p_to_zone];
ZonedDateTime to_datetime = to_zone.AtStrictly(from_local);

var to_offset_datetime = from_instant.WithOffset(to_datetime.Offset);

return to_zone.AtStrictly(to_offset_datetime.LocalDateTime);
}


The way I would call it looks like this:

DateTime from_datetime = new DateTime(2016, 10, 15, 16, 30, 0);
string from_zone = "US/Central";
string to_zone = "US/Eastern";
var x = ConvertDateTimeToDifferentTimeZone(from_datetime, from_zone, to_zone);

Console.WriteLine(from_datetime.ToString() + " (" + from_zone + ") = " + " (" + x.ToString() + " (" + to_zone + ")");


Am I missing anything or doing anything incorrectly?

Answer

I would stick to Noda Time types as far as possible (and .NET naming conventions). The conversion between zones should be done with ZonedDateTime.WithZone - which means you're really asking about converting to and from ZonedDateTime. If you really, really have to use DateTime instead of Noda Time types, you probably want something like:

public static DateTime ConvertDateTimeToDifferentTimeZone(
    DateTime fromDateTime,
    string fromZoneId,
    string tozoneId)
{
    LocalDateTime fromLocal = LocalDateTime.FromDateTime(fromDateTime);
    DateTimeZone fromZone = DateTimeZoneProviders.Tzdb[fromZoneId];
    ZonedDateTime fromZoned = fromLocal.InZoneLeniently(fromZone);

    DateTimeZone toZone = DateTimeZoneProviders.Tzdb[toZoneId];
    ZonedDateTime toZoned = fromZoned.WithZone(toZone);
    LocalDateTime toLocal = toZoned.LocalDateTime;
    return toLocal.ToDateTimeUnspecified();
}

Note the use of InZoneLeniently here - that means that if the local time you've given is invalid due to a jump in the UTC offset (typically due to daylight savings) it will still return a value rather than throwing an exception - see the docs for more details.

It's hard to know what the method would look like if you were using Noda Time throughout, as we don't know whether you'd start off with a LocalDateTime or a ZonedDateTime. For example, you could have:

public static LocalDateTime ConvertDateTimeToDifferentTimeZone(
    LocalDateTime fromLocal,
    string fromZoneId,
    string tozoneId)
{
    DateTimeZone fromZone = DateTimeZoneProviders.Tzdb[fromZoneId];
    ZonedDateTime fromZoned = fromLocal.InZoneLeniently(fromZone);

    DateTimeZone toZone = DateTimeZoneProviders.Tzdb[toZoneId];
    ZonedDateTime toZoned = fromZoned.WithZone(fromZone);
    return toZoned.LocalDateTime;
}