任意时区之间的转换

3
我正在尝试找到一种简单而又强大的方法,在任意时区之间转换时间。
这个链接:http://www.cpearson.com/excel/TimeZoneAndDaylightTime.aspx 只解释了如何在我的(当前)时区和另一个时区之间进行转换。
这两篇 SO 文章 (Getting Windows Time Zone Information (C++/MFC)How do you get info for an arbitrary time zone in Windows?) 讲述了如何从注册表中获取信息。
那听起来有点复杂和耗时,此外,似乎Windows将TZs存储在它们的“全名”中(例如(UTC-08:00)太平洋时间(美国和加拿大)),而我更愿意使用缩写(例如EDT)来引用TZs。此外,依赖于Windows注册表也可能不安全:不同的用户可能有不同的版本,有些可能不是最新的。这意味着两个人运行的报告可能会提供两个不同的结果!
有没有更简单而且强大的方法?编写查找表可能在一段时间内有效,但当政府决定废除DST或更改其他任何内容时,它就会失效。
也许从互联网上获取TZs列表并解析它?这样安全吗?
更新1
我已经进行了研究并探索了可能性,但这个问题并不像看起来那么简单。如果您认为该函数应该类似于bTime = aTime + 3,请重新考虑。时区和DST处于不断变化的状态。

参考此处:待处理/提议的时区更改列表。请注意,有些国家实际上正在更改他们的时区,而不仅仅是夏令时设置!巴西甚至改变了他们改变冬令时钟表的日期!一个静态查找表很快就会被所有这些更改打破。

更新2

我不想要一个快速而肮脏的黑客,我可以自己想出来。我不想写一些东西然后忘记它;我想创建一个函数,可以安全地用于其他人的不同内部项目,而无需维护噩梦。硬编码已知偶尔会更改的常量是非常糟糕的软件设计(想想由非常古老的代码引起的Y2K错误)。

更新3

这个数据库看起来不错(虽然我不确定它是否足够稳定):https://timezonedb.com/api。他们甚至有一个TZ转换调用 - 正是我需要的!我可能会尝试从VBA解析XML并分享我的结果。


1
编写查找表可能会有用一段时间,但当政府决定废除DST或改变其他任何东西时,它就会失效。- 这是打趣的意思吧? - Vityata
2
你为什么认为这是个笑话,@Vityata?我不明白你的意思。如果你的意思是说夏令时没有改变,那么看一下这个(只是一个例子):https://www.timeanddate.com/news/time/usa-states-no-dst.html 实际上,有一整个变化列表:https://www.timeanddate.com/news/time/ 如果你是在说“查找表是邪恶的”,那么是的,我同意,因此我的问题。 - Alexander
如果夏令时将要更改,它每年只会更改一次。看起来对我来说是一个相当不错的选择。 - Vityata
1
你可以保留一个集中的查找表。共享文件夹是一种选择,但如果你有一个适当的数据库,那就更好了。这样,如果时区发生变化,你只需要维护那个表格。或者,你可以尝试使用一些API并连接到现有服务,我相信许多供应商都有类似的东西。或者你可以编写一个超级AI,以某种方式预测TZ何时会改变并自动更新。 - vacip
1
这看起来很有前途:https://timezonedb.com/api - vacip
显示剩余3条评论
2个回答

4
https://timezonedb.com/references/convert-time-zone提供了一个很好的API,可以获取正确的全球时间、时区和两个位置之间的时区偏移量,考虑到过去/未来的夏令时变化。您建议的只指定时区缩写(例如“将PST转换为EST”)的方法存在问题,因为即使它们不正确,此API也会按照您的时区字面意思进行计算。因此,如果多伦多目前处于EDT,但您指定EST,您可能会得到错误的时间。使用类似于(UTC-08:00) Pacific Time (US & Canada)的“全名”将具有相同的问题。解决这个问题的方法是指定像America/Vancouver这样的时区名称(如此处所列),或者使用适当的参数指定城市、国家和/或地区名称。我编写了一个函数来解决这个问题,但它只适用于某些国家(请参见下文)。

上次万圣节在温哥华时间晚上11:11时,多伦多是什么时间?

http://api.timezonedb.com/v2/convert-time-zone?key=94RKE4SAXH67&from=America/Vancouver&to=America/Toronto&time=1509516660

