您的答案非常接近。有几个问题:
您假设给定时区的今天与 UTC 的今天相同。根据时区的不同,这些日期可能会有所不同。例如,2019 年 10 月 18 日凌晨 1 点(世界标准时间)是 2019 年 10 月 17 日美国中部时间晚上 8:00。
如果您围绕“今天发生了吗”进行设计,您可能会跳过合法的事件。相反,只考虑“未来下一次发生的事情是什么”会更容易。
您没有采取任何措施来处理无效或不明确的本地时间,例如在 DST 开始或结束以及标准时间更改时发生的情况。这对于重复发生的事件很重要。
接下来是代码:
// Get the current UTC time just once at the start
var utcNow = DateTimeOffset.UtcNow;
foreach (var schedule in schedules)
{
// schedule notification only if not already scheduled in the future
if (schedule.LastScheduledDateTime == null || schedule.LastScheduledDateTime.Value < utcNow)
{
// Get the time zone for this schedule
var tz = TimeZoneInfo.FindSystemTimeZoneById(schedule.User.TimeZone);
// Decide the next time to run within the given zone's local time
var nextDateTime = nowInZone.TimeOfDay <= schedule.PreferredTime
? nowInZone.Date.Add(schedule.PreferredTime)
: nowInZone.Date.AddDays(1).Add(schedule.PreferredTime);
// Get the point in time for the next scheduled future occurrence
var nextOccurrence = nextDateTime.ToDateTimeOffset(tz);
// Do the scheduling
BackgroundJob.Schedule<INotificationService>(x => x.Notify(schedule.CompanyUserID), nextOccurrence);
// Update the schedule
schedule.LastScheduledDateTime = nextOccurrence;
}
}
我想你会发现如果你让你的代码和数据更加清晰LastScheduledDateTime
a DateTimeOffset?
代替DateTime?
。上面的代码假设了这一点。如果你don't想要,那么你可以将最后一行更改为:
schedule.LastScheduledDateTime = nextOccurrence.UtcDateTime;
还要注意使用ToDateTimeOffset
,这是一种扩展方法。将其放在某个静态类中。其目的是创建一个DateTimeOffset
from a DateTime
考虑特定时区。在处理不明确和无效的本地时间时,它应用了典型的调度问题。 (我上次发布了关于它的在另一个 Stack Overflow 答案中如果您想阅读更多内容。)这是实现:
public static DateTimeOffset ToDateTimeOffset(this DateTime dt, TimeZoneInfo tz)
{
if (dt.Kind != DateTimeKind.Unspecified)
{
// Handle UTC or Local kinds (regular and hidden 4th kind)
DateTimeOffset dto = new DateTimeOffset(dt.ToUniversalTime(), TimeSpan.Zero);
return TimeZoneInfo.ConvertTime(dto, tz);
}
if (tz.IsAmbiguousTime(dt))
{
// Prefer the daylight offset, because it comes first sequentially (1:30 ET becomes 1:30 EDT)
TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
TimeSpan offset = offsets[0] > offsets[1] ? offsets[0] : offsets[1];
return new DateTimeOffset(dt, offset);
}
if (tz.IsInvalidTime(dt))
{
// Advance by the gap, and return with the daylight offset (2:30 ET becomes 3:30 EDT)
TimeSpan[] offsets = { tz.GetUtcOffset(dt.AddDays(-1)), tz.GetUtcOffset(dt.AddDays(1)) };
TimeSpan gap = offsets[1] - offsets[0];
return new DateTimeOffset(dt.Add(gap), offsets[1]);
}
// Simple case
return new DateTimeOffset(dt, tz.GetUtcOffset(dt));
}
(在您的情况下,类型始终是未指定的,因此您可以根据需要删除第一个检查,但我更愿意保持其功能齐全,以防其他用途。)
顺便说一下,你不需要if (!schedules.HasAny()) { return; }
查看。实体框架已经测试了期间的更改SaveChangesAsync
,如果没有,则不执行任何操作。