Recent

Author Topic: TDateTime to number  (Read 41575 times)

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: TDateTime to number
« Reply #45 on: October 06, 2016, 11:08:46 pm »
I have the impression you have the impression I ignore this simple solution.  :-[
No, it is not about ignoring the solution. It is the cause of the in your eyes faulty conversion from DateTimeToUnix vs UnixToDateTime combined with the fact that DateTimeToStr simply truncates the result (so that you do not notice the difference).

Also in your solution you can compensate whatever you want inside DT2Number but the fact is that your original DateTime value still contains milliseconds.

So, you end up having two datetime values
1) the one your started out with containing milliseconds
2) the one converted back to datetime, not containing milliseconds (well truncated msecs that is).

Compare these two and they keep mismatching.

It does not matter how DateTimeToUnix converts the DT value. DateTimeToUnix rounds the given DT value while DateTimeToStr simply truncates.

You expect DateTimeToUnix function to do the same truncation, and it (currently) doesn't.

That is why you got the mismatch when printing both values using DateTimeToStr.

Now, having said that, we now know that DateTimeToUnix act differently then its Delphi counterpart. But that is the only reason it needs to be corrected. I personally do not consider that to be a bug but simply how FPC implementation of DateTimeToUnix currently works.

I've now put up almost 10 posts showing the real culprit, i'll stop adding more as i seem to fail to bring the point accross. Please read it again whenever you find yourself in a similar situation of mixing values using different resolutions and when they appear to not match up again when comparing. The answer is here  :)
« Last Edit: October 06, 2016, 11:14:02 pm by molly »

ASerge

  • Hero Member
  • *****
  • Posts: 2475
Re: TDateTime to number
« Reply #46 on: October 07, 2016, 12:29:06 am »
I've written code to hold date and time - including milliseconds to 2 decimal places - from Year 0000 to 9999 - in 8 bytes. It's not 'pretty' and would need a little extra to add leading zeros to numbers below 10 but it works forward and reverse.
Great  :D. That's exactly what I wanted to do. Although I would be worried about coding using some special chars like chr(32), chr(34) and chr(44), because they are used as delimiters in some cases.
Use base64 to forget the delimiters. An example of the output length of 6 characters and restriction on approximately 136 years (for more years need more characters).
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses SysUtils, Classes, DateUtils, Base64;
  6.  
  7. type
  8.   EVeryBigYear = class(Exception);
  9.  
  10. function DWordToBase64(Value: DWord): string;
  11. var
  12.   Encoder: TBase64EncodingStream;
  13. begin
  14.   Encoder := TBase64EncodingStream.Create(TStringStream.Create(''));
  15.   try
  16.     Encoder.SourceOwner := True;
  17.     Encoder.WriteDWord(Value);
  18.     Encoder.Flush;
  19.     Result := Copy(TStringStream(Encoder.Source).DataString, 1, 6); // Trim right '=='
  20.   finally
  21.     Encoder.Free;
  22.   end;
  23. end;
  24.  
  25. function Base64ToDWord(const Value: string): DWord;
  26. var
  27.   Decoder: TBase64DecodingStream;
  28. begin
  29.   Decoder := TBase64DecodingStream.Create(TStringStream.Create(Value));
  30.   try
  31.     Decoder.SourceOwner := True;
  32.     Result := Decoder.ReadDWord;
  33.   finally
  34.     Decoder.Free;
  35.   end;
  36. end;
  37.  
  38. function EncodeDateToStr(D, BaseDate: TDateTime): string;
  39. var
  40.   V: Int64;
  41. begin
  42.   V := SecondsBetween(D, BaseDate);
  43.   if Int64Rec(V).Hi <> 0 then
  44.     raise EVeryBigYear.Create('Very big year');
  45.   Result := DWordToBase64(Int64Rec(V).Lo);
  46. end;
  47.  
  48. function DecodeStrToDateTime(const S: string; BaseDate: TDateTime): TDateTime;
  49. begin
  50.   Result := IncSecond(BaseDate, Base64ToDWord(S));
  51. end;
  52.  
  53. procedure ShowDateEncode(D: TDateTime; BaseDate: TDateTime);
  54. var
  55.   S: string;
  56. begin
  57.   WriteLn('Before=', FormatDateTime('c', D));
  58.   S := EncodeDateToStr(D, BaseDate);
  59.   WriteLn(' Value=', S);
  60.   WriteLn(' After=', FormatDateTime('c', DecodeStrToDateTime(S, BaseDate)));
  61. end;
  62.  
  63. procedure Test;
  64. var
  65.   BaseDate: TDateTime;
  66. begin
  67.   BaseDate := EncodeDate(1970, 1, 1);
  68.   try
  69.     ShowDateEncode(Now, BaseDate);
  70.     ShowDateEncode(EncodeDateTime(2105, 12, 31, 1, 2, 3, 4), BaseDate);
  71.     ShowDateEncode(EncodeDateTime(2106, 12, 31, 1, 2, 3, 4), BaseDate);
  72.   except
  73.     on E: Exception do
  74.       Writeln(E.Message);
  75.   end;
  76.   Readln;
  77. end;
  78.  
  79. begin
  80.   Test;
  81. end.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1572
    • Lebeau Software
