将时间转换为UTC vbScript

7
我有以下函数,能够将当前时间转换为UTC时间。
Function toUtc(byVal dDate)
    Dim oShell : Set oShell = CreateObject("WScript.Shell")
    toUtc = dateadd("n", oShell.RegRead("HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias"), cDate(dDate))
End Function

然而,我认为这种方法无法充分处理将未来或历史日期转换为UTC的问题,因为该函数需要知道被转换日期的那个时间点服务器时区的偏移量以及是否处于夏令时。如何解决这个问题呢?我使用的是运行在Windows服务器上的vbScript / Classic ASP和IIS7.5。需要澄清的是,这与格式化日期无关,它只涉及将历史日期从服务器时区转换为UTC。在夏令时期间,如果我尝试转换标准时间下发生的日期时间戳,则偏移量将会有60分钟的差距。例如:假设今天是2013-02-19(非夏令时),我想将一个时间戳从PDT(我的服务器时区)转换为UTC,时间戳是2008-06-05(夏令时期间)。使用我的函数中的方法将给出一个比正确值早60分钟的值(因为当前时间是PST(而不是PDT),偏移量对于该历史日期是不正确的)。换句话说,输入日期的时区偏移量可能是UTC-7或UTC-8,具体取决于那个特定日期是否观察到夏令时。我需要计算出输入日期的正确UTC日期,并且代码需要在当前日期是否观察夏令时时都能正常工作。

1
既然你也可以在服务器端使用JavaScript,我建议你使用JavaScript的toUTCString()函数。 - AardVark71
谢谢@AardVark71,我没有想到那个选项。我将对其进行一些单元测试,并查看结果,然后告诉您它的表现如何。 - GWR
非常顺利。我稍后会发布我的最终解决方案。 - GWR
4个回答

4

这是我最终实施的解决方案,是在@AardVark71的建议下完成的。

我将其放在了我的func.asp文件中:

<script language="javascript" runat="server">
    function toUtcString(d) {
        var dDate = new Date(d);
        return Math.round(dDate.getTime() / 1000);
    };
</script>

它输出一个时间戳值。然后在经典ASP代码的任何地方,我都可以这样做:
response.Write DateAdd("s", toUtcString(cDate("11/11/2012 06:25 PM")), "01/01/1970 00:00:00") 'expect 11/11/2012 10:25:00 PM gmt/utc time
response.Write DateAdd("s", toUtcString(cDate("06/11/2012 06:25 PM")), "01/01/1970 00:00:00") 'expect  6/11/2012  9:25:00 PM gmt/utc time

这个(我相信)很简单,它会产生预期值并充分考虑夏令时。

这段代码无法处理时间戳值大于 subtype Long 的情况。例如:DateAdd("s", toUtcString(cDate("01/01/2060 06:25 PM")), "01/01/1970 00:00:00")。 - ulluoink
这对于ISO8601日期无效。 在服务器上运行,new Date("2015-08-27T12:28:51")返回NaN 在客户端运行Thu Aug 27 2015 11:58:16 GMT+0200 我想将UTC转换为本地时间。 - Leif Neland
@Leif Neland - 这只是一个琐碎的日期格式问题,而不是上述函数的问题。 - GWR

2
在ASP经典版中,您可以在一个页面中混合使用VBScript和JScript代码。JScript的Date对象可用于本地 ⇄ UTC日期转换。完整示例:
<%@ language="vbscript" %>
<%
Option Explicit
Dim local_date
Dim utc_date
For Each local_date In Array("11/11/2012 06:25 PM", "06/11/2012 06:25 PM")
    utc_date = local_to_utc(local_date)
    Response.Write "Local: " & local_date & ", UTC: " & utc_date & vbNewLine
Next
For Each utc_date In Array("2012-11-12T02:25:00Z", "2012-06-12T01:25:00+00:00")
    local_date = utc_to_local(utc_date)
    Response.Write "UTC: " & utc_date & ", Local: " & local_date & vbNewLine
Next
%>
<script language="jscript" runat="server">
    function local_to_utc(datestr) {
        // note that this function lets JScript engine parse the date, correctly or otherwise
        var date = new Date(datestr);
        var result = date.getUTCFullYear() + "-" + (date.getUTCMonth() + 1) + "-" + date.getUTCDate() + "T" + date.getUTCHours() + ":" + date.getUTCMinutes() + ":" + date.getUTCSeconds();
        return result.replace(/(\D)(\d)(?!\d)/g, "$10$2") + "Z";
    }
    function utc_to_local(datestr) {
        // note that this function parses only a subset of ISO 8601 date format
        var match = datestr.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{3}))?(?:Z|([-+])(\d{2}):(\d{2}))$/);
        var offset = match[8] ? (match[8] === "+" ? 1 : -1) * (match[9] * 60 + match[10] * 1) : 0;
        var date = new Date(Date.UTC(match[1], match[2] - 1, match[3], match[4], match[5] - offset, match[6], match[7] || 0));
        var result = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
        return result.replace(/(\D)(\d)(?!\d)/g, "$10$2");
    }
