Skip to content

Commit

Permalink
Firstly floor from to seconds to handle rare DST cases
Browse files Browse the repository at this point in the history
  • Loading branch information
aidmsu authored and odinserj committed May 26, 2021
1 parent e002b8a commit a831ac9
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/Cronos/CronExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ public static unsafe CronExpression Parse(string expression, CronFormat format)
return new DateTime(found, DateTimeKind.Utc);
}

fromUtc = DateTimeHelper.FloorToSeconds(fromUtc);

var zonedStart = TimeZoneInfo.ConvertTime(fromUtc, zone);
var zonedStartOffset = new DateTimeOffset(zonedStart, zonedStart - fromUtc);
var occurrence = GetOccurrenceByZonedTimes(zonedStartOffset, zone, inclusive);
Expand Down Expand Up @@ -254,6 +256,8 @@ public static unsafe CronExpression Parse(string expression, CronFormat format)
return new DateTimeOffset(found, TimeSpan.Zero);
}

from = DateTimeHelper.FloorToSeconds(from);

var zonedStart = TimeZoneInfo.ConvertTime(from, zone);
return GetOccurrenceByZonedTimes(zonedStart, zone, inclusive);
}
Expand Down
24 changes: 24 additions & 0 deletions src/Cronos/DateTimeHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Cronos
{
internal static class DateTimeHelper
{
private static readonly TimeSpan OneSecond = TimeSpan.FromSeconds(1);

public static DateTimeOffset FloorToSeconds(DateTimeOffset dateTimeOffset)
{
return dateTimeOffset.AddTicks(-GetExtraTicks(dateTimeOffset.Ticks));
}

public static DateTime FloorToSeconds(DateTime dateTime)
{
return dateTime.AddTicks(-GetExtraTicks(dateTime.Ticks));
}

private static long GetExtraTicks(long ticks)
{
return ticks % OneSecond.Ticks;
}
}
}
23 changes: 23 additions & 0 deletions tests/Cronos.Tests/CronExpressionFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,28 @@ public void GetNextOccurrence_HandleDST_WhenTheClockJumpsForward_And_TimeZoneIsE

[Theory]

[InlineData("30 0 L * *", "2017-03-30 23:59:59.9999999 +02:00", "2017-03-31 01:00:00 +03:00", false)]
[InlineData("30 0 L * *", "2017-03-30 23:59:59.9999000 +02:00", "2017-03-31 01:00:00 +03:00", false)]
[InlineData("30 0 L * *", "2017-03-30 23:59:59.9990000 +02:00", "2017-03-31 01:00:00 +03:00", false)]
[InlineData("30 0 L * *", "2017-03-30 23:59:59.9900000 +02:00", "2017-03-31 01:00:00 +03:00", false)]
[InlineData("30 0 L * *", "2017-03-30 23:59:59.9000000 +02:00", "2017-03-31 01:00:00 +03:00", false)]

[InlineData("30 0 L * *", "2017-03-31 01:00:00.0000001 +02:00", "2017-04-30 00:30:00 +03:00", true)]
public void GetNextOccurrence_HandleDST_WhenTheClockJumpsForward_And_FromIsAroundDST(string cronExpression, string fromString, string expectedString, bool inclusive)
{
var expression = CronExpression.Parse(cronExpression);

var fromInstant = GetInstant(fromString);
var expectedInstant = GetInstant(expectedString);

var executed = expression.GetNextOccurrence(fromInstant, JordanTimeZone, inclusive);

Assert.Equal(expectedInstant, executed);
Assert.Equal(expectedInstant.Offset, executed?.Offset);
}

[Theory]

// 2017-10-01 is date when the clock jumps forward from 1:59 am +10:30 standard time (ST) to 2:30 am +11:00 DST on Lord Howe.
// ________1:59 ST///invalid///2:30 DST________

Expand Down Expand Up @@ -2788,6 +2810,7 @@ private static DateTimeOffset GetInstant(string dateTimeOffsetString)
{
"yyyy-MM-dd HH:mm:ss zzz",
"yyyy-MM-dd HH:mm zzz",
"yyyy-MM-dd HH:mm:ss.fffffff zzz"
},
CultureInfo.InvariantCulture,
DateTimeStyles.None);
Expand Down
103 changes: 103 additions & 0 deletions tests/Cronos.Tests/DateTimeHelperFacts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Globalization;
using Xunit;

