如何使用波斯历(Jalali)触发Hangfire循环任务?
在Hangfire中实现波斯历(Jalali/Shamsi)循环任务调度
Hangfire默认基于公历(Gregorian)进行任务调度,直接修改线程区域设置无法改变核心调度逻辑,以下是无需手动转换日期的可行方案:
核心思路
利用成熟的波斯历日期库(如NodaTime)处理波斯历的日期计算逻辑,结合Hangfire的动态调度或自定义触发器实现需求。
步骤1:安装波斯历支持库
推荐使用NodaTime,它对非公历日历的支持完善,可通过NuGet安装:
Install-Package NodaTime
步骤2:实现波斯历时间计算逻辑
以“波斯历每月最后一天”和“波斯历每周第一天”为例,编写时间计算方法:
using NodaTime; using NodaTime.Calendars; public static class JalaliScheduleHelper { private static readonly DateTimeZone TehranTimeZone = DateTimeZoneProviders.Tzdb["Asia/Tehran"]; private static readonly CalendarSystem JalaliCalendar = CalendarSystem.GetPersianCalendar(); // 获取下一次波斯历每月最后一天的触发时间(示例:凌晨2点) public static DateTimeOffset GetNextJalaliMonthlyLastDay(LocalTime triggerTime = default) { if (triggerTime == default) triggerTime = new LocalTime(2, 0); var nowInstant = SystemClock.Instance.GetCurrentInstant(); var localNow = nowInstant.InZone(TehranTimeZone); var jalaliNow = localNow.WithCalendar(JalaliCalendar); // 获取当前波斯历月份的最后一天 var lastDayOfCurrentMonth = jalaliNow.Date.With(DateAdjusters.EndOfMonth); // 若当前日期已过本月最后一天,则取下个月的最后一天 var targetDate = jalaliNow.Date <= lastDayOfCurrentMonth ? lastDayOfCurrentMonth : lastDayOfCurrentMonth.PlusMonths(1).With(DateAdjusters.EndOfMonth); var targetLocalTime = targetDate.At(triggerTime); return targetLocalTime.InZone(TehranTimeZone).ToDateTimeOffset(); } // 获取下一次波斯历每周第一天的触发时间(波斯历每周第一天为周六) public static DateTimeOffset GetNextJalaliWeekFirstDay(LocalTime triggerTime = default) { if (triggerTime == default) triggerTime = new LocalTime(2, 0); var nowInstant = SystemClock.Instance.GetCurrentInstant(); var localNow = nowInstant.InZone(TehranTimeZone); var jalaliNow = localNow.WithCalendar(JalaliCalendar); // 波斯历每周第一天是周六(IsoDayOfWeek.Saturday) var daysToNextSaturday = ((int)IsoDayOfWeek.Saturday - (int)jalaliNow.DayOfWeek + 7) % 7; // 若当天已是周六,且当前时间已过触发点,则取下周周六 if (daysToNextSaturday == 0 && localNow.TimeOfDay > triggerTime) { daysToNextSaturday = 7; } var targetDate = jalaliNow.Date.PlusDays(daysToNextSaturday); var targetLocalTime = targetDate.At(triggerTime); return targetLocalTime.InZone(TehranTimeZone).ToDateTimeOffset(); } }
步骤3:集成到Hangfire
方案A:动态递推调度(简单易实现)
每次任务执行完成后,自动调度下一次任务:
// 初始调度第一次任务 BackgroundJob.Schedule(() => ExecuteYourTask(), JalaliScheduleHelper.GetNextJalaliMonthlyLastDay()); // 任务执行方法 public void ExecuteYourTask() { // 这里编写你的任务逻辑 // 调度下一次任务 BackgroundJob.Schedule(() => ExecuteYourTask(), JalaliScheduleHelper.GetNextJalaliMonthlyLastDay()); }
方案B:自定义Hangfire触发器(更贴合RecurringJob模式)
实现Hangfire的ITrigger接口,将波斯历逻辑封装为触发器:
public class JalaliMonthlyLastDayTrigger : ITrigger { private readonly LocalTime _triggerTime; public JalaliMonthlyLastDayTrigger(LocalTime triggerTime = default) { _triggerTime = triggerTime == default ? new LocalTime(2, 0) : triggerTime; } public DateTimeOffset? GetNextOccurrence(DateTimeOffset now, TimeSpan? timeZoneOffset) { var nextTime = JalaliScheduleHelper.GetNextJalaliMonthlyLastDay(_triggerTime); return nextTime > now ? nextTime : null; } } // 添加循环任务 RecurringJob.AddOrUpdate<YourJobService>( job => job.ExecuteYourTask(), new JalaliMonthlyLastDayTrigger() );
为什么修改区域设置无效?
Hangfire的核心调度逻辑基于UTC时间和公历DateTime/DateTimeOffset,修改线程CurrentCulture仅影响任务执行时的文化(如格式化输出),不会改变调度时间的计算规则,因此无法实现波斯历调度。
内容的提问来源于stack exchange,提问作者Aisa Asghari
相关产品推荐
相关产品推荐

