I recommend using Noda Time to solve this. (Sure, you could use TimeZoneInfo
but processing adjustment rules are a pain.)
Declare these imports:
using System;
using System.Linq;
using NodaTime;
Define a function to do the main work:
static (LocalDateTime StartDateTime, LocalDateTime UntilDateTime, Offset UtcOffset)[]
GetOffsetRanges(DateTimeZone tz, LocalDate startDate, LocalDate untilDate)
{
// Get ZonedDateTime values representing the start of day of each date in the given time zone.
ZonedDateTime startZonedDateTime = startDate.AtStartOfDayInZone(tz);
ZonedDateTime untilZonedDateTime = untilDate.AtStartOfDayInZone(tz);
// We'll also need the corresponding Instant of each value.
Instant startInstant = startZonedDateTime.ToInstant();
Instant untilInstant = untilZonedDateTime.ToInstant();
// Get the ZoneInterval values, which tell us the range that an offset is in effect.
return tz.GetZoneIntervals(startInstant, untilInstant).Select(interval =>
{
// Snap the values to the range we're working with. (This is optional, you could just use the IsoLocalStart/End values directly.)
LocalDateTime startDateTime = interval.Start >= startInstant ? interval.IsoLocalStart : startZonedDateTime.LocalDateTime;
LocalDateTime untilDateTime = interval.End <= untilInstant ? interval.IsoLocalEnd : untilZonedDateTime.LocalDateTime;
// Include the local date and time range, and the offset that's in effect over that range.
return (startDateTime, untilDateTime, interval.WallOffset);
}).ToArray();
}
Use the function like so:
// Define your input values
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
var startDate = new LocalDate(2020, 1, 1);
var untilDate = new LocalDate(2021, 1, 1);
// Call the function to get the ranges
var ranges = GetOffsetRanges(tz, startDate, untilDate);
// Example output
Console.WriteLine($"Offsets in {tz.Id} between {startDate:R} and {untilDate:R} are as follows:");
foreach (var range in ranges)
{
Console.WriteLine($"{range.StartDateTime:s} - {range.UntilDateTime:s} : {range.UtcOffset:m}");
}
Output:
Offsets in America/New_York between 2020-01-01 and 2021-01-01 are as follows:
2020-01-01T00:00:00 - 2020-03-08T02:00:00 : -05:00
2020-03-08T03:00:00 - 2020-11-01T02:00:00 : -04:00
2020-11-01T01:00:00 - 2021-01-01T00:00:00 : -05:00
A few notes:
I don't recommend simplifying the input or output in the way you stated in your question. It is not precise enough to say Mar-Oct
.
As Panagiotis pointed out in the question comments, you will need to identify a time zone to use. A single offset input is not sufficient. My example showed a named IANA time zone, but if desired you could instead pass the system's local time zone by using DateTimeZoneProviders.Tzdb.GetSystemDefault()
If this is going to run on a server, then please don't use the system's local time zone. In general, the server's time zone should be considered irrelevant.
In a web application, you can get the user's IANA time zone identifier in JavaScript with Intl.DateTimeFormat().resolvedOptions().timeZone
. You can pass that to your server and use it as input with the above code. (This works on most modern web browsers, but not all older ones.)