5

我看到许多网站(通常是论坛)允许用户通过选择来指定他们的时区偏好:

  • 时区
  • 是否使用夏令时

据我所知,在进行转换时,.NET 总是将 DST 考虑在内,所以问题是:

“不使用夏令时”部分如何在 C# 中实现?

在这一点之下,我介绍了我到目前为止所做的事情,但感觉很老套,我想知道是否有更清洁/更好的方法。

首先,为了确保自动应用 DST,我编写了以下测试:

[Test]
public void DateTimeConversion_ToLocalTime_HandlesDSTByDefault()
{
    var utcDateInDstInterval = new DateTime(2012, 07, 15, 0, 0, 0, DateTimeKind.Utc);
    var utcDateOutisdeDstInterval = new DateTime(2012, 02, 15, 0, 0, 0, DateTimeKind.Utc);

    var roTimezone = TimeZoneInfo.FindSystemTimeZoneById("GTB Standard Time");

    Assert.AreEqual(3, TimeZoneInfo.ConvertTimeFromUtc(utcDateInDstInterval, roTimezone).Hour);
    Assert.AreEqual(2, TimeZoneInfo.ConvertTimeFromUtc(utcDateOutisdeDstInterval, roTimezone).Hour);
}

该测试通过,表明:

  • 当不适用 DST 时(例如在冬季),罗马尼亚时间为 UTC + 2 小时
  • 当 DST 应用时(例如在夏季),罗马尼亚时间为 UTC + 3 小时
  • 在进行 DateTime 转换时,会自动考虑 DST

接下来,我注意到TimeZoneInfo.GetAdjustmentRules()返回对象数组的方法,AdjustmentRule并发现如果我撤消这些规则的影响,我可以获得 DST 未受影响的值。

因此,如果DateTime对象受 DST 影响,我编写了以下方法:

private DateTime RemoveDSTFromDateTime(DateTime dateTime, TimeZoneInfo timeZoneInfo)
{
    if (!dateTime.IsDaylightSavingTime())
        return dateTime;

    var result = dateTime;

    foreach (var adjustmentRule in timeZoneInfo.GetAdjustmentRules())
        result = result.Subtract(adjustmentRule.DaylightDelta);

    return result;
}

回到最初的 DST/No DST 场景,但这一次强制结果不受 DST 影响:

[Test]
public void DateTimeConversion_ToLocalTime_WithoutDST()
{
    var utcDateInDstInterval = new DateTime(2012, 07, 15, 0, 0, 0, DateTimeKind.Utc);
    var utcDateOutisdeDstInterval = new DateTime(2012, 02, 15, 0, 0, 0, DateTimeKind.Utc);

    var roTimezone = TimeZoneInfo.FindSystemTimeZoneById("GTB Standard Time");

    var convertedDateWithDst = TimeZoneInfo.ConvertTimeFromUtc(utcDateInDstInterval, roTimezone);
    var convertedDateWithoutDst = TimeZoneInfo.ConvertTimeFromUtc(utcDateOutisdeDstInterval, roTimezone);

    Assert.AreEqual(2, RemoveDSTFromDateTime(convertedDateWithDst, roTimezone).Hour);
    Assert.AreEqual(2, RemoveDSTFromDateTime(convertedDateWithoutDst, roTimezone).Hour);
}

这个测试也通过了,表明现在 DST 的效果被取消了(我们总是得到 UTC + 2h,不管一年中的什么时间)。

在写下这篇文章时,我得到了另一个似乎可行的想法:不要使用任何TimeZoneInfo.Convert...()方法,只需添加roTimezone.BaseUtcOffsetUTC 日期即可。

谁能指出这样做的正确方法是什么?

4

3 回答 3

4

解析您的问题,您似乎有两个建议的解决方案:

  1. 确定 DST 调整并将其从转换后的输出中删除。

  2. 采用 UTCDateTime并添加UTC,BaseUtcOffset因此您始终处理标准时间。

由于您的问题是如何不实施 DST,我想说您自己用选项 2 回答了它。

也就是说,您最初的假设是有缺陷的。许多网站要求时区和 DST 的原因通常是因为他们的时区实现不支持 DST。他们正在做的是让您选择一个基本偏移量并决定您是否希望在一年中的某个时间增加一个小时。他们可能称其为“时区”,但它绝不像 .Net 的TimeZoneInfo类或像Noda Time这样的库那么健壮。

如果您真的选择“时区”,例如TimeZoneInfo.GetSystemTimeZones()(您应该保存Id属性并显示DisplayName属性),那么您绝对不应该询问用户是否使用 DST。如果用户住在不使用 DST 的地方(例如在亚利桑那州),则已经有一个选择。用户通常很擅长选择正确的选择来获得他们想要的效果,特别是如果他们住在一个有奇怪 DST 规则的地区(如印第安纳州)。

如果您使用 IANA/Olson 样式的时区"America/New York",无论它们是由Noda Time还是其他一些实现提供,同样的想法也适用。您应该将 DST 决定委托给图书馆——而不是用户。

将其委托给图书馆的一个非常有效的论点是,这些 DST 规则可以更改,并且在其生命周期中的许多时间点都发生了更改。如果您正在编写处理过去信息的任何类型的应用程序或网站,您可能会发现今天有效的内容并不是对过去发生的事情的正确调整。此外,时区世界也有很多奇怪之处,比如澳大利亚,某些地区的时差是 30 分钟而不是一小时。你真的想自己管理吗?可能不是。:)

于 2012-09-11T18:59:16.907 回答
3

我的一个建议是创建一个自定义TimeZoneInfo并使用它来生成DateTime对象

myTimeZoneInfo = TimeZoneInfo.CreateCustomTimeZone(
                 TimeZoneInfo.Local.Id,
                 TimeZoneInfo.Local.BaseUtcOffset,
                 TimeZoneInfo.Local.DisplayName,
                 TimeZoneInfo.Local.StandardName, "", null, true);

关键参数是true,即disableDaylightSavingsTime

用法:

DateTime noDaylightSavings = TimeZoneInfo.ConvertTimeFromUtc(reading, myTimeZoneInfo);

或者

DateTime noDaylightSavings = TimeZoneInfo.ConvertTimeToUtc(reading, myTimeZoneInfo);
于 2014-04-03T15:56:38.993 回答
2

我最近这样做是为了在远程计算亚利桑那州的当前时间时删除 DST。就我而言,我在数据库中按邮政编码有一个 DST (y/n) 标志。对于使用上述代码(示例 #2)删除 DST 调整的任何人,您可能需要限定您打算应用的调整的时间范围,因为一些调整存在但已过期(我认为可能是未来日期当变化即将来临)。这是对我有用的示例:

private DateTime RemoveDSTFromDateTime(DateTime dateTime, TimeZoneInfo timeZoneInfo)
{
    #region detail...
    //removes daylight savings time adjustment as defined in current year
    int _currYear = DateTime.Now.Year;

    if (!dateTime.IsDaylightSavingTime())
        return dateTime;

    var result = dateTime;

    foreach (var adjustmentRule in timeZoneInfo.GetAdjustmentRules())
    {
        if (adjustmentRule.DateStart.Year <= _currYear && adjustmentRule.DateEnd.Year >= _currYear)
        {
            result = result.Subtract(adjustmentRule.DaylightDelta);
        }
    }

    return result; 
    #endregion
}
于 2013-04-30T19:38:11.207 回答