Jump to content
Sign in to follow this  
IgorSavkic

Bug in LocalDateTimeToUTC

Recommended Posts

var
  Temp, Wrong, Correct: TDateTime;
begin
  Temp := EncodeDateTime(2020, 2, 1, 18, 0, 0, 0);
  Wrong := LocalDateTimeToUTC(Temp);
  Correct := LocalDateTimeToUTCDateTime(Temp);

  WriteLn(DateTimeToStr(Temp));
  WriteLn(DateTimeToStr(Wrong));
  WriteLn(DateTimeToStr(Correct));
end;

Problem is that SMS uses current time to calculate UTCOffset which is not correct, it must calculate offset from the given time.

Solution is something like:

  var JD := DateTimeToJDate(LocalTime);
  result:=LocalTime+JDateToDateTime(JD)-JDateToLocalDateTime(JD);

Or better yet just make LocalDateTimeToUTC call LocalDateTimeToUTCDateTime from DWS

Share this post


Link to post
Share on other sites

@IgorSavkic Sorry for a late reply. I just tested this and got the following result:

2020-02-01 18:00:00  [line #590]
2020-02-01 14:00:00  [line #590]
2020-02-01 14:00:00  [line #590]

What version of SMS are you using? These results are with the alpha channel.

Share this post


Link to post
Share on other sites

It's 3.9.1.171, I think it's same in all SMS versions, even latest stable (2.2.4543, where functions are called differently but code algo is same).

I'm getting:

2020-02-01 18:00:00  [line #590]
2020-02-01 16:00:00  [line #590]
2020-02-01 17:00:00  [line #590]

We're in different timezone (I'm CET), most likely that's the why you see difference, perhaps you would get wrong time for (which gives me correct one):

Temp := EncodeDateTime(2020, 10, 1, 18, 0, 0, 0); 

 

 

 

Share this post


Link to post
Share on other sites
10 hours ago, IgorSavkic said:

We're in different timezone (I'm CET), most likely that's the why you see difference, perhaps you would get wrong time for (which gives me correct one):

Temp := EncodeDateTime(2020, 10, 1, 18, 0, 0, 0); 

This will create a value of 43862.75 everywhere in the world regardless of time zone. It's the same way Delphi and Lazarus does it.

9 hours ago, Daniel Eiszele said:

Hi @jarto, I agree with @IgorSavkic, the current implementation doesn't take into account the possibility that the date in question is within daylight savings time. To be correct the "computation" date should be the date that has been encoded, not "now" as is the current implementation. 

EncodeTime and EncodeDateTime specifically does not care about TZ.

LocalDateTimeToUTC uses a JS date just to extract the TZ difference, so we know how much the LocalTime needs to be changed:

function LocalDateTimeToUTC(const LocalTime: TDateTime): TDateTime;
begin
  var JD:=new JDate();
  result:=LocalTime+JDateToDateTime(JD)-JDateToLocalDateTime(JD);
end;

When I run tests using Stockholm as time zone, I can get the same results as @IgorSavkic but I believe that LocalDateTimeToUTC is the one that returns the correct result while DWS's LocalDateTimeToUTCDateTime is wrong. That's because the time zone is UTC+2 at the moment due to daylight saving = difference to UTC is 120 minutes.

The reason LocalDateTimeToUTCDateTime returns a different value is because DWS handles TDateTime differently from Delphi, Lazarus and Smart Mobile Studio. It's not specifically "wrong" but "different" as in DWS the value inside TDateTime depends on the TZ where the app is run.

 

Share this post


Link to post
Share on other sites
11 minutes ago, Daniel Eiszele said:

Hi @jarto, are you taking into account the fact that Stockholm isn't in daylight savings time at the date noted above? I am East coast Australia which is in daylight time and if I use the encoded time then everything appears to line up as per @IgorSavkic suggestion.

The date is irrelevant. EncodeDateTime and EncodeTime does always generate the same time fraction no matter where and when it is run.

Ahh, now I understand what you guys are suggesting. The conversion should get the tz based on the date itself.

Share this post


Link to post
Share on other sites

Like this? If the DateTime is less than one, it doesn't contain a date and we'll then use the current time zone.

function DateTimeToLocal(const UTCTime: TDateTime): TDateTime;
begin
  var JD:=new JDate();
  if UTCTime>1 then JD:=DateTimeToJDate(UTCTime);
  result:=UTCTime+JDateToLocalDateTime(JD)-JDateToDateTime(JD);
end;

function LocalDateTimeToUTC(const LocalTime: TDateTime): TDateTime;
begin
  var JD:=new JDate();
  if LocalTime>1 then JD:=DateTimeToJDate(LocalTime);
  result:=LocalTime+JDateToDateTime(JD)-JDateToLocalDateTime(JD);
end;

 

Share this post


Link to post
Share on other sites

Universal Coordinated Time (UTC) does not observe daylight savings time, so to compute it for a timezone that does observe daylight savings it is important to know whether it is in effect or not to be able to remove it. For a timezone that doesn't observe daylight savings at the date noted, then there will be no difference in results from the functions. If we take into account the below function it is doing the time difference based on "todays" date. For me, that is 1 hour different to what it would be for the same comparison using the example date above as I am not currently in daylight time but it was daylight time at 2020/2/1. Igor similarly, is currently in daylight time but wasn't at the time above (hence the difference).

If I swap out the line var JD:= new JDate(); with var JD := JDate.Create(2020,2,1,18,0,0,0); then everything lines up correctly because the additional hour is taken into account. Admittedly that shouldn't be hard coded but hopefully it explains the problem?  And for you, there still shouldn't be a problem as without daylight time there is no difference either way.

function LocalDateTimeToUTC(const LocalTime: TDateTime): TDateTime;
begin
  var JD:=new JDate();
  result:=LocalTime+JDateToDateTime(JD)-JDateToLocalDateTime(JD);
end;

 

Share this post


Link to post
Share on other sites
1 minute ago, Daniel Eiszele said:

Looks close. I'm not behind a computer, but I think you need to compute the absolute values of the tdatetimes and change the test to >0 as TDatetime's can be negative?

In TDateTime the integer part is the number of days since Dec 30th 1899. If the TDateTime contains just a time with no date, then it's a number that is bigger than 0 and smaller than 1. In that case it makes sense to use the current TZ. Maybe like this:

function DateTimeToLocal(const UTCTime: TDateTime): TDateTime;
begin
  var JD:=new JDate();
  if Abs(UTCTime)>=1 then JD:=DateTimeToJDate(UTCTime);
  result:=UTCTime+JDateToLocalDateTime(JD)-JDateToDateTime(JD);
end;

function LocalDateTimeToUTC(const LocalTime: TDateTime): TDateTime;
begin
  var JD:=new JDate();
  if Abs(LocalTime)>=1 then JD:=DateTimeToJDate(LocalTime);
  result:=LocalTime+JDateToDateTime(JD)-JDateToLocalDateTime(JD);
end;

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...