Delphi - 需要与C# DateTime.IsDaylightSavingTime()方法等效的方法

8
我需要一种方法来确定某个 TDateTime 值是否在我的时区的夏令时范围内(在 C# 中,DateTime.IsDaylightSavingTime() 方法可以实现相同的功能)。
我知道 Delphi 中没有类似的函数,因为 Delphi 的 TDateTime 不包含有关时区的信息,但我想使用 Win32 API 的某种方式来实现这一点。
我已经查看了 Win32 API GetTimeZoneInformationGetTimeZoneInformationForYear 函数,但我不太明白如何使用它们,所以我想请你帮忙。非常感谢任何提示。
编辑:
例如:
在我的时区(中欧),今年夏令时从 3 月 28 日上午 2 点开始,到 2010 年 10 月 31 日上午 3 点结束。
我需要一个具有以下标题的函数:
function IsDaylightSavingTime(input: TDateTime): boolean;

如果输入日期在2010年3月28日2:00至2010年10月31日3:00之间,将返回true,否则返回false。(该示例仅适用于2010年,但我需要它适用于所有年份。)

再次强调,我知道仅保存在TDateTime中的信息是不够的,但我认为通过一些Win32 API函数,我应该能够从Windows设置中获取有关当前时区的信息。

7个回答

3
这并不像听起来那么简单,因为:
1)夏令时和标准时间之间的切换日期对于所有国家来说都不相同。
2)对于同一国家的所有年份,夏令时和标准时间之间的切换日期算法也不相同(例如,在中欧,以前是四月第一个星期日,现在是三月最后一个星期日)。美国从2007年开始,将切换日期从四月第一个星期日改为三月第二个星期日。
因此,仅仅提供一个简单的日期是不够的,你还需要知道地理位置。
但是,如果你可以接受这样一个事实,即你仅限于使用当前年份、当前地区(国家)的当前算法计算出的切换日期,并且这可能会在未来和过去的日期上出错,那么你可以使用TIME_ZONE_INFORMATION中的信息来计算切换日期。
USES Windows,SysUtils,DateUtils;

FUNCTION GetDaylightSavingsSwitchOverDates(Year : Cardinal ; VAR Start,Stop : TDateTime) : BOOLEAN;

  VAR
    TZ : TTimeZoneInformation;

  FUNCTION DecodeSwitchOverDate(Year : Cardinal ; CONST Time : TSystemTime) : TDateTime;
    VAR
      I : Cardinal;

    BEGIN
      Result:=EncodeDateTime(Year,Time.wMonth,1,Time.wHour,Time.wMinute,Time.wSecond,0);
      IF Time.wDay=5 THEN BEGIN
        Result:=DateOf(EndOfTheMonth(Result))+TimeOf(Result);
        WHILE PRED(DayOfWeek(Result))<>Time.wDayOfWeek DO
          Result:=IncDay(Result,-1)
        END
      ELSE BEGIN
        WHILE PRED(DayOfWeek(Result))<>Time.wDayOfWeek DO Result:=IncDay(Result);
        FOR I:=1 TO PRED(Time.wDay) DO Result:=IncWeek(Result)
      END
    END;

  BEGIN
    IF GetTimeZoneInformation(TZ)=TIME_ZONE_ID_UNKNOWN THEN
      Result:=FALSE
    ELSE BEGIN
      Start:=DecodeSwitchOverDate(Year,TZ.DaylightDate);
      Stop:=DecodeSwitchOverDate(Year,TZ.StandardDate);
      Result:=TRUE
    END
  END;

FUNCTION StartOfDST(Year : Cardinal) : TDateTime;
  VAR
    Stop : TDateTime;

  BEGIN
    IF NOT GetDaylightSavingsSwitchOverDates(Year,Result,Stop) THEN Result:=0
  END;

FUNCTION EndOfDST(Year : Cardinal) : TDateTime;
  VAR
    Start : TDateTime;

  BEGIN
    IF NOT GetDaylightSavingsSwitchOverDates(Year,Start,Result) THEN Result:=0
  END;

在我的电脑上(中欧时区),循环遍历2000年至2020年间的日期,结果如下:

