Skip to content

Commit

Permalink
Timezone issues (#128)
Browse files Browse the repository at this point in the history
* Fix Trigger issue with different Timezone
  • Loading branch information
Valentin-MF committed Apr 26, 2024
1 parent c927de4 commit da7cea5
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 121 deletions.
23 changes: 9 additions & 14 deletions MFiles.VAF.Extensions.Tests/Configuration/FrequencyTests.cs
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using MFiles.VAF.Extensions.ScheduledExecution;
using MFiles.VAF.Extensions.Tests.ScheduledExecution;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
Expand Down Expand Up @@ -43,17 +44,13 @@ public static IEnumerable<object[]> SplitTriggerType_Data()
{
yield return new object[]
{
new DateTime(2022, 10, 06, 20, 01, 00, DateTimeKind.Utc),
new DateTime(2022, 10, 06, 20, 30, 00, DateTimeKind.Utc)
new DateTimeOffset(2022, 10, 06, 20, 01, 00, TimeSpan.Zero),
new DateTimeOffset(2022, 10, 06, 20, 30, 00, TimeSpan.Zero)
};
yield return new object[]
{
TimeZoneInfo.ConvertTimeBySystemTimeZoneId
(
new DateTime(2022, 10, 26, 20, 31, 00, DateTimeKind.Local),
"GMT Standard Time"
),
new DateTime(2022, 10, 26, 20, 00, 00, DateTimeKind.Utc)
new DateTimeOffset(2022, 10, 06, 20, 01, 00, TimeSpan.FromHours(1)),
new DateTimeOffset(2022, 10, 06, 20, 30, 00, TimeSpan.FromHours(1))
};
}
public static IEnumerable<object[]> DaylightSaving_ClocksGoBackwards_Data()
Expand Down Expand Up @@ -111,7 +108,7 @@ DateTimeOffset expected

[DynamicData(nameof(SplitTriggerType_Data), DynamicDataSourceType.Method)]
[TestMethod]
public void SplitTriggerType(DateTime now, DateTime expected)
public void SplitTriggerType(DateTimeOffset now, DateTimeOffset expected)
{
var frequency = Newtonsoft.Json.JsonConvert.DeserializeObject<Frequency>(@"{
""Triggers"": [
Expand Down Expand Up @@ -210,11 +207,9 @@ public void SplitTriggerType(DateTime now, DateTime expected)
""TriggerTimeType"": ""UTC""
}
");
{
var nextRun = frequency.GetNextExecution(now);
Assert.IsNotNull(nextRun.Value);
Assert.AreEqual(expected, nextRun.Value.ToUniversalTime());
}
var nextRun = frequency.GetNextExecution(now);
Assert.IsNotNull(nextRun.Value);
Assert.AreEqual(expected, nextRun.Value.ToUniversalTime());
}

/// <summary>
Expand Down
@@ -1,12 +1,9 @@
using MFiles.VAF.Extensions;
using MFiles.VAF.Extensions.ScheduledExecution;
using MFiles.VAF.Extensions.ScheduledExecution;
using MFiles.VAF.Extensions.Tests.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MFiles.VAF.Extensions.Tests.ScheduledExecution
{
Expand All @@ -22,14 +19,15 @@ public class DailyTriggerTests
public void GetNextExecution
(
IEnumerable<TimeSpan> triggerTimes,
DateTime? after,
DateTime? expected
DateTimeOffset? after,
DateTimeOffset? expected,
TimeZoneInfo timezoneInfo
)
{
var execution = new DailyTrigger()
{
TriggerTimes = triggerTimes.ToList()
}.GetNextExecution(after);
TriggerTimes = triggerTimes.ToList(),
}.GetNextExecution(after, timezoneInfo);
Assert.AreEqual(expected?.ToUniversalTime(), execution?.ToUniversalTime());
}

Expand All @@ -39,72 +37,109 @@ public static IEnumerable<object[]> GetNextExecutionData()
yield return new object[]
{
new []{ new TimeSpan(17, 0, 0) }, // Scheduled for Wednesday at 5pm.
new DateTime(2021, 03, 17, 01, 00, 00), // Wednesday @ 1am
new DateTime(2021, 03, 17, 17, 00, 00), // Wednesday @ 5pm
new DateTimeOffset(new DateTime(2021, 03, 17, 01, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 1am
new DateTimeOffset(new DateTime(2021, 03, 17, 17, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 5pm
null,
};

// Execution later same day (one passed).
yield return new object[]
{
new []{ new TimeSpan(0, 0, 0), new TimeSpan(17, 0, 0) },
new DateTime(2021, 03, 17, 01, 00, 00), // Wednesday @ 1am
new DateTime(2021, 03, 17, 17, 00, 00), // Wednesday @ 5pm
new DateTimeOffset(new DateTime(2021, 03, 17, 01, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 1am
new DateTimeOffset(new DateTime(2021, 03, 17, 17, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 5pm
null,
};

// Execution later same day (multiple matching, returns first).
yield return new object[]
{
new []{ new TimeSpan(14, 0, 0), new TimeSpan(17, 0, 0) },
new DateTime(2021, 03, 17, 01, 00, 00), // Wednesday @ 1am
new DateTime(2021, 03, 17, 14, 00, 00), // Wednesday @ 2pm
new DateTimeOffset(new DateTime(2021, 03, 17, 01, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 1am
new DateTimeOffset(new DateTime(2021, 03, 17, 14, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 2pm
null,
};

// Execution next day.
yield return new object[]
{
new []{ new TimeSpan(14, 0, 0) },
new DateTime(2021, 03, 17, 15, 00, 00), // Wednesday @ 3pm
new DateTime(2021, 03, 18, 14, 00, 00), // Thursday @ 2pm
new DateTimeOffset(new DateTime(2021, 03, 17, 15, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 3pm
new DateTimeOffset(new DateTime(2021, 03, 18, 14, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Thursday @ 2pm
null,
};

// Execution next day (multiple items, returns first).
yield return new object[]
{
new []{ new TimeSpan(0, 0, 0), new TimeSpan(17, 0, 0) },
new DateTime(2021, 03, 17, 18, 00, 00), // Wednesday @ 6pm
new DateTime(2021, 03, 18, 00, 00, 00), // Thursday @ midnight
new DateTimeOffset(new DateTime(2021, 03, 17, 18, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 6pm
new DateTimeOffset(new DateTime(2021, 03, 18, 00, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Thursday @ midnight
null,
};

// No valid executions = null.
yield return new object[]
{
new TimeSpan[0],
new DateTime(2021, 03, 17, 18, 00, 00), // Wednesday @ 6pm
(DateTime?)null
new DateTimeOffset(new DateTime(2021, 03, 17, 18, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 6pm
(DateTimeOffset?)null,
null,
};

// Exact current time now.
yield return new object[]
{
new []{ new TimeSpan(17, 0, 0) },
new DateTime(2021, 03, 17, 17, 00, 00), // Wednesday @ 6pm
new DateTime(2021, 03, 17, 17, 00, 00), // Thursday @ midnight
new DateTimeOffset(new DateTime(2021, 03, 17, 17, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 6pm
new DateTimeOffset(new DateTime(2021, 03, 17, 17, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Thursday @ midnight
null,
};

// One minute past returns next day.
yield return new object[]
{
new []{ new TimeSpan(17, 0, 0) },
new DateTime(2021, 03, 17, 17, 01, 00), // Wednesday @ 6pm
new DateTime(2021, 03, 18, 17, 00, 00), // Thursday @ midnight
new DateTimeOffset(new DateTime(2021, 03, 17, 17, 01, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 6pm
new DateTimeOffset(new DateTime(2021, 03, 18, 17, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Thursday @ midnight
null,
};

// Daylight savings changes
yield return new object[]
{
new []{ new TimeSpan(17, 0, 0) },
new DateTime(2021, 03, 17, 17, 01, 00), // Wednesday @ 6pm
new DateTime(2021, 03, 18, 17, 00, 00), // Thursday @ midnight
new DateTimeOffset(new DateTime(2021, 03, 17, 17, 01, 00), TimeZoneInfo.Local.BaseUtcOffset), // Wednesday @ 6pm
new DateTimeOffset(new DateTime(2021, 03, 18, 17, 00, 00), TimeZoneInfo.Local.BaseUtcOffset), // Thursday @ midnight
null,
};

// Custom Timezone different day in UTC
var auTimezone = TimeZoneInfo.FindSystemTimeZoneById("W. Australia Standard Time");
yield return new object[]
{
new []{ new TimeSpan(10, 0, 0) },
TimeZoneInfo.ConvertTime(new DateTimeOffset(2024, 04, 16, 22, 00, 00, TimeSpan.Zero), auTimezone), // Tuesday @ 6am
TimeZoneInfo.ConvertTime(new DateTimeOffset(2024, 04, 17, 2, 00, 00, TimeSpan.Zero), auTimezone), // Wednesday @ 10am
auTimezone,
};

// Custom Timezone different day in local
yield return new object[]
{
new []{ new TimeSpan(10, 0, 0) },
TimeZoneInfo.ConvertTime(new DateTimeOffset(2024, 04, 16, 14, 00, 00,TimeSpan.Zero), auTimezone), // Tuesday @ 6am
TimeZoneInfo.ConvertTime(new DateTimeOffset(2024, 04, 17, 2, 00, 00, TimeSpan.Zero), auTimezone), // Wednesday @ 10am
auTimezone,
};

// UTC Timezone different day
yield return new object[]
{
new []{ new TimeSpan(10, 0, 0) },
new DateTimeOffset(2024, 04, 16, 14, 00, 00,TimeSpan.Zero), // Tuesday @ 6am
new DateTimeOffset(2024, 04, 17, 10, 00, 00, TimeSpan.Zero), // Wednesday @ 10am
TimeZoneInfo.Utc,
};
}
}
Expand Down
@@ -1,17 +1,21 @@
using MFiles.VAF.Extensions;
using MFiles.VAF.Extensions.ScheduledExecution;
using MFiles.VAF.Extensions.ScheduledExecution;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace MFiles.VAF.Extensions.Tests.ScheduledExecution
{
[TestClass]
public class ScheduleTests
{
public ScheduleTests()
{
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
}

[TestMethod]
public void ScheduleIsEnabledByDefault()
{
Expand All @@ -24,13 +28,53 @@ public void ScheduleTriggersAreNotNullByDefault()
Assert.IsNotNull(new Schedule().Triggers);
}

[TestMethod]
// https://community.m-files.com/groups-1602070992/developers/f/developer-forum/9824/trouble-with-daily-scheduling-repeating
public void ForumPost9824()
{
using (var x = new LocalTimeZoneInfoMocker("FLE Standard Time"))
{
var schedule = new Schedule()
{
Enabled = true,
Triggers = new List<Trigger>()
{
new DailyTrigger()
{
TriggerTimes = new List<TimeSpan>()
{
TimeSpan.FromHours(1)
}
}
},
RunOnVaultStartup = false
};

Assert.AreEqual
(
// Lets say we run just after the time above.
new DateTime(2024, 01, 02, 01, 00, 00, DateTimeKind.Local),
// We expect to run the next day at 1am local time.
schedule.GetNextExecution(new DateTime(2024, 01, 01, 01, 00, 01, DateTimeKind.Local))
);

Assert.AreEqual
(
// Lets say we run just after the time above but without an explicit datetimekind.
new DateTime(2024, 01, 02, 01, 00, 00),
// We expect to run the next day at 1am local time.
schedule.GetNextExecution(new DateTime(2024, 01, 01, 01, 00, 01))
);
}
}

[TestMethod]
[DynamicData(nameof(GetNextExecutionData_UTC), DynamicDataSourceType.Method)]
public void GetNextExecution
public void GetNextExecution_UTC
(
IEnumerable<TriggerBase> triggers,
DateTime? after,
DateTime? expected
DateTimeOffset? after,
DateTimeOffset? expected
)
{
Assert.AreEqual
Expand All @@ -42,7 +86,8 @@ public void GetNextExecution
Triggers = triggers
.Select(t => new Trigger(t))
.Where(t => t != null)
.ToList()
.ToList(),
TriggerTimeType = TriggerTimeType.Utc,
}.GetNextExecution(after)
);
}
Expand Down Expand Up @@ -141,8 +186,8 @@ public void GetNextExecution_GMT
public void GetNextExecution_NotEnabled
(
IEnumerable<TriggerBase> triggers,
DateTime? after,
DateTime? expected
DateTimeOffset? after,
DateTimeOffset? expected
)
{
// We use the same data as the GetNextExecution, but
Expand Down Expand Up @@ -177,8 +222,8 @@ public static IEnumerable<object[]> GetNextExecutionData_UTC()
}.ToList()
}
},
new DateTime(2021, 03, 17, 01, 00, 00, DateTimeKind.Utc), // Wednesday @ 1am
new DateTime(2021, 03, 17, 17, 00, 00, DateTimeKind.Utc), // Wednesday @ 5pm
new DateTimeOffset(2021, 03, 17, 01, 00, 00, TimeSpan.Zero), // Wednesday @ 1am
new DateTimeOffset(2021, 03, 17, 17, 00, 00, TimeSpan.Zero), // Wednesday @ 5pm
};

// Multiple triggers returns earliest.
Expand All @@ -199,16 +244,16 @@ public static IEnumerable<object[]> GetNextExecutionData_UTC()
}.ToList()
}
},
new DateTime(2021, 03, 17, 01, 00, 00, DateTimeKind.Utc), // Wednesday @ 1am
new DateTime(2021, 03, 17, 12, 00, 00, DateTimeKind.Utc), // Wednesday @ 5pm
new DateTimeOffset(2021, 03, 17, 01, 00, 00, TimeSpan.Zero), // Wednesday @ 1am
new DateTimeOffset(2021, 03, 17, 12, 00, 00, TimeSpan.Zero), // Wednesday @ 5pm
};

// No triggers = null.
yield return new object[]
{
new TriggerBase[0],
new DateTime(2021, 03, 17, 01, 00, 00, DateTimeKind.Utc), // Wednesday @ 1am
(DateTime?)null
new DateTimeOffset(2021, 03, 17, 01, 00, 00, TimeSpan.Zero), // Wednesday @ 1am
(DateTimeOffset?)null
};

// Trigger at exact current time returns now.
Expand All @@ -223,8 +268,8 @@ public static IEnumerable<object[]> GetNextExecutionData_UTC()
}.ToList()
}
},
new DateTime(2021, 03, 17, 17, 00, 00, DateTimeKind.Utc), // Wednesday @ 1am
new DateTime(2021, 03, 17, 17, 00, 00, DateTimeKind.Utc)
new DateTimeOffset(2021, 03, 17, 17, 00, 00, TimeSpan.Zero), // Wednesday @ 1am
new DateTimeOffset(2021, 03, 17, 17, 00, 00, TimeSpan.Zero)
};
}

Expand Down Expand Up @@ -278,8 +323,8 @@ public static IEnumerable<object[]> GetNextExecutionData_GMT()
}.ToList()
}
},
new DateTimeOffset(2022, 10, 30, 02, 00, 00, 0, TimeSpan.Zero), // This is 0100 BST
new DateTimeOffset(2022, 10, 30, 02, 30, 00, 0, TimeSpan.Zero), // So it should run at 0030UTC / 0130 BST
new DateTimeOffset(2022, 10, 30, 00, 00, 00, 0, TimeSpan.FromHours(1)), // This is 0100 BST
new DateTimeOffset(2022, 10, 30, 02, 30, 00, 0, TimeSpan.Zero), // So it should run at 0230 GMT
};

// Just before clocks go backwards.
Expand Down

0 comments on commit da7cea5

Please sign in to comment.