namespace Cronos.Tests
{
public class DateTimeHelperFacts
{
[Theory]

[InlineData("2017-03-30 23:59:59.0000000 +02:00", "2017-03-30 23:59:59.0000000 +02:00")]

[InlineData("2017-03-30 23:59:59.9000000 +03:00", "2017-03-30 23:59:59.0000000 +03:00")]
[InlineData("2017-03-30 23:59:59.9900000 +03:00", "2017-03-30 23:59:59.0000000 +03:00")]
[InlineData("2017-03-30 23:59:59.9990000 +03:00", "2017-03-30 23:59:59.0000000 +03:00")]
[InlineData("2017-03-30 23:59:59.9999000 +03:00", "2017-03-30 23:59:59.0000000 +03:00")]
[InlineData("2017-03-30 23:59:59.9999900 +03:00", "2017-03-30 23:59:59.0000000 +03:00")]
[InlineData("2017-03-30 23:59:59.9999990 +03:00", "2017-03-30 23:59:59.0000000 +03:00")]
[InlineData("2017-03-30 23:59:59.9999999 +03:00", "2017-03-30 23:59:59.0000000 +03:00")]

[InlineData("2017-03-30 23:59:59.0000001 +01:00", "2017-03-30 23:59:59.0000000 +01:00")]
[InlineData("2017-03-30 23:59:59.0000010 +01:00", "2017-03-30 23:59:59.0000000 +01:00")]
[InlineData("2017-03-30 23:59:59.0000100 +01:00", "2017-03-30 23:59:59.0000000 +01:00")]
[InlineData("2017-03-30 23:59:59.0001000 +01:00", "2017-03-30 23:59:59.0000000 +01:00")]
[InlineData("2017-03-30 23:59:59.0010000 +01:00", "2017-03-30 23:59:59.0000000 +01:00")]
[InlineData("2017-03-30 23:59:59.0100000 +01:00", "2017-03-30 23:59:59.0000000 +01:00")]
[InlineData("2017-03-30 23:59:59.1000000 +01:00", "2017-03-30 23:59:59.0000000 +01:00")]
public void FloorToSeconds_WorksCorrectlyWithDateTimeOffset(string dateTime, string expected)
{
var dateTimeOffset = GetDateTimeOffsetInstant(dateTime);
var expectedDateTimeOffset = GetDateTimeOffsetInstant(expected);

var flooredDateTimeOffset = DateTimeHelper.FloorToSeconds(dateTimeOffset);

Assert.Equal(expectedDateTimeOffset, flooredDateTimeOffset);
Assert.Equal(expectedDateTimeOffset.Offset, flooredDateTimeOffset.Offset);
}

[Theory]

[InlineData("2021-04-19 01:45:45.0000000", "2021-04-19 01:45:45.0000000", DateTimeKind.Unspecified)]

[InlineData("2021-04-19 01:45:45.9000000", "2021-04-19 01:45:45.0000000", DateTimeKind.Utc)]
[InlineData("2021-04-19 01:45:45.9900000", "2021-04-19 01:45:45.0000000", DateTimeKind.Utc)]
[InlineData("2021-04-19 01:45:45.9990000", "2021-04-19 01:45:45.0000000", DateTimeKind.Utc)]
[InlineData("2021-04-19 01:45:45.9999000", "2021-04-19 01:45:45.0000000", DateTimeKind.Utc)]
[InlineData("2021-04-19 01:45:45.9999900", "2021-04-19 01:45:45.0000000", DateTimeKind.Utc)]
[InlineData("2021-04-19 01:45:45.9999990", "2021-04-19 01:45:45.0000000", DateTimeKind.Utc)]
[InlineData("2021-04-19 01:45:45.9999999", "2021-04-19 01:45:45.0000000", DateTimeKind.Utc)]

[InlineData("2021-04-19 01:45:45.0000001", "2021-04-19 01:45:45.0000000", DateTimeKind.Unspecified)]
[InlineData("2021-04-19 01:45:45.0000010", "2021-04-19 01:45:45.0000000", DateTimeKind.Unspecified)]
[InlineData("2021-04-19 01:45:45.0000100", "2021-04-19 01:45:45.0000000", DateTimeKind.Unspecified)]
[InlineData("2021-04-19 01:45:45.0001000", "2021-04-19 01:45:45.0000000", DateTimeKind.Unspecified)]
[InlineData("2021-04-19 01:45:45.0010000", "2021-04-19 01:45:45.0000000", DateTimeKind.Unspecified)]
[InlineData("2021-04-19 01:45:45.0100000", "2021-04-19 01:45:45.0000000", DateTimeKind.Unspecified)]
[InlineData("2021-04-19 01:45:45.1000000", "2021-04-19 01:45:45.0000000", DateTimeKind.Unspecified)]
public void FloorToSeconds_WorksCorrectlyWithDateTimeUtc(string dateTime, string expected, DateTimeKind kind)
{
var dateTimeInstant = GetDateTimeUtcInstant(dateTime, kind);
var expectedDateTimeInstant = GetDateTimeUtcInstant(expected, kind);

var flooredDateTime = DateTimeHelper.FloorToSeconds(dateTimeInstant);

Assert.Equal(expectedDateTimeInstant, flooredDateTime);
Assert.Equal(expectedDateTimeInstant.Kind, flooredDateTime.Kind);
}

private static DateTimeOffset GetDateTimeOffsetInstant(string dateTimeOffsetString)
{
dateTimeOffsetString = dateTimeOffsetString.Trim();

var dateTime = DateTimeOffset.ParseExact(
dateTimeOffsetString,
new[]
{
"yyyy-MM-dd HH:mm:ss.fffffff zzz",
},
CultureInfo.InvariantCulture,
DateTimeStyles.None);

return dateTime;
}

private static DateTime GetDateTimeUtcInstant(string dateTimeString, DateTimeKind kind)
{
dateTimeString = dateTimeString.Trim();

var dateTime = DateTime.ParseExact(
dateTimeString,
new[]
{
"yyyy-MM-dd HH:mm:ss.fffffff",
},
CultureInfo.InvariantCulture,
DateTimeStyles.None);

dateTime = DateTime.SpecifyKind(dateTime, kind);

return dateTime;
}
}
}

0 comments on commit a831ac9

Please sign in to comment.