结果:(默认为XML,但也可用JSON。)

<result>
    <status>OK</status>
    <message/>
    <fromZoneName>America/Vancouver</fromZoneName>
    <fromAbbreviation>PDT</fromAbbreviation>
    <fromTimestamp>1509516660</fromTimestamp>
    <toZoneName>America/Toronto</toZoneName>
    <toAbbreviation>EDT</toAbbreviation>
    <toTimestamp>1509527460</toTimestamp>
    <offset>10800</offset>
</result>

程序获取数据:

在决定哪些选项和查询方法时,有很多选择,但这里提供一种使用VBA函数的示例:

圣诞节当天温哥华和柏林之间的时间差是多少?

输入时间: 2018-12-25 00:00:00 = 温哥华本地Unix时间 1545724800

Function GetTimeZoneOffsetHours(fromZone As String, _
            toZone As String, UnixTime As Long) As Single

    Const key = "94RKE4SAXH67"
    Const returnField = "<offset>"
    Dim HTML As String, URL As String
    Dim XML As String, pStart As Long, pStop As Long

    URL = "http://api.timezonedb.com/v2/convert-time-zone?key=" & key & _
        "&from=" & fromZone & "&to=" & toZone & "&time=" & UnixTime
    With CreateObject("MSXML2.XMLHTTP")
        .Open "GET", URL, False
        .Send
        XML = .ResponseText
    End With

    pStart = InStr(XML, returnField)
    If pStart = 0 Then
        MsgBox "Something went wrong!"
        Exit Function
    End If

    pStart = pStart + Len(returnField) + 1
    pStop = InStr(pStart, XML, "</") - 1
    GetTimeZoneOffsetHours = Val(Mid(XML, pStart, pStop - pStart)) / 60
End Function


Sub testTZ()
    Debug.Print "Time Zone Offset (Vancouver to Berlin) = " & _
        GetTimeZoneOffsetHours("America/Vancouver", _
        "Europe/Berlin", 1545724800) & " hours"
End Sub

Unix/UTC Timestamps:

Unix time is defined as "the number of seconds that have elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970."

You can convert times between Unix and/or UTC or Local time at: epochconverter.com ... the site also has conversion formulas for several programming languages.

For example, the formua to convert Unix time to GMT/UTC in Excel is:

=(A1 / 86400) + 25569

您也可以下载静态文件(以SQLCSV格式)在此处,而不是调用API,并且该页面还有示例查询。 但是请注意:使用夏令时易犯错误(如上所述)。

我创建了一个虚拟帐户来获取示例中使用的“演示”,但您应该获得自己的(免费)密钥以供长期使用。 (如果由于过度使用而被锁定,我不负责任!)

一个很好的替代时区API是Google Maps时区API。 区别在于您需要指定纬度和经度。似乎没有密钥也能正常工作 您需要注册获取密钥。

在白宫,6月1日的时区偏移量是多少?

https://maps.googleapis.com/maps/api/timezone/json?location=38.8976,-77.0365&timestamp=1527811200&key={YourKeyHere}

结果

{
   "dstOffset" : 0,
   "rawOffset" : -18000,
   "status" : "OK",
   "timeZoneId" : "America/Toronto",
   "timeZoneName" : "Eastern Standard Time"
}

偏移量将为-18000秒(即5个小时)。

确定夏令时的实施时间

下面是我编写的一个函数,以便我可以“信任”来自不同API的夏令时(DST)值,但是(如其他人所讨论的),规则没有模式而且在世界某些地区甚至在城镇之间也在不断变化,因此这只适用于以下国家:

  • DST每年从3月第二个星期日开始
  • DST每年从11月第一个星期日结束

适用的国家有巴哈马,百慕大,加拿大,古巴,海地,圣皮埃尔和美国。来源:各国夏令时**)

Function IsDST(dateTime As Date) As Boolean

    'Returns TRUE if Daylight Savings is in effect during the [dateTime]
    'DST Start (adjust clocks forward) Second Sunday March at 02:00am
    'DST end (adjust clocks backward) First Sunday November at 02:00am

    Dim DSTStart As Date, DSTstop As Date

    DSTStart = DateSerial(Year(dateTime), 3, _
        (14 - Weekday(DateSerial(Year(dateTime), 3, 1), 3))) + (2 / 24)
    DSTstop = DateSerial(Year(dateTime), 11, _
        (7 - Weekday(DateSerial(Year(dateTime), 11, 1), 3))) + (2 / 24)
    IsDST = (dateTime >= DSTStart) And (dateTime < DSTstop)

