8

Can anyone give me the most straightforward way to create a ZonedDateTime, given "4:30pm" and "America/Chicago".

I want this object to represent that time for the current date in that timezone.

Thanks!

I tried this... but it seems to actually give me an instant in the local timezone which gets offset when creating the zonedDateTime.

        string time = "4:30pm";
        string timezone = "America/Chicago";
        DateTime dateTime;
        if (DateTime.TryParse(time, out dateTime))
        {
            var instant = new Instant(dateTime.Ticks);
            DateTimeZone tz = DateTimeZoneProviders.Tzdb[timezone];
            var zonedDateTime = instant.InZone(tz);
Brian Rice
  • 3,107
  • 1
  • 35
  • 53

1 Answers1

11
using NodaTime;
using NodaTime.Text;

// your inputs
string time = "4:30pm";
string timezone = "America/Chicago";

// parse the time string using Noda Time's pattern API
LocalTimePattern pattern = LocalTimePattern.CreateWithCurrentCulture("h:mmtt");
ParseResult<LocalTime> parseResult = pattern.Parse(time);
if (!parseResult.Success) {
    // handle parse failure
}
LocalTime localTime = parseResult.Value;

// get the current date in the target time zone
DateTimeZone tz = DateTimeZoneProviders.Tzdb[timezone];
IClock clock = SystemClock.Instance;
Instant now = clock.Now;
LocalDate today = now.InZone(tz).Date;

// combine the date and time
LocalDateTime ldt = today.At(localTime);

// bind it to the time zone
ZonedDateTime result = ldt.InZoneLeniently(tz);

A few notes:

  • I intentionally separated many items into separate variables so you could see the progression from one type to the next. You may condense them as desired for fewer lines of code. I also used the explicit type names. Feel free to use var.

  • You may want to put this in a function. When you do, you should pass in the clock variable as a parameter. This will let you replace the system clock for a FakeClock in your unit tests.

  • Be sure to understand how InZoneLeniently behaves, and note how it's changing in the upcoming 2.0 release. See "Lenient resolver changes" in the 2.x migration guide.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • I had to switch to 2.0.0 for the .At function to work... then I had to use: Instant now = clock.GetCurrentInstant(); instead of clock.Now in 2.0.0. – Brian Rice Dec 30 '15 at 02:47
  • 1
    `At` was added in 1.3.0. You must have been on 1.2.0 before. In 1.2, you'd use the `+` operator to combine `LocalDate` and `LocalTime`. (You can still use that if you like.) But yes, the method for the clock instant has changed in 2.0 also. – Matt Johnson-Pint Dec 30 '15 at 04:34
  • 2
    In 2.0 you can also use a `ZonedClock` to make things simpler, if you're going to take the current date/time from the same time zone repeatedly. – Jon Skeet Dec 30 '15 at 11:31
  • Great instructional code. I would suggest using `DateTimeZoneProviders.Tzdb.GetZoneOrNull(timezone)` instead of `DateTimeZoneProviders.Tzdb[timezone]` to show that this step may not work. – tymtam Nov 20 '17 at 00:04