在 C# 中将 DST 规则转换为 DateTime

Convert DST rule to DateTime in C#

我有这样的 DST 规则:

"2,-1,1,3600000"

在 C# DateTime 中转换它的正确方法是什么?

到目前为止我已经这样做了:

public static DateTime ConvertDstRule(int year, string rule, bool isEndRule)
{
    const int DaysInWeek = 7;
    var ruleName = isEndRule ? "endRule" : "startRule";

    var startStrings = rule.Split(',');
    var month = Convert.ToInt32(startStrings[0]);
    if ((month < 0) || (month > 11))
    {
        throw new ArgumentOutOfRangeException(ruleName, "The month value must be between 0 and 11");
    }

    var week = Convert.ToInt32(startStrings[1]);
    if ((week < -1) || (week > 5))
    {
        throw new ArgumentOutOfRangeException(ruleName, "The week value must be between -1 and 5");
    }

    if ((Convert.ToInt32(startStrings[2]) < 1) || (Convert.ToInt32(startStrings[2]) > 7))
    {
        throw new ArgumentOutOfRangeException(ruleName, "The day value must be between 1 and 7");
    }

    var day = (DayOfWeek)(Convert.ToInt32(startStrings[2]) - 1); // DayOfWeek is zero based so shift by one.
    var timeOffset = Convert.ToInt64(startStrings[3]);
    if ((timeOffset / 1000 / 60) > 86400)
    {
        throw new ArgumentOutOfRangeException(ruleName, "The time offset is limited to one day");
    }

    // Find the start of the relevant year.
    var startTime = new DateTime(year, 1, 1);

    // Add on the month to get to the start of the selected month.
    startTime = startTime.AddMonths(month);

    // If the week is negative then go to the first occurance of the day in
    // the next month, adding a negative week number will jump back into
    // the previous month.
    if (week < 0)
    {
        startTime = startTime.AddMonths(1);
    }
    else
    {
        week = week - 1;
    }

    // Jump to the first occurence of the day to switch in that month.
    var monthStartsOn = startTime.DayOfWeek;
    var daysToSwitchDay = (int)day - (int)monthStartsOn;

    // This is likely to be negative as most zones switch on a Sunday
    if (daysToSwitchDay < 0)
    {
        daysToSwitchDay = DaysInWeek + daysToSwitchDay; // daysToSwitchDay is negative so add it.
    }

    startTime = startTime.AddDays(daysToSwitchDay); // Now on the correct day.

    startTime = startTime.AddDays(week * 7); // Week counts from 1.

    startTime = startTime.AddMilliseconds(timeOffset);
    if (isEndRule)
    {
        startTime = startTime.AddHours(-1); // Take off the DST hour to convert it to UTC.
    }

    return startTime;
}

是否像印度一样考虑半小时的 DST 变化?你能发现这段代码中的任何错误吗?

几件事:

  • 您描述的输入类型称为 "Transition Rule"。或者在时区上下文之外,它是一种特殊类型的 "Recurrence Rule"。它只是描述了一种模式,用于根据月、周和工作日确定给定年份的特定时间点何时出现。

  • 转换规则不会告诉您计算时区值所需知道的一切。特别是,它不会告诉您转换之前或之后与 UTC 的偏移量,或者偏移量调整的方向。另外,您需要其中的 set,因为通常每年有两个,但也可能有任意数量。例如,2014 年,Russia had only one transition, and Egypt had four。还要考虑到这些规则随时间发生了变化,因此单个规则,甚至一对规则,不会告诉您如何转换所有时间点的值。对于给定的时区,您需要一组规则集,这就是我们说 "a time zone".

  • 的意思
  • 在 .NET Framework 中,TimeZoneInfo class 用于处理时区。它包含子classes、TimeZoneInfo.AdjustmentRuleTimeZoneInfo.TransitionTime。特别是,TimeZoneInfo 中有一个名为 TransitionTimeToDateTime 的内部函数,它完全符合您的要求。它需要 TransitionTime 和一年,并为您提供 DateTime 过渡发生在一年内。你可以找到这个 in the .NET Reference Source here.

但是,如果您真的认为您想要尝试从您自己的数据源实施时区规则,您应该考虑以下几点:

  1. 你真的认为你能比你之前的数百人做得更好吗?也许是这样,但不要以 "this is simple" 的态度来讨论这个问题。我建议你 watch this short video,并在尝试之前做大量研究。

  2. 您是否在考虑如何维护这些东西?时区规则经常变化。每年,全世界可能会发生十几个变化。您是否计划监控新闻提要、论坛、政府新闻稿和其他来源?当政府没有发出那么多关于夏令时规则将要改变的警告时,您准备好采取行动了吗?

  3. 当事情不准确时,它会对您的系统产生多大影响?它的范围可以从不多(例如:博客或论坛)到重要(例如:航班时刻表、通讯、医疗、财务等)。

此外:

  • 问题评论中使用 Noda Time 的建议很好,但不幸的是 Noda Time 没有任何具体的 API 来解释单个转换规则.相反,您最好使用现有的 TZDB 区域,例如 "America/New_York"。或者,您可以在 .NET Framework 中使用 TimeZoneInfo class,ID 类似于 "Eastern Standard Time".

  • 在内部,Noda Time 通过 ZoneYearOffset class. You can see how a TransitionRule maps to a ZoneYearOffset in this code.

  • 处理转换规则
  • 在您的代码示例中,注释 "Take off the DST hour to convert it to UTC." 极具误导性。您的函数的输出是当地时间。 UTC 与它无关。您在此处显示的代码中的其他任何地方都没有跟踪 UTC 的偏移量,无论是否使用 DST。我认为你的意思是“......将其转换为标准时间”,但我不确定你为什么要这样做。这个特殊的功能不应该试图解释这一点。

  • 您还问:"Does it takes into account half-hour DST changes like in India?"该问题无效,因为印度没有夏令时。它的标准时区偏移量有半小时 (UTC+05:30),但没有 DST。目前世界上唯一使用半小时夏令时偏差的地方是豪勋爵岛,它在 tzdb 中以 "Australia/Lord_Howe" 时区表示,目前没有 Windows 等效时区。在世界其他任何地方(南极洲的几个研究站除外),如果使用夏令时,转换偏差为一小时。