Closed atelierw closed 3 years ago
Hi Jose,
Thanks for pointing out the Wiki issues. I have replaced the old references to the new ones.
Which version of the TZDB are you using? There is no floating-point math used in the current version for date manipulation. The only conversion happens from TDateTime to Int64 and back.
The line you provided is pure Int64 math.
Can you give me a bit more details (like exact line number) and the stack trace.
P.S. The unit tests are passing on my latest version of Delphi and on FPC. Alex.
I am using the latest version and the project is the simplest possible. Just a form and a button to call the procedure. It is very strange indeed, this is the stack trace.
I tested in Delphi 10.3.3 and there is no problem. However, the Delphi version I use for programming is XE2 and never felt similar issue. I am also curious.
Thanks for screenshot Jose,
There are only 2 functions that deal with actual TDateTime
in the code base:
DateTimeToPreciseTime
,PreciseTimeToDateTime
.Looking at the code in the function, I use a helper function YearOf
that calls PreciseTimeToDateTime
. All of them are inlined for performance, and that might be throwing off the debugger.
I use RAD Studio 10.3 Community Edition, so I run the code on x86 (I don't have x64 support). Since there are differences how FP operations work between x86 and x64 I wonder if that maybe the case? What platform are you running this on?
You mind removing all inline
attributes on the functions in the code and try to step into that function see exactly what line fails? Also what type of FP exception are you receiving?
Thanks, Alex.
I have been investigating and I think the problem is specific to Delphi XE2, but may be you can produce a workaround.
The exception starts here at TZDB: function YearOf(const APreciseTime: TPreciseTime): Word; //inline; begin Result := {$IFDEF DELPHI}System.{$ENDIF}DateUtils.YearOf(PreciseTimeToDateTime(APreciseTime)); end;
when it calls System.DateUtils.YearOf: function YearOf(const AValue: TDateTime): Word; var LMonth, LDay: Word; begin DecodeDate(AValue, Result, LMonth, LDay); end;
which calls: procedure DecodeDate(const DateTime: TDateTime; var Year, Month, Day: Word); var Dummy: Word; begin DecodeDateFully(DateTime, Year, Month, Day, Dummy); end;
which calls: function DecodeDateFully(const DateTime: TDateTime; var Year, Month, Day, DOW: Word): Boolean; const D1 = 365; D4 = D1 4 + 1; D100 = D4 25 - 1; D400 = D100 * 4 + 1; var Y, M, D, I: Word; T: Integer; DayTable: PDayTable; begin T := DateTimeToTimeStamp(DateTime).Date; .... end;
which calls: function DateTimeToTimeStamp(DateTime: TDateTime): TTimeStamp; {$IFDEF PUREPASCAL} var LTemp, LTemp2: Int64; begin LTemp := Round(DateTime * FMSecsPerDay); LTemp2 := (LTemp div IMSecsPerDay); Result.Date := DateDelta + LTemp2; Result.Time := Abs(LTemp) mod IMSecsPerDay; end; {$ELSE !PUREPASCAL} {$IFDEF X86ASM} asm PUSH EBX {$IFDEF PIC} PUSH EAX CALL GetGOT MOV EBX,EAX POP EAX {$ELSE !PIC} XOR EBX,EBX {$ENDIF !PIC} MOV ECX,EAX FLD DateTime FMUL [EBX].FMSecsPerDay SUB ESP,8 FISTP QWORD PTR [ESP] FWAIT POP EAX POP EDX OR EDX,EDX JNS @@1 NEG EDX NEG EAX SBB EDX,0 DIV [EBX].IMSecsPerDay NEG EAX JMP @@2 @@1: DIV [EBX].IMSecsPerDay @@2: ADD EAX,DateDelta MOV [ECX].TTimeStamp.Time,EDX MOV [ECX].TTimeStamp.Date,EAX // #281569 [vk] PUSH EAX CALL ValidateTimeStampDate POP EAX // POP EBX end; {$ENDIF X86ASM} {$ENDIF !PUREPASCAL}
So, the exception is here: // #281569 [vk] procedure ValidateTimeStampDate(const TimeStampDate: Integer); begin if (TimeStampDate <= 0) then raise EInvalidOp.Create(SInvalidOp); end; because TimeStampDate is not valid.
I hope this will help.
This part: // #281569 [vk] PUSH EAX CALL ValidateTimeStampDate POP EAX // has been removed in later Delphi versions, probably for a reason.
You are correct, in XE2 that function uses ValidateTimeStampDate
. In my version it does not.
As a test, can you replace the value of CNullDateTime
constant to 0
and try again?
That works, no exceptions anymore.
I have just another question: Why is Asia/Tokyo showing: 04-Nov-20 8:02:13 AM When it is: 05-Nov-20 2:02:13 AM
That is, they are at GMT+9 now.
Hi Jose:
var
LTimeZone: TBundledTimeZone;
LResult: TDateTime;
begin
LTimeZone := TBundledTimeZone.Create('Asia/Tokyo');
LResult := LTimeZone.ToLocalTime(EncodeDateTime(2020, 11, 04, 17, 0, 0, 000));
CheckEquals(0, CompareDateTime(EncodeDateTime(2020, 11, 05, 2, 0, 0, 000), LResult));
end;
Works as expected. If I flip ToLocalTime
with ToUniversalTime
then I get your result. You'd be converting local time in Tokyo to UTC which is 9 hours behind.
I am still looking at an appropriate fix for the issue you had btw.
Probably, there is a problem with the way we name things. Current time in Japan is UTC+9, we can confirm from here. https://24timezones.com/time-zone/utc+9#gref
Where I live, local time is the same as UTC time, except when we are in DTS and it becomes UTC+1.
You have done a great job and I will try again your TZBD some time in the future. For now, I have solved my problem using the Windows API, and the timezone settings stored in the Registry. I had only 5 cities to handle: Tokyo, Sydney, New York, London and Frankfurt.
Hi Jose, Great that you solved your issue!
I have submitted a tentative fix for your issue to master. If you have time to test it out that would help a lot. I don't have XE2 so it's hard for me to validate.
Thanks, Alex.
Yes, it appears to have fixed it. Thank you.
I am having problems building for Delphi Xe2. Your samples appear to be very dated and even TTimeZone has been replaced with TBundledTimeZone, I guess.
After adapting the sample exercise from your readme.md var LTimeZone: TBundledTimeZone; begin LTimeZone := TBundledTimeZone.GetTimeZone('Africa/Cairo'); WriteLn(LTimeZone.ToUniversalTime(Now));
I receive an invalid floating point exception here. { Extract the last millisecond in the end to mark the end of the period. } LEnd := IncMilliSecond(LEnd, -1);
Any idea what I am doing wrong?