【问题标题】:Compensating for TimeZone offsets while running Quartz jobs在运行 Quartz 作业时补偿 TimeZone 偏移
【发布时间】:2013-06-09 08:42:08
【问题描述】:

我有一个独特的问题,我的石英作业调度程序实现是使用quartz.net 代码库ver 2.0.1 构建的,最近发现在运行和执行作业时忽略了时区和UTC 偏移量。这是这个版本的quartz.net 中的一个继承错误,现在更新到2.1.1 版本超出了范围,所以我写了一个快速而肮脏的方法来使用这个算法计算偏移量:

(ServerTime - ClientTime) - TargetTime = New_TargetTime_With_Offset

这里的想法是客户,据说在纽约市,在下午 5:00 做一份工作,并希望它在下午 2:00 运行。服务器(此应用程序和作业服务器运行的地方)当前时间是下午 2:00,因此我们使用客户端时间和服务器时间来获取偏移量并将该偏移量应用于目标时间,即作业应该运行的时间。

我的问题是,这感觉像是一种计算日期的方法,但似乎可以完成这项工作。有没有更好/更可靠的方法来做这个日期数学?在边缘情况下这似乎也有问题,我错过了什么?

这里是实现:

    /// <summary>
    /// Takes three dates and returns the adjusted hour value.
    /// All date data is ignored except for the hour. 
    /// </summary>
    /// <param name="serverTime"></param>
    /// <param name="clientTime"></param>
    /// <param name="targetTime"></param>
    /// <returns></returns>
    private static DateTime OutputDate(DateTime serverTime, DateTime clientTime, DateTime targetTime)
    {
        DateTime? output = null;
        TimeSpan? dateDiff;

        if (serverTime < clientTime)
        {
            dateDiff = (clientTime - serverTime);
        }
        else
        {
            dateDiff = (serverTime - clientTime);
        }

        output = (targetTime - dateDiff);

        return output.Value;
    }

这里有两个利用它的例子:

    /// <summary>
    /// -5 Offset (NYC)
    /// </summary>
    /// <returns></returns>
    private static Int32 ZoneTest001()
    {
        var targetTime = DateTime.Parse("6/12/2013 5:00PM");  // NYC (est) [The time the report should be received in NYC]
        var clientTime = DateTime.Parse("6/12/2013 5:00PM");   // NYC (est) [The time of the client when the report is created (now) ]
        var serverTime = DateTime.Parse("6/12/2013 2:00PM");  // SEA (pst) [The time of the app server when the report is created (now) ]

        //
        // NYC Wants to send a report at 5:00pm EST
        // The server time will be 2:00pm PST
        // The client time will be 5:00pm EST

        double outputHour = 0;   // should end up as 2:00pm PST

        //
        // 1) Get offset (diff between client & server time)
        // 2) Subtract offset from "targetTime"
        // 3) Set the report to be sent at the new hour value.

        outputHour = OutputDate(serverTime, clientTime, targetTime).Hour;

        return (int)outputHour;

    }

    /// <summary>
    /// +5 Offset (India)
    /// </summary>
    /// <returns></returns>
    private static Int32 ZoneTest002()
    {
        var targetTime = DateTime.Parse("6/12/2013 5:00PM"); // IND (ist)
        var clientTime = DateTime.Parse("6/12/2013 9:00AM");  // IND (ist)
        var serverTime = DateTime.Parse("6/12/2013 2:00PM"); // SEA (pst)

        //
        // INDIA Wants to send a report at 5:00pm IST
        // The server time will be 2:00pm PST
        // The client time will be 9:00am PST

        double outputHour = 0;   // should end up as 2:00pm PST
        outputHour = OutputDate(serverTime, clientTime, targetTime).Hour;

        return (int)outputHour;

    }

谢谢。

【问题讨论】:

  • 你考虑过使用 DateTimeOffset 类型吗?
  • 似乎使用 DateTimeOffset(DateTime, TimeSpan) 构造函数会稍微清理我的代码,但这仍然不能真正回答我原来的问题。
  • 我遇到了一个非常相似的问题,并通过使用 Noda-Time 解决了它。 stackoverflow.com/questions/7553997/…
  • 感谢该链接,Stephan - Noda-Time 似乎是补充石英并扩展 .net DateTime 对象缺少的功能的好方法。很棒的发现;相当的编程宝石。我要使用它:)
  • @StephenKennedy:另一条注释;因为我是 Jon Skeet 的忠实粉丝,在阅读了你的帖子和他对 noda-time 的认可之后,我不得不试一试,我现在正在这样做。很高兴我问了这个问题,并感谢像你这样的人,马特约翰逊和乔恩 - 干杯!

