在VBA中,如何以简单的方式将UTC UNIX时间戳转换为本地时区日期?

时间:2017-02-08 17:56:30

标签: vba timezone utc dst

据我所知,我们可以使用以下内容将UNIX时间戳粗略地转换为VB日期

CDate([UNIX时间戳] / 60/60/24)+" 1/1/1970"

但是,不考虑时区和日光信息。

时区不是什么大问题。但我无法获得特定UNIX时间戳的日光偏差信息。

尽管日期1/1的日光偏差明显不同于日期6/1,但是对于日期3/12或日期11/5,日光偏差计算非常复杂。

我尝试了几个API,例如“FileTimeToLocalFileTime”和“GetTimeZoneInformation”,但它们都不起作用。

这是我的代码无法处理日光偏差

Option Explicit

#If VBA7 Then
  Private Declare PtrSafe Function LocalFileTimeToFileTime Lib "kernel32" (src@, tgt@) As Long
  Private Declare PtrSafe Function FileTimeToLocalFileTime Lib "kernel32" (src@, tgt@) As Long
#Else
  Private Declare Function LocalFileTimeToFileTime Lib "kernel32" (src@, tgt@) As Long
  Private Declare Function FileTimeToLocalFileTime Lib "kernel32" (src@, tgt@) As Long
#End If

Public Function ToUTC(ByVal datetime As Date) As Date
  Dim ftLoc@, ftUtc@
  ftLoc = (datetime - #1/1/1601#) * 86400000
  LocalFileTimeToFileTime ftLoc, ftUtc
  ToUTC = ftUtc / 86400000# + #1/1/1601#
End Function

Public Function FromUTC(ByVal datetime As Date) As Date
  Dim ftUtc@, ftLoc@
  ftUtc = (datetime - #1/1/1601#) * 86400000
  FileTimeToLocalFileTime ftUtc, ftLoc
  FromUTC = ftLoc / 86400000# + #1/1/1601#
End Function

Function getDateFromTimestamp(ByVal value) As Date
    Dim t1, t2
    t1 = CDate(value / 60 / 60 / 24) + "1/1/1970"
    t2 = FromUTC(t1)
    Debug.Print t2 - t1
    getDateFromTimestamp = t2
End Function

1 个答案:

答案 0 :(得分:2)

可能最简单的方法是通过COM Interop使用.NET内置的DateTime API。这种方法有一个很好的介绍in this answer

创建一个C#类库。为了简化操作,请使用.NET Framework 4.6或更高版本,以便在FromUnixTimeSeconds类上使用ToUnixTimeSecondsDateTimeOffset方法。

using System;
using System.Runtime.InteropServices;

namespace MyDateTimeLibrary
{
    [Serializable]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class DateTimeFunctions
    {
        public DateTime UnixTimeToDateTime(int unixTimeInSeconds)
        {
            return DateTimeOffset.FromUnixTimeSeconds(unixTimeInSeconds).UtcDateTime;
        }

        public long DateTimeUtcToUnixTime(DateTime utcDateTime)
        {
            return new DateTimeOffset(utcDateTime, TimeSpan.Zero).ToUnixTimeSeconds();
        }

        public DateTime UtcToLocal(DateTime utcDateTime)
        {
            return utcDateTime.ToLocalTime();
        }

        public DateTime LocalToUtc(DateTime localDateTime)
        {
            return localDateTime.ToUniversalTime();
        }

        public DateTime TZSpecificDateTimeToUTC(DateTime sourceDateTime, string sourceTimeZoneId)
        {
            var tzi = TimeZoneInfo.FindSystemTimeZoneById(sourceTimeZoneId);
            return TimeZoneInfo.ConvertTimeToUtc(sourceDateTime, tzi);
        }

        public DateTime UTCToTZSpecificDateTime(DateTime utcDateTime, string destinationTimeZoneId)
        {
            var tzi = TimeZoneInfo.FindSystemTimeZoneById(destinationTimeZoneId);
            return TimeZoneInfo.ConvertTimeFromUtc(utcDateTime, tzi);
        }
    }
}

对于你提出的问题,你真的只需要上面代码中的UnixTimeToDateTimeUtcToLocal方法,但我想我会为可能处于类似情况的其他人投入更多。真的,你可以在这里做任何你在.NET中可以做的事情。 .NET DateTimeDateTimeOffsetTimeZoneInfo API比您执行类似工作所需的Win32 API更容易,更可靠。

在构建之前检查Register for COM interop选项(如前面链接的答案中所述),或者如果要部署到另一台计算机,则通过RegAsm.exe实用程序向COM注册DLL。

接下来,将您的VBA项目中的引用添加到新注册的MyDateTimeLibrary。然后使用它,就像这样(例如):

Sub Test()

    Dim dtf As New MyDateTimeLibrary.DateTimeFunctions
    Dim utcDt As Date, localDt As Date

    utcDt = dtf.UnixTimeToDateTime(1487010504)
    localDt = dtf.UtcToLocal(utcDt)

    MsgBox ("UTC: " + CStr(utcDt) + vbCrLf + "Local: " + CStr(localDt))

    Set dtf = Nothing

End Sub

screenshot

我确信你可以重构这个来编写一些有用的VBA函数来传入和传出日期来转换,从Excel单元格中调用它们,或者你的上下文。我会把那部分留给你。