关于c#:用时区和夏令时确定一天的开始

determining the beginning of a day with timezones AND daylight saving

我在会话中将用户的时区存储为小数。 例如,如果用户在EST时区,我就有

1
UserTimeZone = -5.00;

数据库中的数据以UTC格式存储,因此我想计算该用户当天的开始和结束时间,以便当用户想要特定日期的数据时,记录会按时区进行调整。

这就是我正在做的事情:

1
2
3
4
5
6
7
DateTime StartDate =  DateTime.Now.ToUniversalTime();

StartDate = StartDate.AddHours((double)UserTimeZone);
StartDate = StartDate.Date;
StartDate = StartDate.AddHours((double)UserTimeZone);

DateTime EndDate = StartDate.AddHours(24);

我遇到的问题是,这不考虑夏令时,所以即使考虑到EST时间比UTC晚了5个小时,目前由于夏令时的变化,它实际上落后于UTC 4小时。

你有什么建议吗? 谢谢。


要进行此类计算,您需要使用TimeZoneInfoDateTimeOffset类。

首先,我们需要为本地时间和用户的本地时间获取TimeZoneInfo实例:

1
2
var localTimezone = TimeZoneInfo.Local;
var userTimezone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

这里需要注意的是,你需要知道用户本地时区的id(你目前的偏移量是不够的)。您可以获取系统使用TimeZoneInfo.GetSystemTimeZones识别的所有TimeZoneInfo实例的列表,然后您需要一种方法将用户提供的时区与其中一个匹配。

对于这个例子,我有硬编码的EST。

然后你需要在当地时区得到今天午夜(一天开始)的DateTimeOffset实例:

1
2
3
var todayDate = DateTime.Today;
var todayLocal = new DateTimeOffset(todayDate,
                                    localTimezone.GetUtcOffset(todayDate));

鉴于此,您可以在用户的??时区中计算代表"今天"午夜的DateTimeOffset实例(基于您当地时间)。请注意,根据时区,这可能实际上是用户时区的未来!

1
var todayUser = TimeZoneInfo.ConvertTime(todayLocal, userTimezone);

最后,您可以为这两个日期创建时间戳,如下所示:

1
2
3
var epochStart = DateTime.Parse("01/01/1970 00:00:00");
var todayLocalTs = (todayLocal.Ticks - epochStart.Ticks)/TimeSpan.TicksPerSecond;
var todayUserTs = (todayUser.Ticks - epochStart.Ticks) / TimeSpan.TicksPerSecond;


您需要使用JavaScript从用户的浏览器中收集必要的信息 - 有关此部分,请参阅http://www.onlineaspect.com/2007/06/08/auto-detect-a-time-zone-with-javascript/

当你有这个信息时,你可以设置UserTimeZone(顺便说一下,这不应该是一个int,因为时区有几小时!)来容纳当前时区,包括DST ......


正如BrokenGlass所提到的,简单的偏移量不足以确定白天的处理时间,因为每个区域中的不同国家可能会以不同的方式处理夏令时。 C#TimeZone类更具体,并且支持夏令时(在MSDN上查看详细信息)。不幸的是,没有简单的方法可以从浏览器中获取相关的时区,但是有关如何允许用户选择时区的帖子有一些建议。

如果你想在没有用户帮助的情况下尝试计算时区,有几种方法可以做到这一点(通常围绕获取浏览器的首选语言,然后将其映射到一个国家......),这里有一些例子和这里。


接受的答案并不认为有时区在午夜就有DST前进过渡,因此当天的开始可能不是00:00而是01:00。伊朗和巴西(部分地区)就是很好的例子。

此外,某些时区可能具有后退过渡,这两个时间段提供两个可能的午夜时间点。

考虑以下功能,它适用于两种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static DateTimeOffset GetStartOfDay(DateTime dt, TimeZoneInfo tz)
{
    // Work in the time zone provided
    if (dt.Kind != DateTimeKind.Unspecified)
    {
        dt = TimeZoneInfo.ConvertTime(dt, tz);
    }

    // Start with assuming midnight
    var d = dt.Date;

    // Check for the time being invalid and handle if so
    if (tz.IsInvalidTime(d))
    {
        // the gap is *usually* 1hr, but not always, so calculate it
        var gap = tz.GetUtcOffset(dt.AddDays(1)) - tz.GetUtcOffset(dt.AddDays(-1));

        // advance forward by the amount of the gap
        d = d.Add(gap);
    }

    // Also check for the time being ambiguous, such as in a fall-back transition.
    // We want the *first* occurrence, which will have a *larger* offset
    var offset = tz.IsAmbiguousTime(d)
        ? tz.GetAmbiguousTimeOffsets(d).OrderByDescending(x => x).First()
        : tz.GetUtcOffset(d);

    // Now we know when the date starts precisely
    return new DateTimeOffset(d, offset);
}


我建议在用户设置中添加时区并存储。时区根据一年中的时间有不同的时间。您可以使用TimeZoneInfo.GetSystemTimeZones方法提供用户可供选择的时区列表。您可以将任何日期存储为UTC并将其(使用TimeZoneInfo.ConvertTime方法)转换为显示时的用户时间,并在保存时将其转换回UTC。这将允许用户随时更改其时区而不会导致问题。如果你遵循这种格式,你不应该遇到任何问题。

预先警告,如果您不将日期存储为UTC并按照上面的建议进行转换,则可能会遇到问题。在某些时区,从夏令时变为标准时,某些日期不存在某些时间。 TimeZoneInfo类不适合这些不存在的时间。


正确的答案是每个人都告诉你要做的事情 - 在框架中使用TimeZone API。但在.NET 3.5之前,TimeZoneInfo API并不存在。如果您确实不想使用API??S,或者您使用的是.NET 3.5之前的某些内容,则可以在注册表中找到所有时区信息。

HKLM/Software/Microsoft/Windows NT/CurrentVersion/Timezones

http://www.michaelbrumm.com/simpletimezone.html上有一组类可以直接读取注册表数据并进行所需的所有时区计算 - 并对DST进行调整。这是一个很好的代码(我们已经可靠地使用它多年)与源代码,所以你可以看到他们实际在做什么。


你不应该这样做。您应该只使用.net中内置的TimeZoneInfo。

例如:

1
2
TimeZoneInfo.ConvertTimeToUtc();
TimeZoneInfo.ConvertTimeFromUtc();

由于您似乎无法查找API参数,因此您可以:

http://msdn.microsoft.com/en-us/library/system.timezoneinfo.converttimefromutc.aspx

http://msdn.microsoft.com/en-us/library/bb381744.aspx


您需要时区文件http://www.twinsun.com/tz/tz-link.htm
Record all you time in UTC/GMT.

日光节省不一致,各国改变其DLS规则。
因此,您的应用程序将始终需要最新的Tz文件。

您的用户在注册时应选择Time-Zone name,而不是时间偏移。因为两个区域可以具有相同的时间偏移(有时使用DLS)。

使用Tz Name,您可以知道
- 哪个国家/地区用户属于;
- 什么是his time offset;
- 什么是his DLS offset
add these both获取用户当前时间。

已知问题:如果您没有历史DLS详细信息,则历史记录中的时间无法转换为实际时间,因为这些详细信息在该历史时间可能无效。