标签: c# datetime quartz-scheduler quartz.net timespan


【解决方案1】:

实际上你错过了很多。

  1. 时区偏移不是恒定的。许多时区为夏令时切换偏移量(又名“夏令时”)。因此,当您根据每个位置(服务器、客户端、目标)的“现在”计算偏移量时,这仅反映 当前 偏移量。

  2. 在任何有 DST 的时区中,时钟向前滚动时会丢失一个小时,而时钟向后滚动时会有一个重复的小时。如果您处理的是当地时间,并且计划的事件属于不明确的时间段,则您无法确定运行它的实际时间。为了消除歧义,您需要被告知对应的偏移量是多少,或者你需要处理UTC。

  3. 如果您要从一个时区转换到另一个时区,您需要处理时区,而不仅仅是它们的偏移量。在 .Net 中,您可以使用内置的 Windows 时区数据库和相应的TimeZoneInfo 类。或者,您可以使用更标准的 IANA 时区数据库,以及 Noda Time 等库。

  4. 在使用 DateTime 类型时,要非常小心 .Kind 属性的设置。许多函数在处理不同类型时具有不同的行为。改用DateTimeOffset 类型会更安全、更有用。

  5. 您真的不应该依赖于运行代码的服务器的时区。服务器代码应该是时区中立的。您唯一应该涉及DateTime.NowTimeZoneInfo.Local 或任何类似功能的地方是桌面移动 应用程序。服务器代码应该只依赖于 UTC。

  6. 我真的不明白为什么您的 OutputDate 方法中有可以为空的值。没有理由这样做。此外,您实际上是在获取差异的绝对值 - 这正在降低方向性。时区偏移确实是有方向的,因此您可能会在当前的实现中得到无效的结果。

  7. 我查看了 Quartz.net API,似乎他们更喜欢您在 UTC 中安排事件时间。这是一件非常好的事情,因为 UTC 不存在歧义问题。从Quartz.Net Tutorial 来看,trigger.StartTimeUtc 显然是 UTC DateTime。既然你说你不能使用最新版本,我也检查了他们旧的 1.0 API 文档,它仍然是 UTC。

    更新: Quartz.Net 2.5 和更高版本可以更好地处理时区。详情请见#317

让我们将所有这些放在一起用于您的示例用例。纽约市的一位客户希望在当地时区的下午 2:00 运行作业。服务器的时区无关紧要,他创建作业的时间也是如此。

// June 6, 2013 2:00 PM  Kind = Unspecified
DateTime dt = new DateTime(2013, 6, 13, 14, 0, 0);

// This is the correct Windows time zone for New York
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

// Get the time in UTC - The kind matters here.
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(dt, tz);

// Feed it to a quartz event trigger
trigger.StartTimeUtc = utc;

当我在第三步将时间转换为 UTC 时,如果时间不明确,.Net 将假定您想要 标准 时间而不是 daylight 时间.如果您想更具体,您必须检查是否有歧义,然后询问您的用户他们想要的两个当地时间中的哪一个。然后你必须使用DateTimeOffset 来区分它们。如果你认为你可能需要这个,请告诉我,我可以制作一个样品,但它有点复杂。

为了更好地衡量,如果您想使用带有Noda Time 的 IANA 时区,它看起来像这样:

LocalDateTime ldt = new LocalDateTime(2013, 6, 13, 14, 0);
DateTimeZone tz = DateTimeZoneProviders.Tzdb["America/New_York"];
ZonedDateTime zdt = ldt.InZoneLeniently(tz);
trigger.StartTimeUtc = zdt.ToDateTimeUtc();

InZoneLeniently 方法将给出与上述代码相同的行为。但如果需要,您还可以指定其他选项。

哦,这并不重要,但印度是+5:30,而不是+5

【讨论】:

  • 首先,感谢您的精彩回答;所有伟大和相关的信息。我现在可以清楚地看到日期代码中的缺陷;这根本行不通!我可以在两端访问 UTC 时间,所以听起来我在处理这些值时需要更聪明一点,要么使用 Quartz UTC 支持、Noda-Time API,要么做更多的检查;有了白天和所有其他有趣的东西,我认为这些计算留给更好的人,比如编写 Quartz 或 Noda 的人 :) 再次感谢; ++++!
猜你喜欢
  • 1970-01-01
  • 2022-08-10
  • 1970-01-01
  • 2011-07-04
  • 2015-04-12
  • 2012-09-15
  • 2012-02-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多