DST in 2000: Sun 26 Mar 2000 02:00:00 through Sun 29 Oct 2000 03:00:00
DST in 2001: Sun 25 Mar 2001 02:00:00 through Sun 28 Oct 2001 03:00:00
DST in 2002: Sun 31 Mar 2002 02:00:00 through Sun 27 Oct 2002 03:00:00
DST in 2003: Sun 30 Mar 2003 02:00:00 through Sun 26 Oct 2003 03:00:00
DST in 2004: Sun 28 Mar 2004 02:00:00 through Sun 31 Oct 2004 03:00:00
DST in 2005: Sun 27 Mar 2005 02:00:00 through Sun 30 Oct 2005 03:00:00
DST in 2006: Sun 26 Mar 2006 02:00:00 through Sun 29 Oct 2006 03:00:00
DST in 2007: Sun 25 Mar 2007 02:00:00 through Sun 28 Oct 2007 03:00:00
DST in 2008: Sun 30 Mar 2008 02:00:00 through Sun 26 Oct 2008 03:00:00
DST in 2009: Sun 29 Mar 2009 02:00:00 through Sun 25 Oct 2009 03:00:00
DST in 2010: Sun 28 Mar 2010 02:00:00 through Sun 31 Oct 2010 03:00:00
DST in 2011: Sun 27 Mar 2011 02:00:00 through Sun 30 Oct 2011 03:00:00
DST in 2012: Sun 25 Mar 2012 02:00:00 through Sun 28 Oct 2012 03:00:00
DST in 2013: Sun 31 Mar 2013 02:00:00 through Sun 27 Oct 2013 03:00:00
DST in 2014: Sun 30 Mar 2014 02:00:00 through Sun 26 Oct 2014 03:00:00
DST in 2015: Sun 29 Mar 2015 02:00:00 through Sun 25 Oct 2015 03:00:00
DST in 2016: Sun 27 Mar 2016 02:00:00 through Sun 30 Oct 2016 03:00:00
DST in 2017: Sun 26 Mar 2017 02:00:00 through Sun 29 Oct 2017 03:00:00
DST in 2018: Sun 25 Mar 2018 02:00:00 through Sun 28 Oct 2018 03:00:00
DST in 2019: Sun 31 Mar 2019 02:00:00 through Sun 27 Oct 2019 03:00:00
DST in 2020: Sun 29 Mar 2020 02:00:00 through Sun 25 Oct 2020 03:00:00

但至少有些年份是错误的,因为算法已经在列出的年份中从我的语言环境改变了。

那么您的函数将如下:

FUNCTION IsDaylightSavingTime(Input : TDateTime) : BOOLEAN;
  VAR
    Start,Stop : TDateTime;

  BEGIN
    Result:=GetDaylightSavingsSwitchOverDates(YearOf(Input),Start,Stop) AND (Input>=Start) AND (Input<Stop)
  END;

3
也许对于你的特定应用来说有点过度,但开源项目 "Delphi中的Olson时区数据库" 允许访问 tz数据库 项目支持的所有时区。tz数据库会定期更新最新的夏令时更改或修复

TZDB可以在Delphi 6、7、8、9、10、2007、2009、2010和XE或FreePascal 2.0及更高版本上编译。TZDB与Delphi XE一起使用效果最佳,因为它将TTimeZone类引入RTL。


1

Ondra C. -

是的,你说得对。你需要:

  1. 将Delphi TDateTime变量设置为所需日期/时间

  2. 将其转换为Windows SystemTime

  3. 调用GetTimeZoneInformation()以获取TTimeZoneInformation

  4. 使用您的TTimeZoneInformation结构调用GetTimeZoneInformationForYear(),以获取所在时区的DST信息(我不确定在哪里可以获得任意时区的TTimeZoneInformation - 但您应该能够在MSDN上找到它)。

  5. 进行算术运算,查看您的系统时间是否发生在TTZI.StandardDate之后(在这种情况下,它为标准时间),或者在TTZI.DaylightDate之后(在这种情况下,它为DST)。

或者...

也许您可以将其转换为Delphi表格:

http://www.twinsun.com/tz/tz-link.htm

对于任何时区中的任何日期时间,只需查看给定的日期时间是否处于DST之内或之外。哇!无需使用Microsoft API,只需进行简单的表格查找或if / else语句块即可!希望这可以帮助您.. pSM

0

