From e9f864d150b8f848dffa62aaa33bc9db7d11ed8d Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Fri, 15 Apr 2016 13:37:58 +0000 Subject: [PATCH] Fixed: Daylight Saving Time dependant on the target date instead of the current settings --- UnitTests/Units/PersistTest.pas | 25 ++++- UnitTests/Units/XMLDataBindingUtilsTest.pas | 64 +++++++++++- XMLDataBindingUtils.pas | 108 +++++++++++++++----- 3 files changed, 168 insertions(+), 29 deletions(-) diff --git a/UnitTests/Units/PersistTest.pas b/UnitTests/Units/PersistTest.pas index 0751d15..8f930e3 100644 --- a/UnitTests/Units/PersistTest.pas +++ b/UnitTests/Units/PersistTest.pas @@ -72,6 +72,11 @@ type function WriteInteger(const AName: String; AValue: Integer): Boolean; override; function WriteString(const AName: String; const AValue: String): Boolean; override; + procedure GetKeys(const ADest: TStrings); override; + procedure GetSections(const ADest: TStrings); override; + procedure DeleteKey(const AName: string); override; + procedure DeleteSection(const AName: string); override; + property Output: IPersistTestOutput read FOutput write FOutput; end; @@ -191,7 +196,7 @@ var testFiler: TX2UtPersistTestFiler; begin - testFiler := TX2UtPersistTestFiler.Create(AIsReader);; + testFiler := TX2UtPersistTestFiler.Create(AIsReader); testFiler.Output := Self.Output; Result := testFiler; @@ -266,6 +271,24 @@ begin end; +procedure TX2UtPersistTestFiler.GetKeys(const ADest: TStrings); +begin +end; + +procedure TX2UtPersistTestFiler.GetSections(const ADest: TStrings); +begin +end; + +procedure TX2UtPersistTestFiler.DeleteKey(const AName: string); +begin +end; + +procedure TX2UtPersistTestFiler.DeleteSection(const AName: string); +begin +end; + + + { TPersistTestOutput } constructor TPersistTestOutput.Create(); begin diff --git a/UnitTests/Units/XMLDataBindingUtilsTest.pas b/UnitTests/Units/XMLDataBindingUtilsTest.pas index 4654277..1d099b0 100644 --- a/UnitTests/Units/XMLDataBindingUtilsTest.pas +++ b/UnitTests/Units/XMLDataBindingUtilsTest.pas @@ -10,12 +10,16 @@ type published procedure TestIsValidXMLChar; procedure TestGetValidXMLText; + procedure TestXMLToDateTime; + procedure TestXMLToDate; + procedure TestDateTimeToXML; + procedure TestDateToXML; end; implementation uses - XMLDataBindingUtils; + XMLDataBindingUtils, DateUtils, SysUtils; { TXMLDataBindingUtilsTest } @@ -33,6 +37,64 @@ begin end; +procedure TXMLDataBindingUtilsTest.TestXMLToDateTime; +var + dateInWintertime, dateInSummerTime : TDateTime; +begin + // Local Time + dateInWintertime := EncodeDateTime(2016, 2, 2, 00, 59, 59, 0); + dateInSummerTime := EncodeDateTime(2016, 4, 9, 00, 59, 59, 0); + + // Wintertijd + CheckEquals(dateInWintertime, XMLToDateTime('2016-02-01T23:59:59Z', xdtDateTime)); + CheckEquals(IncMilliSecond(dateInWintertime, 678), XMLToDateTime('2016-02-01T23:59:59.678Z', xdtDateTime)); + CheckEquals(dateInWintertime, XMLToDateTime('2016-02-02T00:59:59+01:00', xdtDateTime)); + CheckEquals(IncMilliSecond(dateInWintertime, 678), XMLToDateTime('2016-02-02T00:59:59.678+01:00', xdtDateTime)); + + // Zomertijd + CheckEquals(dateInSummerTime, XMLToDateTime('2016-04-08T22:59:59Z', xdtDateTime)); + CheckEquals(IncMilliSecond(dateInSummerTime, 678), XMLToDateTime('2016-04-08T22:59:59.678Z', xdtDateTime)); + CheckEquals(dateInSummerTime, XMLToDateTime('2016-04-09T00:59:59+02:00', xdtDateTime)); + CheckEquals(IncMilliSecond(dateInSummerTime, 678), XMLToDateTime('2016-04-09T00:59:59.678+02:00', xdtDateTime)); +end; + + +procedure TXMLDataBindingUtilsTest.TestXMLToDate; +begin + CheckEquals(EncodeDate(2016, 2, 2), XMLToDateTime('2016-02-02', xdtDate)); + CheckEquals(EncodeDate(2016, 4, 9), XMLToDateTime('2016-04-09', xdtDate)); +end; + + +procedure TXMLDataBindingUtilsTest.TestDateTimeToXML; +var + dateInWintertime, dateInSummerTime : TDateTime; +begin + dateInWintertime := EncodeDateTime(2016, 2, 1, 14, 59, 59, 0); + dateInSummerTime := EncodeDateTime(2016, 4, 8, 14, 59, 59, 0); + + // Wintertijd + CheckEquals('2016-02-01T13:59:59+01:00', DateTimeToXML(XMLToDateTime('2016-02-01T12:59:59Z', xdtDateTime), xdtDateTime, [xtfTimezone])); + CheckEquals('2016-02-01T13:59:59.678+01:00', DateTimeToXML(IncMilliSecond(XMLToDateTime('2016-02-01T12:59:59Z', xdtDateTime), 678), xdtDateTime, [xtfTimezone, xtfMilliseconds])); + CheckEquals('2016-02-01T14:59:59+01:00', DateTimeToXML(dateInWintertime, xdtDateTime, [xtfTimezone])); + CheckEquals('2016-02-01T14:59:59.678+01:00', DateTimeToXML(IncMilliSecond(dateInWintertime, 678), xdtDateTime, [xtfTimezone, xtfMilliseconds])); + + // Zomertijd + CheckEquals('2016-04-08T14:59:59+02:00', DateTimeToXML(XMLToDateTime('2016-04-08T12:59:59Z', xdtDateTime), xdtDateTime, [xtfTimezone])); + CheckEquals('2016-04-08T14:59:59.678+02:00', DateTimeToXML(IncMilliSecond(XMLToDateTime('2016-04-08T12:59:59Z', xdtDateTime), 678), xdtDateTime, [xtfTimezone, xtfMilliseconds])); + CheckEquals('2016-04-08T14:59:59+02:00', DateTimeToXML(dateInSummerTime, xdtDateTime, [xtfTimezone])); + CheckEquals('2016-04-08T14:59:59.678+02:00', DateTimeToXML(IncMilliSecond(dateInSummerTime, 678), xdtDateTime, [xtfTimezone, xtfMilliseconds])); +end; + + +procedure TXMLDataBindingUtilsTest.TestDateToXML; +begin + CheckEquals('2016-02-02', DateTimeToXML(EncodeDate(2016, 2, 2), xdtDate)); + CheckEquals('2016-04-09', DateTimeToXML(EncodeDate(2016, 4, 9), xdtDate)); +end; + + + initialization RegisterTest(TXMLDataBindingUtilsTest.Suite); diff --git a/XMLDataBindingUtils.pas b/XMLDataBindingUtils.pas index 74a1ac6..3e632db 100644 --- a/XMLDataBindingUtils.pas +++ b/XMLDataBindingUtils.pas @@ -18,7 +18,7 @@ type TXMLDateTimeFormat = (xdtDateTime, xdtDate, xdtTime); TXMLTimeFragment = (xtfMilliseconds, xtfTimezone); TXMLTimeFragments = set of TXMLTimeFragment; - + TDateConvert = (dcToUtc, dcToLocal); IXSDValidate = interface ['{3BFDC851-7459-403B-87B3-A52E9E85BC8C}'] @@ -197,12 +197,79 @@ begin end; +function InDSTSpan(ADate: TDateTime; ATimeZoneInfo: TTimeZoneInformation): boolean; +var + lowerDayLight: TDateTime; + upperDayLight: TDateTime; + day: TDate; + days: Integer; + + function GetDay(AYear, AMonth, ADay, ADayOfWeek: Integer): TDate; + var + I, Counter : Integer; + begin + Result := 0; + Counter := 0; + + days := DaysInAMonth(AYear, AMonth); + for I := 1 to days do + begin + Result := EncodeDate(AYear, AMonth, I); + // Delphi DayOfWeek 1 = Sunday + // TimeZoneInfo.wDayOfWeek 0 = Sunday + if DayOfWeek(Result) -1 = ADayOfWeek then + begin + inc(Counter); + if (counter = ADay) or ((Counter < Aday) and (I >= days - 6)) then + break; + end; + end; + end; + +begin + with ATimeZoneInfo.DaylightDate do + begin + day := GetDay(wYear + YearOf(ADate), wMonth, wDay, wDayOfWeek); + lowerDayLight := day + EncodeTime(wHour, wMinute, wSecond, wMilliseconds); + end; + + with ATimeZoneInfo.StandardDate do + begin + day := GetDay(wYear + YearOf(ADate), wMonth, wDay, wDayOfWeek); + upperDayLight := day + EncodeTime(wHour, wMinute, wSecond, wMilliseconds); + end; + + Result := (ADate >= lowerDayLight) and (ADate <= upperDayLight); +end; + + +function ConvertDate(ADate: TDateTime; ADateconvert: TDateConvert): TDateTime; +var + timeZone: TTimeZoneInformation; + timeZoneID: Cardinal; + localOffset: Integer; + +begin + FillChar(timeZone, SizeOf(TTimeZoneInformation), #0); + timeZoneID := GetTimeZoneInformation(timeZone); + + if timeZoneID in [TIME_ZONE_ID_STANDARD, TIME_ZONE_ID_DAYLIGHT] then + localOffset := -timeZone.Bias - IfThen(InDSTSpan(ADate, timeZone), timeZone.DaylightBias, timeZone.StandardBias) + else + localOffset := 0; + + if ADateconvert = dcToUtc then + localOffset := localOffset * -1; + + Result := IncMinute(ADate, localOffset); +end; + function DateTimeToXML(ADate: TDateTime; AFormat: TXMLDateTimeFormat; ATimeFragments: TXMLTimeFragments): string; var formatSettings: TFormatSettings; - timeZone: TTimeZoneInformation; - timeOffset: Integer; + utcDate: TDateTime; + offsetMinutes: Integer; begin formatSettings := GetDefaultFormatSettings; @@ -213,21 +280,16 @@ begin if xtfMilliseconds in ATimeFragments then Result := Result + FormatDateTime(XMLMsecsFormat, ADate); - if xtfTimezone in ATimeFragments then + if (xtfTimezone in ATimeFragments) then begin - FillChar(timeZone, SizeOf(TTimeZoneInformation), #0); - if GetTimeZoneInformation(timeZone) <> TIME_ZONE_ID_INVALID then - begin - timeOffset := -timeZone.Bias; + utcDate := ConvertDate(ADate, dcToUtc); + offsetMinutes := MinutesBetween(ADate, utcDate); - if timeOffset = 0 then - Result := Result + XMLTimezoneZulu - else - Result := Result + Format(XMLTimezoneFormat, - [XMLTimezoneSigns[timeOffset > 0], - Abs(timeZone.Bias div 60), - Abs(timeZone.Bias mod 60)]); - end; + if offsetMinutes = 0 then + Result := Result + XMLTimezoneZulu + else + Result := Result + Format(XMLTimezoneFormat, + [XMLTimezoneSigns[offsetMinutes > 0], offsetMinutes div 60, offsetMinutes mod 60]); end; end; end; @@ -252,8 +314,6 @@ var msec: Integer; hasTimezone: Boolean; xmlOffset: Integer; - timeZone: TTimeZoneInformation; - localOffset: Integer; begin Result := 0; @@ -326,7 +386,6 @@ begin if Length(time) > 0 then begin hasTimezone := False; - xmlOffset := 0; if time[1] = XMLTimezoneZulu then begin @@ -343,18 +402,13 @@ begin if time[1] = XMLTimezoneSigns[False] then xmlOffset := -xmlOffset; + + Result := IncMinute(Result, - xmlOffset); end; end; if hasTimezone then - begin - FillChar(timeZone, SizeOf(TTimeZoneInformation), #0); - if GetTimeZoneInformation(timeZone) <> TIME_ZONE_ID_INVALID then - begin - localOffset := -timeZone.Bias; - Result := IncMinute(Result, localOffset - xmlOffset); - end; - end; + Result := ConvertDate(Result, dcToLocal); end; end; end;