You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用波斯历(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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 09:05:06