</script>

预期结果:

在使用PST/PDT时间的服务器上执行时的输出:

Local: 11/11/2012 06:25 PM,       UTC:   2012-11-12T02:25:00Z
Local: 06/11/2012 06:25 PM,       UTC:   2012-06-12T01:25:00Z
UTC:   2012-11-12T02:25:00Z,      Local: 2012-11-11 18:25:00
UTC:   2012-06-12T01:25:00+00:00, Local: 2012-06-11 18:25:00

有趣。我今天稍后会对此进行一些测试。之后会回复你。 - GWR
完美运作,避免使用 **Server.CreateObject("WbemScripting.SWbemDateTime")**。此外,它还优雅地允许在页面中直接包含 jScript 代码。 - AlexLaforge

0

如果我没有漏掉什么,您需要在远程计算机上获取本地时间和它的时区偏移量,对吗?

这里 我看到了一个基于 WMI Win32_TimeZone Bias 属性的自我回答。但是根据这个 MSDN 的例子:“将本地时间转换为 UTC 时间”,引用:

使用 Win32_ComputerSystem CurrentTimeZone 属性,因为它会自动调整夏令时的时区偏移量,而 Win32_TimeZone Bias 属性则不会。

所以,下一个示例函数获取当前日期时间,我将让您修改它以进行具体的日期时间测量。

' "." mean local computer
WScript.Echo FormatDateTime(UTCDate("."), 0)

Function UTCDate(strComputer)
    Dim objWMIService, ColDate, ColCS

    On Error Resume Next
    Set objWMIService = _
        GetObject("winmgmts:{impersonationLevel=impersonate}!\\" _
        & strComputer & "\root\cimv2")

    Set ColDate = objWMIService.ExecQuery("Select * From Win32_LocalTime")

    UTCDate = DateSerial(100, 1, 1)
    For Each objDate In ColDate
        UTCDate = DateAdd("yyyy", ObjDate.Year - 100, UTCDate)
        UTCDate = DateAdd("m", ObjDate.Month - 1, UTCDate)
        UTCDate = DateAdd("d", ObjDate.Day - 1, UTCDate)
        UTCDate = DateAdd("h", ObjDate.Hour, UTCDate)
        UTCDate = DateAdd("n", ObjDate.Minute, UTCDate)
        UTCDate = DateAdd("s", ObjDate.Second, UTCDate)
    Next

    ' http://msdn.microsoft.com/en-us/library/windows/desktop/ms696015(v=vs.85).aspx
    Set ColCS = objWMIService.ExecQuery("Select * From Win32_ComputerSystem")

    Dim TimeZoneOffset, LocalTimeZone
    For Each LocalTimeZone In ColCS
        TimeZoneOffset = LocalTimeZone.CurrentTimeZone
    Next

    If TimeZoneOffset < 0 Then
        TimeZoneOffset = Abs(TimeZoneOffset)
    Else
        TimeZoneOffset = -Abs(TimeZoneOffset)
    End If

    UTCDate = DateAdd("n", TimeZoneOffset, UTCDate)
    If Err Then UTCDate = vbNull
    Set objWMIService = Nothing
End Function

[编辑] 好像不能信任 MSDN(或者可能只适用于当前时间)。不过,我看到这不是你要找的答案。如果找不到更好的想法,你可以自动化这个 在线计算器

P.S. 好的,划掉上面的代码,让我们尝试另一个“迭代”。

只是要注意,我编写了一个与我的时区(保加利亚)兼容的代码,在那里太平洋夏令时从三月的最后一个星期日开始,到十月的最后一个星期日结束。这使我可以轻松设置我的 PDT 范围。简而言之,思路是使算法适用于您的地区。其余部分很清楚,希望如此。

With New DateDrill
    Debug.WriteLine .UTCDate("2008-6-28")
    Debug.WriteLine .UTCDate("2014-1-21")
End With