Re: TDateTime to number
« Reply #47 on: October 07, 2016, 07:22:47 am »
I've been following this thread with interest since in days gone by I needed to be very frugal with data storage and dates were an area of concern. in the '80s I held dates in 3 bytes (I didn't need to store time) and never had any issue with the 'Millenium Bug'  :D

I did something similar, but using bit stuffing instead.  I have a custom logging solution that stores date/time values using COM's DATE type, which is similar to TDateTime in that it is an 8-byte floating point type.  But for a new version I needed a new storage format without changing the existing storage size, so I extracted the individual date/time components and bit stuffed them, and was able to store not only date/time with 4-digit years down to 3-digit milliseconds, but also UTC offsets as well:

year: 12 bits (-2048 - +2047 or 0-4095)
month: 4 bits (1-12)
day: 5 bits (1-31)
hour: 5 bits (0-23)
min: 6 bits (0-59)
sec: 6 bits (0-59)
msec: 11 bits (0-999)
tz offset (in min): 11 bits (±720) or 12 bits (±1440)

= 60/61 bits
« Last Edit: October 07, 2016, 07:32:34 am by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

J-G

  • Hero Member
  • *****
  • Posts: 966
Re: TDateTime to number
« Reply #48 on: October 07, 2016, 09:35:48 am »
I've written code to hold date and time - including milliseconds to 2 decimal places - from Year 0000 to 9999 - in 8 bytes. It's not 'pretty' and would need a little extra to add leading zeros to numbers below 10 but it works forward and reverse.

Great  :D. That's exactly what I wanted to do. Although I would be worried about coding using some special chars like chr(32), chr(34) and chr(44), because they are used as delimiters in some cases.
Avoiding those (32),(34),(44) is a simple matter of changing the 'offset' to 50 - or anything above 44.  I selected 32 just because it is the first non-control char, and used the global [offset] to allow for this very issue.

Bit-Stuffing would be equally efficient but (I think) a little more complex to code.
 
FPC 3.0.0 - Lazarus 1.6 &
FPC 3.2.2  - Lazarus 2.2.0 
Win 7 Ult 64

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1572
    • Lebeau Software
Re: TDateTime to number
« Reply #49 on: October 11, 2016, 07:59:03 pm »
Bit-Stuffing would be equally efficient but (I think) a little more complex to code.

Not too much, and it can be wrapped in a couple of functions or a helper class to hide any complexities.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

cdbc

  • Hero Member
  • *****
  • Posts: 2606
    • http://www.cdbc.dk
Re: TDateTime to number
« Reply #50 on: October 12, 2016, 08:54:23 am »
Hi
I don't if this could help you, but I rolled my own classes to deal with this problem, attaching source unit....
Hth - Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

Edson

  • Hero Member
  • *****
  • Posts: 1326
Re: TDateTime to number
« Reply #51 on: October 12, 2016, 06:01:45 pm »
I did something similar, but using bit stuffing instead.  I have a custom logging solution that stores date/time values using COM's DATE type, which is similar to TDateTime in that it is an 8-byte floating point type.  But for a new version I needed a new storage format without changing the existing storage size, so I extracted the individual date/time components and bit stuffed them, and was able to store not only date/time with 4-digit years down to 3-digit milliseconds, but also UTC offsets as well:

year: 12 bits (-2048 - +2047 or 0-4095)
month: 4 bits (1-12)
day: 5 bits (1-31)
hour: 5 bits (0-23)
min: 6 bits (0-59)
sec: 6 bits (0-59)
msec: 11 bits (0-999)
tz offset (in min): 11 bits (±720) or 12 bits (±1440)

= 60/61 bits

Your solution is similar to the mine. 
Without miliseconds:

N = days*86400 + hours*3600 + minutes*60 + seconds

With miliseconds:

N =days*86400000 + hours*3600000 + minutes*60000 + seconds*1000 + miliseconds

And this can be stored in 42 bits. Even if you include the TZ, the size is small. This is because this code doesn't waste bits. :D
Lazarus 2.2.6 - FPC 3.2.2 - x86_64-win64 on Windows 10

 

TinyPortal © 2005-2018