End Function

以下是我如何使用 IsDST 函数的几个示例:

Public Function UTCtoPST(utcDateTime As Date) As Date
    'Example for 'PST' time zone, where Offset = -7 during DST, otherwise if -8

    If IsDST(utcDateTime) Then
        UTCtoPST = utcDateTime - (7 / 24)
    Else
        UTCtoPST = utcDateTime - (8 / 24)
    End If

End Function


Function UTCtimestampMStoPST(ByVal ts As String) As Date
    'Example for 'PST', to convert a UTC Unix Time Stamp to 'PST' Time Zone

    UTCtimestampMStoPST = UTCtoPST((CLng(Left(ts, 10)) / 86400) + 25569)

End Function

注意函数IsDST是不完整的:它没有考虑到在2点实际生效之前/之后的小时数。具体来说,在春季,时钟从标准时间01:59最后一刻向前跳动到03:00的夏令时,并且那天有23个小时,而在秋季,时钟从夏令时01:59最后一刻向后跳动到标准时间01:00,重复那个小时,并且那天有25个小时......但是,如果有人想要添加该功能以更新该函数,请随意!我对那最后一部分感到困惑,而且并不需要那么详细的信息,但我相信其他人会感激的!
最后一个选择是我用于轮询当前/未来/历史天气数据的API,同时也提供时区偏移量,它是DarkSky。
它通过纬度/经度查询,是免费的(每日最多1000次调用),并提供"超精确的天气数据"(在美国更为如此,因为它可以预测到分钟平方码的天气!但我在不可预测的加拿大西海岸也看到了相当准确的预测!)
响应仅以JSON格式提供,最后一行是时区偏移与UTC/GMT时间

DarkSky示例调用:

https://api.darksky.net/forecast/85b57f827eb89bf903b3a796ef53733c/40.70893,-74.00662

它说接下来的60个小时Stack Overflow总部会下雨。☂

...但是我不知道,今天看起来是个很好的日子!☀

SO Head Office w/ Flag (flag)


1
嘿,谢谢Ashlee,那是一个非常详细和惊人的答案!感激不尽! - Alexander
不客气。我碰巧也在处理时区和夏令时的问题,当我看到你的帖子时,现在它也在这里被记录下来了,对我也很有帮助!如果你有任何问题或改进,请告诉我。 - ashleedawg
一个示例文档 +1)。- 仅供参考:在您的示例中,获取Unix时间戳1545724800精确输入时间应为2018-12-25 08:00:00(上午八点),通过(DateSerial(2018, 12, 25) + 8 / 24 - DateSerial(1970, 1, 1)) * 86400DateDiff("s", "01/01/1970 00:00:00", DateSerial(2018, 12, 25) + 8/24)进行测试。 - T.M.

2
很抱歉,与时区有关的任何事情都不是一项简单的任务(问问任何网页设计师,他们会说这是一个巨大的挑战)。
解决您的问题有两种方法:
1)简单的方法 - 创建一个中央列表,所有其他工作簿都链接到该列表。可以将其保存在SharePoint或共享驱动器上,然后您只需更新此表格即可。
2)困难的方法 - 使用网站API获取最新的时区数据。 https://www.amdoren.com/ 是一个不错的网站,您可以通过注册获得免费的API密钥。唯一的问题是您需要解析来自网站的Json文件。这并不容易,但如果您搜索“vba解析json”,您将找到一些解决方案(通常需要导入一些库并使用其他人的代码作为起点)。
希望您能找到正确的解决方案,如果找到了,可能值得分享,因为我相信会有其他人遇到同样的问题。

谢谢David。不幸的是,我无法访问amdoren.com,可能被公司内部屏蔽了。@vacip建议的https://timezonedb.com/api看起来非常不错;它可以返回XML和JSON格式的数据,我想XML解析会更容易。我会尝试并发布结果! - Alexander
嗨,亚历山大,使用XML绝对是个好主意(我没想到)。我从未使用过timezonedb,但看起来不错。期待结果。 - David wyatt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接