4

我有一个独特的问题,我的石英作业调度程序实现是使用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;

    }

谢谢你。

4

1 回答 1

12

你实际上错过了很多。

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

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

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

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

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

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

  7. 我查看了 Quartz.net API,似乎他们更喜欢您在 UTC 中安排事件时间。这是一件非常好的事情,因为 UTC 不存在歧义问题。从Quartz.Net 教程中,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 将假定您想要标准时间而不是夏令时。如果您想更具体,您必须检查是否有歧义,然后询问您的用户他们想要的两个当地时间中的哪一个。然后,您将不得不使用 aDateTimeOffset来区分它们。如果你认为你可能需要这个,请告诉我,我可以制作一个样品,但它有点复杂。

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

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

于 2013-06-13T06:06:59.817 回答