如何在Delphi中将本地时间转换为UTC时间?以及如何从UTC转换回本地时间?

20

我正在使用 Delphi,尝试在数据库中存储记录,并使用 UTC datetime,在客户端读取时将其转换为本地时间。你有什么关于这个双向转换的想法吗?

6个回答

28

这是我用于将UTC时间转换为本地时间的函数。

function LocalDateTimeFromUTCDateTime(const UTCDateTime: TDateTime): TDateTime;
var
  LocalSystemTime: TSystemTime;
  UTCSystemTime: TSystemTime;
  LocalFileTime: TFileTime;
  UTCFileTime: TFileTime;
begin
  DateTimeToSystemTime(UTCDateTime, UTCSystemTime);
  SystemTimeToFileTime(UTCSystemTime, UTCFileTime);
  if FileTimeToLocalFileTime(UTCFileTime, LocalFileTime) 
  and FileTimeToSystemTime(LocalFileTime, LocalSystemTime) then begin
    Result := SystemTimeToDateTime(LocalSystemTime);
  end else begin
    Result := UTCDateTime;  // Default to UTC if any conversion function fails.
  end;
end;

如您所见,该函数将UTC日期时间转换为以下内容:

  • 日期时间 -> 系统时间
  • 系统时间 -> 文件时间
  • 文件时间 -> 本地文件时间(这是从UTC到本地的转换)
  • 本地文件时间 -> 系统时间
  • 系统时间 -> 日期时间

很明显如何反向操作。


请注意,此转换将夏令时视为现在的状态,而不是在转换时是/曾经的状态。XE引入了DateUtils.TTimeZone类型,试图解决这个问题。代码如下:

LocalDateTime := TTimeZone.Local.ToLocalTime(UniversalDateTime);

在另一个方向上使用 ToUniversalTime

这个类似乎(宽泛地)基于 .net 的 TimeZone 类。

一个警告。不要期望尝试转换时考虑到夏令时的尝试 100%准确。这是根本不可能做到的。至少不用时间机器。而且这只考虑了未来的时间。即使是过去的时间也很复杂。Raymond Chen 在这里讨论了这个问题:为什么夏令时是非直观的


13

你可以使用来自kernel32的TzSpecificLocalTimeToSystemTime和SystemTimeToTzSpecificLocalTime函数。

var
  Form1: TForm1;

function TzSpecificLocalTimeToSystemTime(lpTimeZoneInformation: PTimeZoneInformation; var lpLocalTime, lpUniversalTime: TSystemTime): BOOL; stdcall;
function SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation: PTimeZoneInformation; var lpUniversalTime,lpLocalTime: TSystemTime): BOOL; stdcall;

implementation

function TzSpecificLocalTimeToSystemTime; external kernel32 name 'TzSpecificLocalTimeToSystemTime';
function SystemTimeToTzSpecificLocalTime; external kernel32 name 'SystemTimeToTzSpecificLocalTime';

{$R *.dfm}


Function DateTime2UnivDateTime(d:TDateTime):TDateTime;
var
 TZI:TTimeZoneInformation;
 LocalTime, UniversalTime:TSystemTime;
begin
  GetTimeZoneInformation(tzi);
  DateTimeToSystemTime(d,LocalTime);
  TzSpecificLocalTimeToSystemTime(@tzi,LocalTime,UniversalTime);
  Result := SystemTimeToDateTime(UniversalTime);

end;

Function UnivDateTime2LocalDateTime(d:TDateTime):TDateTime;
var
 TZI:TTimeZoneInformation;
 LocalTime, UniversalTime:TSystemTime;
begin
  GetTimeZoneInformation(tzi);
  DateTimeToSystemTime(d,UniversalTime);
  SystemTimeToTzSpecificLocalTime(@tzi,UniversalTime,LocalTime);
  Result := SystemTimeToDateTime(LocalTime);
end;


procedure TForm1.Button1Click(Sender: TObject);
var

 Date,Univdate,DateAgain:TDateTime;

begin
  Date := Now;
  Univdate := DateTime2UnivDateTime(Date);
  DateAgain := UnivDateTime2LocalDateTime(Univdate);
  Showmessage(DateTimeToStr(Date) +#13#10 + DateTimeToStr(Univdate)+#13#10 + DateTimeToStr(DateAgain));
end;

你知道SystemTimeToTzSpecificLocalTime是否支持“历史”夏令时信息,例如tzdb吗? - mjn
SystemTimeToTzSpecificLocalTime函数在以下情况下可能会错误地计算本地时间: 当时区在旧年份和新年份使用不同的UTC偏移量时。 要转换的UTC时间和计算出的本地时间处于不同的年份。http://msdn.microsoft.com/de-de/library/windows/desktop/ms724949(v=vs.85).aspx - bummi
1
上述代码中有一个小错误。在 UnivDateTime2LocalDateTime 函数中,DateTimeToSystemTime(d,LocalTime); 这行应改为 DateTimeToSystemTime(d,UniversalTime);。 - Bob Moss

13

如果有人需要2020年的答案:

请确保包含以下内容:

Uses 
System.DateUtils;

然后是针对协调世界时 (UTC)。

function GetUTC(dt: TDateTime): TDateTime;
begin
  result := TTimeZone.Local.ToUniversalTime(dt);
end;

并且为当地时间

function GetLocalTime(dt: TDateTime):TDateTime;
begin
  result := TTimeZone.Local.ToLocalTime(dt);
end;

至少在XE2中,当夏令时为30分钟时无法工作。 - Jose

10

我认为这不适用于有30分钟夏令时的国家。 - Jose

2
function NowUTC: TDateTime;
Var UTC: TSystemTime;
begin
  GetSystemTime(UTC);
  Result := SystemTimeToDateTime(UTC);
end;

function UTCToLocal(UTC: TDateTime): TDateTime;
begin
  Result := IncMinute(UTC, UTCToLocalTimeOffsetMinutes);
end;

function UTCToLocalTimeOffsetMinutes: Int16;
Var UTC: TSystemTime;
    UTC2: TSystemTime;
    t  : TDateTime;
    t2 : TDateTime;
begin
  GetSystemTime(UTC);
  GetLocalTime(UTC2);
  t  := SystemTimeToDateTime(UTC);
  t2 := SystemTimeToDateTime(UTC2);
  Result := System.DateUtils.MinutesBetween(t,t2);
end;

1
如果您的客户使用本地的Delphi应用程序,则可以使用System日期函数来完成此操作。但是,如果您处于客户端/服务器环境中(例如,Delphi应用程序是Web服务器,并且客户端仅接收HTML页面),则需要以不同的方式转换为用户的本地时间。服务器需要知道用户的时区,并进行适当的转换。此外,夏令时可能会在应用程序需要转换历史数据时带来麻烦-您需要知道用户所在地区是否实行夏令时。在这些用例中,Delphi时区数据库可能会有所帮助。

TZDB提供了一个简单的数据库和专门的时区类,允许访问Time Zone Database项目支持的所有时区。

我不知道有SystemTimeToTzSpecificLocalTime,但我刚读到Jon Skeet更喜欢TZDB来处理时区。

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