private DateTimeOffset?GetOccurrenceByZonedTimes(DateTimeOffset from, TimeZoneInfo zone, bool inclusive) { var fromLocal = from.DateTime; if (TimeZoneHelper.IsAmbiguousTime(zone, fromLocal)) { var currentOffset = from.Offset; var standardOffset = zone.BaseUtcOffset; if (standardOffset != currentOffset) { var daylightOffset = TimeZoneHelper.GetDaylightOffset(zone, fromLocal); var daylightTimeLocalEnd = TimeZoneHelper.GetDaylightTimeEnd(zone, fromLocal, daylightOffset).DateTime; // Early period, try to find anything here. var foundInDaylightOffset = FindOccurrence(fromLocal.Ticks, daylightTimeLocalEnd.Ticks, inclusive); if (foundInDaylightOffset != NotFound) { return(new DateTimeOffset(foundInDaylightOffset, daylightOffset)); } fromLocal = TimeZoneHelper.GetStandardTimeStart(zone, fromLocal, daylightOffset).DateTime; inclusive = true; } // Skip late ambiguous interval. var ambiguousIntervalLocalEnd = TimeZoneHelper.GetAmbiguousIntervalEnd(zone, fromLocal).DateTime; if (HasFlag(CronExpressionFlag.Interval)) { var foundInStandardOffset = FindOccurrence(fromLocal.Ticks, ambiguousIntervalLocalEnd.Ticks - 1, inclusive); if (foundInStandardOffset != NotFound) { return(new DateTimeOffset(foundInStandardOffset, standardOffset)); } } fromLocal = ambiguousIntervalLocalEnd; inclusive = true; } var occurrenceTicks = FindOccurrence(fromLocal.Ticks, inclusive); if (occurrenceTicks == NotFound) { return(null); } var occurrence = new DateTime(occurrenceTicks); if (zone.IsInvalidTime(occurrence)) { var nextValidTime = TimeZoneHelper.GetDaylightTimeStart(zone, occurrence); return(nextValidTime); } if (TimeZoneHelper.IsAmbiguousTime(zone, occurrence)) { var daylightOffset = TimeZoneHelper.GetDaylightOffset(zone, occurrence); return(new DateTimeOffset(occurrence, daylightOffset)); } return(new DateTimeOffset(occurrence, zone.GetUtcOffset(occurrence))); }
private DateTimeOffset?GetOccurrenceConsideringTimeZone(DateTimeOffset fromUtc, TimeZoneInfo zone, bool inclusive) { if (!DateTimeHelper.IsRound(fromUtc)) { // Rarely, if fromUtc is very close to DST transition, `TimeZoneInfo.ConvertTime` may not convert it correctly on Windows. // E.g., In Jordan Time DST started 2017-03-31 00:00 local time. Clocks jump forward from `2017-03-31 00:00 +02:00` to `2017-03-31 01:00 +3:00`. // But `2017-03-30 23:59:59.9999000 +02:00` will be converted to `2017-03-31 00:59:59.9999000 +03:00` instead of `2017-03-30 23:59:59.9999000 +02:00` on Windows. // It can lead to skipped occurrences. To avoid such errors we floor fromUtc to seconds: // `2017-03-30 23:59:59.9999000 +02:00` will be floored to `2017-03-30 23:59:59.0000000 +02:00` and will be converted to `2017-03-30 23:59:59.0000000 +02:00`. fromUtc = DateTimeHelper.FloorToSeconds(fromUtc); inclusive = false; } var from = TimeZoneInfo.ConvertTime(fromUtc, zone); var fromLocal = from.DateTime; if (TimeZoneHelper.IsAmbiguousTime(zone, fromLocal)) { var currentOffset = from.Offset; var standardOffset = zone.BaseUtcOffset; if (standardOffset != currentOffset) { var daylightOffset = TimeZoneHelper.GetDaylightOffset(zone, fromLocal); var daylightTimeLocalEnd = TimeZoneHelper.GetDaylightTimeEnd(zone, fromLocal, daylightOffset).DateTime; // Early period, try to find anything here. var foundInDaylightOffset = FindOccurrence(fromLocal.Ticks, daylightTimeLocalEnd.Ticks, inclusive); if (foundInDaylightOffset != NotFound) { return(new DateTimeOffset(foundInDaylightOffset, daylightOffset)); } fromLocal = TimeZoneHelper.GetStandardTimeStart(zone, fromLocal, daylightOffset).DateTime; inclusive = true; } // Skip late ambiguous interval. var ambiguousIntervalLocalEnd = TimeZoneHelper.GetAmbiguousIntervalEnd(zone, fromLocal).DateTime; if (HasFlag(CronExpressionFlag.Interval)) { var foundInStandardOffset = FindOccurrence(fromLocal.Ticks, ambiguousIntervalLocalEnd.Ticks - 1, inclusive); if (foundInStandardOffset != NotFound) { return(new DateTimeOffset(foundInStandardOffset, standardOffset)); } } fromLocal = ambiguousIntervalLocalEnd; inclusive = true; } var occurrenceTicks = FindOccurrence(fromLocal.Ticks, inclusive); if (occurrenceTicks == NotFound) { return(null); } var occurrence = new DateTime(occurrenceTicks); if (zone.IsInvalidTime(occurrence)) { var nextValidTime = TimeZoneHelper.GetDaylightTimeStart(zone, occurrence); return(nextValidTime); } if (TimeZoneHelper.IsAmbiguousTime(zone, occurrence)) { var daylightOffset = TimeZoneHelper.GetDaylightOffset(zone, occurrence); return(new DateTimeOffset(occurrence, daylightOffset)); } return(new DateTimeOffset(occurrence, zone.GetUtcOffset(occurrence))); }