在观察夏令时的同时解析本地时间的有序时间戳(到 UTC)

Posted

技术标签:

【中文标题】在观察夏令时的同时解析本地时间的有序时间戳(到 UTC)【英文标题】:Parsing of Ordered Timestamps in Local Time (to UTC) While Observing Daylight Saving Time 【发布时间】:2014-11-30 18:48:06 【问题描述】:

我有 CSV 数据文件,其中包含本地时间的时间戳记录。不幸的是,数据文件涵盖了夏令时更改的时间段(2013 年 11 月 3 日),因此记录时间戳的时间部分为:12:45, 1:00, 1:15, 1:30, 1:45, 1:00, 1:15, 1:30, 1:45, 2:00。我希望能够将数据库中的值转换并存储为 UTC。

不幸的是,.NET 的标准 DateTime.Parse() 函数将解析为这样(2013 年 11 月 3 日):

| Time String | Parsed Local Time | In DST | Parsed Local Time to UTC
|  12:45 am   |    12:45 am       |   Yes  |     4:45 am
| 12:59:59 am |    12:59:59 am    |   Yes  |     4:59:59 am
|  01:00 am   |     1:00 am       |   No   |     6:00 am
|  01:15 am   |     1:15 am       |   No   |     6:15 am

因此它永远不会将1:00-1:59:59 am 范围视为在 DST 中,并且我在 UTC 中解析的时间戳会跳一个小时。

是否有一个库或类可以让我解析时间戳并考虑 DST 的变化?就像某个可实例化的类会记住它已经收到的时间戳流并相应地调整解析的时间戳?

解析时可以做出的数据假设:

    我在本地和 UTC 文件的标题部分都有文件的开始时间(第一条记录的时间戳)。 记录按时间戳排序 所有当地时间均采用东部标准时间 数据也可以通过其他方式:从 DST 之外进入它 记录包含完整的时间戳,格式为:yyyy/mm/dd HH:mm:ss (2013/11/03 00:45:00)

注意:虽然我的软件是 C#,但我没有专门标记 C#/.NET,因为我认为我可以使用任何语言的解决方案实现并在必要时重新编码。

【问题讨论】:

文件中的连续时间戳可以在 UTC 中倒退吗? 不,可以假定它们是按 UTC 顺序排列的。 我只是懒得输入上面的表格。数据文件中的时间戳字段包含完整的日期和时间。我将其添加到上述假设中。 【参考方案1】:

在 C# 中:

// Define the input values.
string[] input =

    "2013-11-03 00:45:00",
    "2013-11-03 01:00:00",
    "2013-11-03 01:15:00",
    "2013-11-03 01:30:00",
    "2013-11-03 01:45:00",
    "2013-11-03 01:00:00",
    "2013-11-03 01:15:00",
    "2013-11-03 01:30:00",
    "2013-11-03 01:45:00",
    "2013-11-03 02:00:00",
;

// Get the time zone the input is meant to be interpreted in.
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

// Create an array for the output values
DateTimeOffset[] output = new DateTimeOffset[input.Length];

// Start with the assumption that DST is active, as ambiguities occur when moving
// out of daylight time into standard time.
bool dst = true;

// Iterate through the input.
for (int i = 0; i < input.Length; i++)

    // Parse the input string as a DateTime with Unspecified kind
    DateTime dt = DateTime.ParseExact(input[i], "yyyy-MM-dd HH:mm:ss",
                                      CultureInfo.InvariantCulture);

    // Determine the offset.
    TimeSpan offset;
    if (tz.IsAmbiguousTime(dt))
    
        // Get the possible offsets, and use the DST flag and the previous entry
        // to determine if we are past the transition point.  This only works
        // because we have outside knowledge that the items are in sequence.
        TimeSpan[] offsets = tz.GetAmbiguousTimeOffsets(dt);
        offset = dst && (i == 0 || dt >= output[i - 1].DateTime)
                 ? offsets[1] : offsets[0];
    
    else
    
        // The value is unambiguous, so just get the single offset it can be.
        offset = tz.GetUtcOffset(dt);
    

    // Use the determined values to construct a DateTimeOffset
    DateTimeOffset dto = new DateTimeOffset(dt, offset);

    // We can unambiguously check a DateTimeOffset for daylight saving time,
    // which sets up the DST flag for the next iteration.
    dst = tz.IsDaylightSavingTime(dto);

    // Save the DateTimeOffset to the output array.
    output[i] = dto;



// Show the output for debugging
foreach (var dto in output)

    Console.WriteLine("0:yyyy-MM-dd HH:mm:ss zzzz => 1:yyyy-MM-dd HH:mm:ss UTC",
                       dto, dto.UtcDateTime);

输出:

2013-11-03 00:45:00 -04:00 => 2013-11-03 04:45:00 UTC
2013-11-03 01:00:00 -04:00 => 2013-11-03 05:00:00 UTC
2013-11-03 01:15:00 -04:00 => 2013-11-03 05:15:00 UTC
2013-11-03 01:30:00 -04:00 => 2013-11-03 05:30:00 UTC
2013-11-03 01:45:00 -04:00 => 2013-11-03 05:45:00 UTC
2013-11-03 01:00:00 -05:00 => 2013-11-03 06:00:00 UTC
2013-11-03 01:15:00 -05:00 => 2013-11-03 06:15:00 UTC
2013-11-03 01:30:00 -05:00 => 2013-11-03 06:30:00 UTC
2013-11-03 01:45:00 -05:00 => 2013-11-03 06:45:00 UTC
2013-11-03 02:00:00 -05:00 => 2013-11-03 07:00:00 UTC