Class DateDrill

    Public Function UTCDate(ByVal dtDate)
        If Not IsDate(dtDate) Then Err.Raise 5
        dtDate = CDate(dtDate)
        Dim ZoneBias: ZoneBias = TimeZoneBias()
        If IsPDT(Now) <> IsPDT(dtDate) Then
            ZoneBias = ZoneBias - 60
        End If
        UTCDate = DateAdd("n", ZoneBias, dtDate)
    End Function

    Private Function IsPDT(ByVal dtDate)
        If Not IsDate(dtDate) Then Err.Raise 5
        dtDate = CDate(dtDate)
        Dim pdtLow, pdtUpr, nDaysBack

        pdtLow = DateSerial(Year(dtDate),  3, 31)
        pdtUpr = DateSerial(Year(dtDate), 10, 31)
        pdtLow = DateAdd("h", 2, pdtLow)
        pdtUpr = DateAdd("h", 2, pdtUpr)

        nDaysBack = Weekday(pdtLow) - 1
        If nDaysBack <> 0 Then
            pdtLow = DateAdd("d", -nDaysBack, pdtLow)
        End If

        nDaysBack = Weekday(pdtUpr) - 1
        If nDaysBack <> 0 Then
            pdtUpr = DateAdd("d", -nDaysBack, pdtUpr)
        End If

        IsPDT = (dtDate >= pdtLow And dtDate <= pdtUpr)
    End Function

    Private Function TimeZoneBias()
        Dim LTZone
        With GetObject("winmgmts:" & _
                "{impersonationLevel=impersonate}!\\.\root\cimv2")
            For Each LTZone In .ExecQuery(_
                    "Select * From Win32_ComputerSystem")
                TimeZoneBias = LTZone.CurrentTimeZone
            Next
        End With
        TimeZoneBias = TimeZoneBias * -1
    End Function

End Class

是的,您错过了主要问题 - 假设今天是2013年2月19日,我想将一个时间戳从PDT(我的服务器时区)转换为UTC,例如2008年6月5日(夏令时期间)。使用您描述的方法将给出一个值,该值与正确值相差60分钟(因为当前时间是PST而不是PDT,所以该历史日期的偏移量将不正确)。 - GWR
等一下,为什么你在使用当前时间?我以为你可以从我的骨头中挖掘出这个想法并完成剩下的部分,或者你需要更多的帮助? - Panayot Karabakalov
所谓“当前”,是指您的wmi查询在您请求时返回的偏移量,可能与您尝试转换的日期时间的偏移量不同。 - GWR
好的,我明白了,我的问题是我太过于信任微软了 :) - Panayot Karabakalov
感谢您的所有努力。您最后一次迭代看起来可以解决问题。我最终选择了使用JavaScript的不同解决方案。将在下面发布。 - GWR

0
这个 ASP VBScript 函数集合可能会满足您的需求。我从 MySQL 数据库中获取 Unix 时间戳。根据您的数据库,查询可能略有不同,但大多数数据库都可以返回 Unix 时间戳。在我的服务器上,查询只需要一两毫秒。
注意:`pquery` 是我自己运行 SQL 查询的函数。显然,请替换为您自己的方法。
每次运行 `unixTime()` 时,我都会计算偏移量。当然,如果您愿意,您可以在“引导”文件中计算一次并设置全局变量。
Function unixTimestamp()
    ' Unix Timestamp, drawn from MySQL because ASP doesn't natively handle time zones
    rs = pquery( "SELECT unix_timestamp() AS `ts`", null )
    unixTimestamp = cLng( rs("ts") )
End Function

Function utc_offset()
    ' Offset of Server time from UTC, in seconds
    dim unixNow : unixNow = unixTimestamp()
    dim aspNow  : aspNow  = cLng( DateDiff( "s", "01/01/1970 00:00:00", now() ) )
    utc_offset = cLng( ( aspNow - unixNow ))
End Function

Function unixTime( byVal dt )
    dim offset : offset = utc_offset()
    if( VarType( dt ) = vbDate ) then
        unixTime = cLng( DateDiff( "s", "01/01/1970 00:00:00", dt ) - offset )
    else
        if( isDate( dt ) ) then
            dt = cDate( dt )
            unixTime = cLng( DateDiff( "s", "01/01/1970 00:00:00", dt ) - offset )
        else
            unixTime = null
        end if
    end if
End Function

Function datetimeFromUnix( byVal udt, offset )
    datetimeFromUnix = cDate( DateAdd( "s", udt + offset , "01/01/1970 00:00:00" ) )
End Function

现在运行:

utcTimestamp = unixTime( dDate )
utcDate = datetimeFromUnix( utcTimestamp, 0 )   ' UTC
localDate = datetimeFromUnix( utcTimestamp, utc_offset() )  ' back where we started

这些很好,但不幸的是,在转换历史日期时,它们没有解决历史日期当时是否实行夏令时的问题(除非我漏掉了什么或者误解了你的代码)。 - GWR
哎呀,你说得对。这是一个难以计算的问题,因为许多不同的地区有不同的规则。 - Stephen R
是的。根据特定的用例,我们过去做过一些事情,其中必须有一个查找表(或您喜欢使用的任何数据结构),其中包含历史日期范围和DST开始/结束日期,这些日期必须考虑到历史转换。现在,在我编写的任何新软件或代码中,我通常尝试将值保存为utc,并同时存储源时区/偏移量/dst信息,以便将来可能需要进行任何转换工作。过度?也许。但我发现这是一个难以通过其他方式解决的棘手问题。 - GWR
第一次就做对总是更容易的!;-) - Stephen R
这实际上是个好主意。可以节省由于历史转换查询所创建的延迟。下次我需要处理这个问题时,可能会采用这种方法。 - GWR
显示剩余3条评论

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