基于HangFire任务调试中心-Cron表达式日期
基于HangFire开发一个后台任务调度中心,完成实现与业务系统分隔执行。
定时任务
新建定时任务
在开发过程中,由于没有很好的理解Cron表达式的应用,在获取定时任务时,创建时间和任务最后的执行时间,有很严重的偏差,不得其原因为何!如下图所示。
创建时间是上午8点,执行时间是当天的凌晨1点?
分析原因之前,了解一下Cron表达式:
Cron 表达式是用于定义固定时间、日期和间隔的掩码。掩码由第二个(可选)、分钟、小时、月份中的某一天、月份和星期几字段组成。所有字段都允许您指定多个值,如果所有字段都包含匹配的值,则任何给定的日期/时间都将满足指定的 Cron 表达式。
Allowed values Allowed special characters Comment ┌───────────── second (optional) 0-59 * , - / │ ┌───────────── minute 0-59 * , - / │ │ ┌───────────── hour 0-23 * , - / │ │ │ ┌───────────── day of month 1-31 * , - / L W ? │ │ │ │ ┌───────────── month 1-12 or JAN-DEC * , - / │ │ │ │ │ ┌───────────── day of week 0-6 or SUN-SAT * , - / # L ? Both 0 and 7 means SUN │ │ │ │ │ │ * * * * * *
或理解如下
* * * * * *
- - - - - -
| | | | | |
| | | | | +----- 星期中星期几 (0 - 7) [星期天 = 0或7]
| | | | +------- 月份 (1 - 12)
| | | +--------- 一个月中的第几天 (1 - 31)
| | +----------- 小时 (0 - 23)
| +------------- 分钟 (0 - 59)
+--------------- 秒 (0 - 59)
推荐一个Cron表达式的Nuget包,
PM> Install-Package Cronos
github上地址:GitHub - HangfireIO/Cronos:一个功能齐全的 .NET 库,用于处理 Cron 表达式。在构建时时考虑了时区,并直观地处理夏令时转换
using Cronos; CronExpression expression = CronExpression.Parse("* * * * *"); DateTime? nextUtc = expression.GetNextOccurrence(DateTime.UtcNow);
说明:将包含给定时间之后的 UTC 中的下一个匹配项,或无法访问时的值(例如,2 月 30 日)。如果给出无效的 Cron 表达式,则会引发异常。nextUtc
null
CronFormatException
从上述描述中可以知道 Cron使用的时间是UTC,接着了解一下UTC:
在.NET中,`DateTime.UtcNow` 是 `System.DateTime` 类的一个属性,它返回当前的协调世界时(UTC,Universal Time Coordinated)时间。UTC 是一个全球统一的时间标准,它与格林尼治标准时间(GMT)相同,不考虑夏令时(DST)。
`DateTime.UtcNow` 属性的特点是:
- 它总是返回 UTC 时间,不考虑调用它的计算机的本地时区设置。
- 它返回的时间是不带时区信息的,也就是说,它不包含关于时区的任何信息,只是简单地返回当前的 UTC 时间。
如果你看到 `DateTime.Now` 或者 `DateTime.UtcNow`,它们的区别在于:
- `DateTime.Now` 返回的是调用它的计算机的本地时间,它考虑了本地时区和夏令时设置。
- `DateTime.UtcNow` 返回的是 UTC 时间,不考虑任何时区转换。
在进行日期和时间的编程时,使用 `DateTime.UtcNow` 可以避免时区引起的混淆,尤其是在涉及跨时区通信或存储时间戳的应用程序中。
如何将UTC转成本地时间?
CronExpression expression = CronExpression.Parse("* * * * *"); DateTimeOffset? next = expression.GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local); var nextLocalTime = next?.DateTime;
或
使用`UtcNow` 是 `DateTime` 结构的一个静态属性,它返回当前的协调世界时(UTC)时间。UTC 是一种标准的时间参考,不受时区的影响,被广泛用于跨时区的时间表示和计算。
`DateTime.UtcNow` 返回的是一个 `DateTime` 对象,表示当前的 UTC 时间。这个对象包含了年、月、日、时、分、秒和毫秒等时间信息。
使用 `DateTime.UtcNow` 可以获取当前的 UTC 时间,而不受本地时区的影响。如果需要将 UTC 时间转换为其他时区的本地时间,可以使用 `TimeZoneInfo` 类的方法进行转换。
示例代码如下:
DateTime utcTime = DateTime.UtcNow; Console.WriteLine("Current UTC time: " + utcTime); TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai"); DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(utcTime, timeZone); Console.WriteLine("Current local time in Shanghai: " + localTime);
在上述代码中,我们首先使用 `DateTime.UtcNow` 获取当前的 UTC 时间,并将其存储在 `utcTime` 变量中。然后,我们使用 `TimeZoneInfo.FindSystemTimeZoneById` 方法获取 "Asia/Shanghai" 时区的信息,并使用 `TimeZoneInfo.ConvertTimeFromUtc` 方法将 UTC 时间转换为上海的本地时间。
示例代码,UTC转成本地时间:
public async Task<List<HttpJobDescriptorDto>> GetAllRecurringJobsList(string? jobName) { var getRecurringJobs = (await _repository.GetQueryableAsync()).Where(p => p.JobType == ((int)HangFireJobTypeEnum.RecurringJobs).ToString()).ToList();//过滤定时任务列表 var dtos = ObjectMapper.Map<List<HttpJobDescriptor>, List<HttpJobDescriptorDto>>(getRecurringJobs); if (dtos.Count > 0) { var jobList = GetAllRecurringJobs(); var _query = from job in dtos join hangfirejob in jobList on job.JobName equals hangfirejob.Id into tempJob from hangfire in tempJob.DefaultIfEmpty() where hangfire != null select new HttpJobDescriptorDto() { Id = job.Id,//主键ID HttpUrl = job.HttpUrl, JobId =string.IsNullOrEmpty(hangfire.LastJobId) ? 0 : int.Parse(hangfire.LastJobId),//最近执行的任务的ID JobName = job.JobName, JobType = ((int)HangFireJobTypeEnum.RecurringJobs).ToString(),//标识定时任务 StateName = string.IsNullOrEmpty(hangfire.LastJobId) ? "任务新建,启动中" : hangfire.LastJobState, HttpMethod = job.HttpMethod,//参数类型 Cron = job.Cron, LastJobState = hangfire.LastJobState,//最近一次运行状态 LastExecution = hangfire.LastExecution.HasValue ? TimeZoneInfo.ConvertTimeFromUtc(hangfire.LastExecution.Value, TimeZoneInfo.Local):null, //LastExecution = CronExpression.Parse(job.Cron).GetNextOccurrence(DateTimeOffset.Now, TimeZoneInfo.Local)?.DateTime, //LastExecution = hangfire.LastExecution,//最近一次运行时间,用的是协调世界时(UTC)时间,不受时区的影响 CreationTime = job.CreationTime,//创建时间 Remark = job.Remark, TimeSpanFromSeconds=job.TimeSpanFromSeconds, }; dtos = _query.WhereIf(!string.IsNullOrEmpty(jobName), p => p.JobName.Contains(jobName)).ToList(); } return dtos; }