请注意,这假定您第一次遇到像 1:00 这样的模棱两可的时间时,它将在 DST 中。假设您的列表被截断为仅最后 5 个条目 - 您不会知道这些是标准时间。在这种特殊情况下,您无能为力。

【讨论】:

老兄。极好的。我不知道TimeZoneInfo 并且基本上是我自己的IsAmbiguousTime() 实现的,但没有获得模糊偏移量的好方法。我试图深入猜测我应该如何向前或向后调整。谢谢!我能够轻松地将其封装到一个类中并添加一些单元测试。它也适用于 DST(只要您在更改之前至少有一个值)。 在更改进入 DST 之前不应该要求有一个值。在那个过渡中,有一个间隙,但没有一个模棱两可的有效时间。另请参阅the dst tag wiki。【参考方案2】:

如果连续的时间戳不能以 UTC 时间表示,则此 Python 脚本可以将本地时间转换为 UTC:

#!/usr/bin/env python3
import sys
from datetime import datetime, timedelta
import pytz  # $ pip install pytz

tz = pytz.timezone('America/New_York' if len(sys.argv) < 2 else sys.argv[1])
previous = None #XXX set it from UTC time: `first_entry_utc.astimezone(tz)`
for line in sys.stdin: # read from stdin
    naive = datetime.strptime(line.strip(), "%Y/%m/%d %H:%M:%S") # no timezone
    try:
        local = tz.localize(naive, is_dst=None) # attach timezone info
    except pytz.AmbiguousTimeError:
        # assume ambiguous time always corresponds to True -> False transition
        local = tz.localize(naive, is_dst=True)
        if previous >= local: # timestamps must be increasing
            local = tz.localize(naive, is_dst=False)
        assert previous < local
    #NOTE: allow NonExistentTimeError to propagate (there shouldn't be
    # invalid local times in the input)
    previous = local
    utc = local.astimezone(pytz.utc)
    timestamp = utc.timestamp()
    time_format = "%Y-%m-%d %H:%M:%S %Z%z"
    print("local:time_format; utc:time_format; timestamp:.0f"
          .format_map(vars()))

输入

2013/11/03 00:45:00
2013/11/03 01:00:00
2013/11/03 01:15:00
2013/11/03 01:30:00
2013/11/03 01:45:00
2013/11/03 01:00:00
2013/11/03 01:15:00
2013/11/03 01:30:00
2013/11/03 01:45:00
2013/11/03 02:00:00

输出

2013-11-03 00:45:00 EDT-0400; 2013-11-03 04:45:00 UTC+0000; 1383453900
2013-11-03 01:00:00 EDT-0400; 2013-11-03 05:00:00 UTC+0000; 1383454800
2013-11-03 01:15:00 EDT-0400; 2013-11-03 05:15:00 UTC+0000; 1383455700
2013-11-03 01:30:00 EDT-0400; 2013-11-03 05:30:00 UTC+0000; 1383456600
2013-11-03 01:45:00 EDT-0400; 2013-11-03 05:45:00 UTC+0000; 1383457500
2013-11-03 01:00:00 EST-0500; 2013-11-03 06:00:00 UTC+0000; 1383458400
2013-11-03 01:15:00 EST-0500; 2013-11-03 06:15:00 UTC+0000; 1383459300
2013-11-03 01:30:00 EST-0500; 2013-11-03 06:30:00 UTC+0000; 1383460200
2013-11-03 01:45:00 EST-0500; 2013-11-03 06:45:00 UTC+0000; 1383461100
2013-11-03 02:00:00 EST-0500; 2013-11-03 07:00:00 UTC+0000; 1383462000

【讨论】:

谢谢你,太好了。另一个解决方案是 C#,因此更容易应用于我的项目,但对于使用 Python 的人来说,这似乎很简单。 @MattJohnson:很好,但在欧洲/莫斯科时区的 2014-10-26 使用最新的 tz 数据库失败(跳转前后的 dst 为 0)。 嗯...tz.normalize(tz.localize(datetime(2014,10,26,1,0,0)) + timedelta(hours=1)) 有效。 (尽管最好不要对 1 小时进行硬编码)。但是你是对的,localize 本身无法给出 +03:00 偏移量,因为is_dst 对两者都是错误的。 @MattJohnson: tz.normalize() 在这里没有解决任何问题:tz.localize(datetime(2014,10,26,1,0,0) + timedelta(hours=1)) 也产生 +0300。我认为在欧洲/莫斯科时区解析输入(将2013/11/03 替换为上面的2014/10/26)没有帮助。 时间变了。没有标准化,你会得到 02:00+3:00。使用 normalize,你会得到 01:00+3:00

以上是关于在观察夏令时的同时解析本地时间的有序时间戳(到 UTC)的主要内容,如果未能解决你的问题,请参考以下文章

前后端解析时间戳得到的时间不一样

夏令时和 UTC 时间

根据夏令时逻辑操作时间

没有时区的时间戳中的夏令时时间偏移

将时间戳数据解析为本地日期和时间的问题

Delphi - 获取非本地时区的 TTimeZone 并在时区之间转换 [重复]