根据时区安排在一天中的特定时间进行hangfire作业 [英] Schedule a hangfire job at specific time of the day based on time zone

查看:562
本文介绍了根据时区安排在一天中的特定时间进行hangfire作业的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在hangfire中,我可以通过延迟调用方法

In hangfire i can schedule a job to run at a specific time by calling a method with delay

BackgroundJob.Schedule(
           () => Console.WriteLine("Hello, world"),
           TimeSpan.FromDays(1));

我有一张桌子,上面有以下信息

I have a table with following information

    User           Time              TimeZone
    --------------------------------------------------------
    User1          08:00:00           Central Standard Time
    User1          13:00:00           Central Standard Time
    User2          10:00:00           Eastern Standard Time
    User2          17:00:00           Eastern Standard Time
    User3          13:00:00           UTC

鉴于此信息,我想为每个要根据其时区在配置的时间每天发送通知的用户

Given this information, For every user i want to send notice every day at configured time based on their time zone

ScheduleNotices方法将在世界标准时间每天凌晨12点运行.此方法将安排当天需要运行的作业.

ScheduleNotices method will run everyday at 12 AM UTC. This method will schedule jobs that needs to run that day.

 public async Task ScheduleNotices()
 {
       var schedules = await _dbContext.GetSchedules().ToListAsync();
       foreach(var schedule in schedules)
       {
          // Given schedule information how do i build enqueueAt that is timezone specific
          var enqueuAt = ??;
          BackgroundJob.Schedule<INotificationService>(x => x.Notify(schedule.User), enqueuAt );
       }
 }

更新1
Schedules表信息不断变化.用户可以选择添加/删除时间.我可以创建一个运行于每分钟的循环作业(分钟是hangfire支持的最小单位),然后该循环作业可以查询Schedules表并根据时间表发送通知.
但是,数据库交互太多.因此,我将只有一个重复的作业ScheduleNotices,它将在上午12点(一天一次)运行,并将作业安排在接下来的24小时内.在这种情况下,他们所做的任何更改将从第二天开始生效.

Update 1
The Schedules table information keep changing. User has option to add/delete time. I can create a recurring job that runs every minuet (minute is minimum unit hangfire supports) and then this recurring job can query Schedules table and send notices based the time schedule.
However that too much database interaction.So instead i will have only one recurring job ScheduleNotices that will run at 12 AM (once in a day) and will schedule jobs for next 24 hours. In this case any changes they make will be effective from next day.

推荐答案

您的答案非常接近.有几个问题:

Your answer was pretty close. There were a few problems:

  • 您假设给定时区中的今天与UTC中的今天是同一日期.根据时区,这些日期可能会有所不同.例如,2019年10月18日世界标准时间凌晨1点,美国中部时间2019年10月17日下午8:00.

  • You were assuming that today in a given time zone was the same date as today in UTC. Depending on time zone, these could be different days. For example, 1 AM UTC on 2019-10-18, is 8:00 PM in US Central Time on 2019-10-17.

如果您围绕今天是否发生过"进行设计,则可能会跳过合法事件.相反,想一想下一个未来发生的事情"要容易得多.

If you design around "has it happened yet today", you'll potentially skip over legitimate occurrences. Instead, it's much easier to just think about "what is the next future occurrence".

您没有采取任何措施来处理无效或含糊的当地时间,例如DST的开始或结束以及标准时间的变化.这对于重复发生的事件很重要.

You weren't doing anything to handle invalid or ambiguous local times, such as occur with the start or end of DST and with changes in standard time. This is important for recurring events.

继续执行代码:

// 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设置为DateTimeOffset?而不是DateTime?,我认为您会发现代码和数据更加清晰.上面的代码假定了这一点.如果您不想 ,则可以将最后一行更改为:

I think you'll find that your code and data are much clearer if you make your LastScheduledDateTime a DateTimeOffset? instead of a DateTime?. The above code assumes that. If you don't want to, then you can change that last line to:

        schedule.LastScheduledDateTime = nextOccurrence.UtcDateTime;

还请注意ToDateTimeOffset的使用,这是一种扩展方法.将其放在某个地方的静态类中.其目的是在考虑特定时区的情况下从DateTime创建DateTimeOffset.在处理模棱两可和无效的本地时间时,它会应用典型的调度问题. (如果您想了解更多信息,我上次在其他Stack Overflow答案中对此发表了文章.)以下是实现: /p>

Also note the use of ToDateTimeOffset, which is an extension method. Place it in a static class somewhere. Its purpose is to create a DateTimeOffset from a DateTime taking a specific time zone into account. It applies typical scheduling concerns when dealing with ambiguous and invalid local times. (I last posted about it in this other Stack Overflow answer if you want to read more.) Here is the implementation:

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));
}

(对于您而言,这种类型始终是未指定的,因此您可以根据需要删除该第一项检查,但是我更喜欢在其他用途​​的情况下保持其全部功能.)

(In your case, the kind is always unspecified, so you could remove that first check if you want to, but I prefer to keep it fully functional in case of other usage.)

顺便说一句,您不需要if (!schedules.HasAny()) { return; }检查.实体框架已经在SaveChangesAsync期间测试了更改,如果没有更改,则不执行任何操作.

Incidentally, you don't need the if (!schedules.HasAny()) { return; } check. Entity Framework already tests for changes during SaveChangesAsync, and does nothing if there aren't any.

这篇关于根据时区安排在一天中的特定时间进行hangfire作业的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