Thanks to @codtex for your answer. Unfortunately it only works in the northern hemisphere. In the southern hemisphere daylight savings usually starts somewhere around October. That means dtAdjustmentEnd
would need to be checked first rather than dtAdjustmentStart
.
So instead of creating another if
statement to handle this, and to simplify the code that handles the next year's transition, I updated the code to get the transitions for the given year and the next, sort them and take the first one that is after the asOfTime
.
public static DateTime? GetNextTransition(DateTime asOfTime, TimeZoneInfo timeZone)
{
TimeZoneInfo.AdjustmentRule[] adjustments = timeZone.GetAdjustmentRules();
if (adjustments.Length == 0)
{
// if no adjustment then no transition date exists
return null;
}
int year = asOfTime.Year;
TimeZoneInfo.AdjustmentRule adjustment = null;
foreach (TimeZoneInfo.AdjustmentRule adj in adjustments)
{
// Determine if this adjustment rule covers year desired
if (adj.DateStart.Year <= year && adj.DateEnd.Year >= year)
{
adjustment = adj;
break;
}
}
if (adjustment == null)
{
// no adjustment found so no transition date exists in the range
return null;
}
// Calculate the adjustments for the asOfTime year and the next.
// Sort them because DST start for any given year can be after DST end.
// Take the first one on or after the asOfTime.
var adjustmentTimes = new List<DateTime>()
{
GetAdjustmentDate(adjustment.DaylightTransitionStart, year),
GetAdjustmentDate(adjustment.DaylightTransitionEnd, year),
GetAdjustmentDate(adjustment.DaylightTransitionStart, year + 1),
GetAdjustmentDate(adjustment.DaylightTransitionEnd, year + 1),
};
adjustmentTimes.Sort();
return adjustmentTimes.First(at => at > asOfTime);
}
public static DateTime GetAdjustmentDate(TimeZoneInfo.TransitionTime transitionTime, int year)
{
if (transitionTime.IsFixedDateRule)
{
return new DateTime(year, transitionTime.Month, transitionTime.Day);
}
else
{
// For non-fixed date rules, get local calendar
Calendar cal = CultureInfo.CurrentCulture.Calendar;
// Get first day of week for transition
// For example, the 3rd week starts no earlier than the 15th of the month
int startOfWeek = transitionTime.Week * 7 - 6;
// What day of the week does the month start on?
int firstDayOfWeek = (int)cal.GetDayOfWeek(new DateTime(year, transitionTime.Month, 1));
// Determine how much start date has to be adjusted
int transitionDay;
int changeDayOfWeek = (int)transitionTime.DayOfWeek;
if (firstDayOfWeek <= changeDayOfWeek)
transitionDay = startOfWeek + (changeDayOfWeek - firstDayOfWeek);
else
transitionDay = startOfWeek + (7 - firstDayOfWeek + changeDayOfWeek);
// Adjust for months with no fifth week
if (transitionDay > cal.GetDaysInMonth(year, transitionTime.Month))
transitionDay -= 7;
return new DateTime(year, transitionTime.Month, transitionDay, transitionTime.TimeOfDay.Hour, transitionTime.TimeOfDay.Minute, transitionTime.TimeOfDay.Second);
}
}
This would be less efficient, but unless it's going to be called thousands of times a second, nobody is going to notice.