12

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?

Logarr
  • 2,120
  • 1
  • 17
  • 29
BitsAndBytes
  • 815
  • 1
  • 9
  • 13

2 Answers2

14

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;
}
Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks for your comments and instruction Jon. The background of this is a calendaring application. The specific purpose is for the event owner to be able to input a date and time value along with a time zone for a particular calendar event. Ideally, this would be the only information that would be saved, although I don't have a problem storing the offset if it makes things easier. On the other end of this, the event receipient/consumer would be able to see an accurate date and time in their time zone of choice (possibly retrieved from the browser settings, or their desired time zone). – BitsAndBytes Aug 30 '16 at 12:54
  • @TomWesson: Well, you need to work out what you want to do if they provide a date/time that doesn't exist or happens twice, due to DST changes. Maybe `InZoneLeniently` does what you want, but you shoudl make that decision explicitly. (Storing the offset as well would allow you to disambiguate.) – Jon Skeet Aug 30 '16 at 12:55
  • You're quick Jon, I made an edit to my comment above. – BitsAndBytes Aug 30 '16 at 12:56
  • @TomWesson: The comment still doesn't address what you want to do if the event owner enters "London time zone, October 30th 2016 1.30am" for example. – Jon Skeet Aug 30 '16 at 12:58
  • I would be ok with catching the exception and informing them that this isn't a valid date/time because of DST for the zone they entered. – BitsAndBytes Aug 30 '16 at 12:58
  • 1
    @TomWesson: That would be suitable for the "spring forward" issue, but 1.30am on October 30th 2016 *is* a valid date/time... it's just that it occurs twice. You could catch the exception for that and ask for a disambiguation though. Instead of exceptions, you could use `DateTimeZone.MapLocal` to find out if the local time maps to 0, 1 or 2 instants in time. – Jon Skeet Aug 30 '16 at 12:59
  • With var x = zone.MapLocal(local) in mind, if the count is 2, I would then ask them to choose between x.EarlyInterval.WallOffset or x.LateInterval.WallOffset? – BitsAndBytes Aug 30 '16 at 13:16
  • @TomWesson: Exactly. – Jon Skeet Aug 30 '16 at 13:19
  • @JonSkeet this link is not accessible, please check https://nodatime.org/1.3.x/api/html/M_NodaTime_LocalDateTime_InZoneLeniently.htm – Vivek Nuna Oct 07 '22 at 14:45
  • 1
    @viveknuna: Updated both links to the 3.1 docs. – Jon Skeet Oct 07 '22 at 14:52
  • @JonSkeet I have one more question. If there are any changes in the IANA time zone or for example they add a new time zone. Then will we need to upgrade the nodatime library or will it handle it? – Vivek Nuna Oct 07 '22 at 15:01
  • @viveknuna: Please ask in a new post. Comments are not the right place for additional questions. (But you should also read the documentation, in this case https://nodatime.org/3.1.x/userguide/tzdb) – Jon Skeet Oct 07 '22 at 15:10
  • @JonSkeet posted a question on this https://stackoverflow.com/questions/73989355/how-nodatime-handles-the-changes-in-the-iana-timezone – Vivek Nuna Oct 07 '22 at 15:18
3

I think the method is written nicely. You are not missing anything/doing anything incorectlly as far as I can tell. But here is a bit improved version of a method:

static ZonedDateTime ConvertDateTimeToDifferentTimeZone(DateTime p_from_datetime, string p_from_zone, string p_to_zone)
{
    DateTimeZone from_zone = DateTimeZoneProviders.Tzdb[p_from_zone];
    var from_local = LocalDateTime.FromDateTime(p_from_datetime);
    ZonedDateTime from_datetime = from_zone.AtStrictly(from_local);

    DateTimeZone to_zone = DateTimeZoneProviders.Tzdb[p_to_zone];
    return from_datetime.WithZone(to_zone);
}
fsacer
  • 1,382
  • 1
  • 15
  • 23