我使用了 .net 反编译器来查看这个函数在 .net 中的实现。它被定义如下,也许你可以将其中的数学部分转换成 Delphi?如果你需要深入挖掘,我建议你自己打开反编译器。我认为这会对你有所帮助!

public static bool IsDaylightSavingTime(DateTime time, DaylightTime daylightTimes)
{
    return (CalculateUtcOffset(time, daylightTimes) != TimeSpan.Zero);
}

internal static TimeSpan CalculateUtcOffset(DateTime time, DaylightTime daylightTimes)
{
    if (daylightTimes != null)
    {
        DateTime time4;
        DateTime time5;
        if (time.Kind == DateTimeKind.Utc)
        {
            return TimeSpan.Zero;
        }
        DateTime time2 = daylightTimes.Start + daylightTimes.Delta;
        DateTime end = daylightTimes.End;
        if (daylightTimes.Delta.Ticks > 0L)
        {
            time4 = end - daylightTimes.Delta;
            time5 = end;
        }
        else
        {
            time4 = time2;
            time5 = time2 - daylightTimes.Delta;
        }
        bool flag = false;
        if (time2 > end)
        {
            if ((time >= time2) || (time < end))
            {
                flag = true;
            }
        }
        else if ((time >= time2) && (time < end))
        {
            flag = true;
        }
        if ((flag && (time >= time4)) && (time < time5))
        {
            flag = time.IsAmbiguousDaylightSavingTime();
        }
        if (flag)
        {
            return daylightTimes.Delta;
        }
    }
    return TimeSpan.Zero;
}

0

我知道这不是你问题的答案,但你可能会对以下两个函数感兴趣:SystemTimeToTzSpecificLocalTime()TzSpecificLocalTimeToSystemTime()。第一个函数将通用时间转换为指定时区的相应时间(其中 nil 表示本地时区)。另一个函数则反过来,但仅包含在 Windows XP 及以上版本中,如 Borland 帮助所述。如果您只需要进行时区相关的时间转换,它们应该可以满足您的需求。而且很好知道它们会检查给定的 UTC 时间是夏令时还是标准时间。我没有在任何地方读到过,但我自己检查了一下,请纠正我如果我错了。

请看以下函数。我不确定是否会在任何地方使用它,因为我不确定它的正确性,但它可能值得一看。请不要告诉我这是一个愚蠢的方法,因为我知道 :-)。

function IsDaylightSavingTime(lLocalTime: TDateTime): boolean;
var
  lUniversalSystemTime: TSystemTime;
  lLocalSystemTime: TSystemTime;
  lTimeZoneInfo: TTimeZoneInformation;
begin
  case GetTimeZoneInformation(lTimeZoneInfo) of
    TIME_ZONE_ID_UNKNOWN:
      begin
        Result := False;
        Exit;
      end;

    TIME_ZONE_ID_STANDARD,
    TIME_ZONE_ID_DAYLIGHT: ;

  else
    //TIME_ZONE_ID_INVALID:
    RaiseLastOSError();
  end;

  DateTimeToSystemTime(lLocalTime, lLocalSystemTime);
  if not TzSpecificLocalTimeToSystemTime(nil, lLocalSystemTime, lUniversalSystemTime) then
    RaiseLastOSError();

  Result := SameTime(SystemTimeToDateTime(lUniversalSystemTime),
    IncMinute(lLocalTime, lTimeZoneInfo.DaylightBias + lTimeZoneInfo.Bias));
end;

0
正如你所说,Delphi中的日期时间并不包含这些信息,所以你不能简单地移植它们。每个生成tdatetime的例程都必须添加这些信息,而在Delphi中并非如此。
也许你应该更详细地解释你真正想做什么,并通过叙述问题本身来描述问题。

信息也没有捆绑在 .Net 中。只是 .Net 类型有一些方法,使它看起来像存储了比实际更多的信息。该方法只是使用相同的底层 Windows API,这就是为什么 .Net 方法在 Windows XP 上的行为不同的原因。 - Rob Kennedy

0

您可以在JEDI Code Library(开源)的JclDateTime.pas单元中找到此示例,其中包含LocalDateTimeToDateTime函数。夏令时信息被检索并用于将本地时间转换为UTC时间以及从UTC时间转换回本地时间。


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