Recent

Author Topic: [SOLVED] Store Date /Time in Binary File  (Read 571 times)

TheLastCayen

  • New Member
  • *
  • Posts: 46
[SOLVED] Store Date /Time in Binary File
« on: December 03, 2019, 09:11:53 am »
Hi,

I am using
 - Lazarus 2.0.6
 - FPC 3.0.4
 - Linux Mint Xfce 19.2

I want to create a data file for my software that contains the current date and time. To create my Binary file, I use a TFileStream. I am pretty limited in my choice of data type and I wonder how I can save the current date/time. I know I can convert the date to a string but I am worried about the result when I use a computer with different regional settings. Is there a better option? Or should I use something else to create my file? 

This is my code

Code: Pascal  [Select]
  1.   TDataFile = Record
  2.     Version: Byte;            
  3.     BackupFolderName: String;
  4.     StartDate : String;        
  5.     StartTime: Word;          
  6.     TotalTime: Word;          
  7.     Completed: Byte;        
  8.   end;  
  9. procedure Tbackup.WriteDataFile(DFPath:String; DFName:String);
  10. var
  11.   fsOut    : TFileStream;
  12.   vDataFile : TDataFile;
  13. begin
  14.   with vDataFile do
  15.     begin
  16.       Version           := 1;
  17.       BackupNumber      := 0;
  18.       BackupFolderName  := DFName;
  19.       StartDate         := DateToStr(date);
  20.       StartTime         := 0;
  21.       TotalTime         := 0;
  22.       Completed         := 0;
  23.       try
  24.         fsOut := TFileStream.Create(DFPath+DFName+'.bck', fmCreate);
  25.         fsOut.WriteByte(Version);
  26.         fsOut.WriteWord(BackupNumber);
  27.         fsOut.WriteAnsiString(BackupFolderName);
  28.         //fsOut.WriteWord(StartDate);
  29.         fsOut.WriteAnsiString(StartDate);
  30.         fsOut.WriteWord(StartTime);
  31.         fsOut.WriteWord(TotalTime);
  32.         fsOut.WriteByte(Completed);
  33.         fsOut.Free;
  34.       finally;
  35.       end;
  36.     end;
  37. end;
  38.  
  39. function Tbackup.ReadDataFile(DFPath:String; DFName:String):TDataFile;
  40. var
  41.   fsOut    : TFileStream;
  42.   vDataFile : TDataFile;
  43. begin
  44.   with vDataFile do
  45.     begin
  46.       try
  47.         fsOut := TFileStream.Create(DFPath+DFName+'.bck', fmOpenRead);
  48.         Version           := fsOut.ReadByte;
  49.         BackupNumber      := fsOut.ReadWord;
  50.         BackupFolderName  := fsOut.ReadAnsiString;
  51.         StartDate         := fsOut.ReadAnsiString;
  52.         StartTime         := fsOut.ReadWord;
  53.         TotalTime         := fsOut.ReadWord;
  54.         Completed         := fsOut.ReadByte;
  55.         fsOut.Free;
  56.       finally;
  57.         Result := vDataFile;
  58.       end;
  59.     end;
  60. end
  61.  
  62.  
« Last Edit: December 03, 2019, 12:37:03 pm by TheLastCayen »

Mike.Cornflake

  • Hero Member
  • *****
  • Posts: 1250
Re: Store Date /Time in Binary File
« Reply #1 on: December 03, 2019, 09:28:37 am »
Instead of using String (for Date) and Word (for Time) use TDateTime for both

https://www.freepascal.org/docs-html/rtl/system/tdatetime.html

TDateTime is a Double.  You're already using Date in DateToStr(Date); , well, Date is also a TDateTime

https://www.freepascal.org/docs-html/rtl/sysutils/datetimeroutines.html
https://www.freepascal.org/docs-html/rtl/sysutils/date.html
https://www.freepascal.org/docs-html/rtl/sysutils/now.html
« Last Edit: December 03, 2019, 09:30:25 am by Mike.Cornflake »
Lazarus Trunk/FPC Trunk on Windows [7, 10]
  Have you tried searching this forum or the wiki?:   http://wiki.lazarus.freepascal.org/Alternative_Main_Page
  BOOKS! (Free and otherwise): http://wiki.lazarus.freepascal.org/Pascal_and_Lazarus_Books_and_Magazines

TheLastCayen

  • New Member
  • *
  • Posts: 46
Re: Store Date /Time in Binary File
« Reply #2 on: December 03, 2019, 09:45:50 am »
Thank you for your reply.

The problem is, I can't save a TDateTime when using TFileStream. If I can, I will be more than happy to learn how to:) I also don't know if I can save a real in a TfileStream :( I am pretty much limited to byte, word, Dword, Qword, string, buffer... I am missing something?

GetMem

  • Hero Member
  • *****
  • Posts: 3519
Re: Store Date /Time in Binary File
« Reply #3 on: December 03, 2019, 09:52:04 am »
@TheLastCayen

If you format the datetime, it won't be dependent on regional settings. Like this:
Code: Pascal  [Select]
  1. var
  2.   DateTime: TDateTime;
  3. begin
  4.   Str := FormatDateTime('YYY.MM.DD hh:mm:ss', DateTime);
  5.   //...
  6.   DateTime := StrToDate(Str, 'YYY.MM.DD hh:mm:ss');
  7. end;

Thaddy

  • Hero Member
  • *****
  • Posts: 9293
Re: Store Date /Time in Binary File
« Reply #4 on: December 03, 2019, 10:04:44 am »
@TheLastCayen

If you format the datetime, it won't be dependent on regional settings. Like this:
Code: Pascal  [Select]
  1. var
  2.   DateTime: TDateTime;
  3. begin
  4.   Str := FormatDateTime('YYY.MM.DD hh:mm:ss', DateTime);
  5.   //...
  6.   DateTime := StrToDate(Str, 'YYY.MM.DD hh:mm:ss');
  7. end;
You can store DateTime as a TDatetime, then you have no issues at all. It is a double.
Storing it in binary format - as a double - actually prevents you from any conversion issues. (this is rather basic)

TDateTime = type double.
also related to equus asinus.

wp

  • Hero Member
  • *****
  • Posts: 6491
Re: Store Date /Time in Binary File
« Reply #5 on: December 03, 2019, 10:25:27 am »
The problem is, I can't save a TDateTime when using TFileStream. If I can, I will be more than happy to learn how to:) I also don't know if I can save a real in a TfileStream
Code: Pascal  [Select]
  1. procedure SaveDateTimeToStream(ADateTime: TDateTime; AStream: TStream);
  2. begin
  3.   AStream.Write(ADateTime, SizeOf(ADateTime));
  4. end;
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

GetMem

  • Hero Member
  • *****
  • Posts: 3519
Re: Store Date /Time in Binary File
« Reply #6 on: December 03, 2019, 10:41:56 am »
@wp
+1  Nice solution :).

TheLastCayen

  • New Member
  • *
  • Posts: 46
Re: Store Date /Time in Binary File
« Reply #7 on: December 03, 2019, 12:36:31 pm »
Thank you everyone for your quick answers.

I will use wp solution:) I don't know if I am doing it right by doing the sizeof on a type but it seems to work fine. I want to reserve the maximum space for this variable so it make it easier to read later on...wp solution will also fix future issue with different data type for my file:)

This is my code now:
Code: Pascal  [Select]
  1.   TDataFile = Record
  2.     Version          : Byte;      
  3.     BackupNumber     : Word;      
  4.     BackupFolderName : String;  
  5.     StartDateTime    : TDateTime;
  6.     EndDateTime      : TDateTime;
  7.     Completed        : Byte;      
  8.   end;
  9.  
  10. procedure Tbackup.WriteDataFile(DFPath:String; DFName:String);
  11. var
  12.   fsOut    : TFileStream;
  13.   vDataFile : TDataFile;
  14. begin
  15.   with vDataFile do
  16.     begin
  17.       Version           := 1;
  18.       BackupNumber      := 0;
  19.       BackupFolderName  := DFName;
  20.       StartDateTime     := Now;
  21.       EndDateTime       := 0;
  22.       Completed         := 0;
  23.       try
  24.         fsOut := TFileStream.Create(DFPath+DFName+'.bck', fmCreate);
  25.         fsOut.WriteByte(Version);
  26.         fsOut.WriteWord(BackupNumber);
  27.         fsOut.WriteAnsiString(BackupFolderName);
  28.         fsOut.Write(StartDateTime,SizeOf(TDateTime));
  29.         fsOut.Write(EndDateTime,SizeOf(TDateTime));
  30.         fsOut.WriteByte(Completed);
  31.         fsOut.Free;
  32.       finally;
  33.       end;
  34.     end;
  35. end;
  36.  
  37. function Tbackup.ReadDataFile(DFPath:String; DFName:String):TDataFile;
  38. var
  39.   fsOut    : TFileStream;
  40.   vDataFile : TDataFile;
  41. begin
  42.   with vDataFile do
  43.     begin
  44.       try
  45.         fsOut := TFileStream.Create(DFPath+DFName+'.bck', fmOpenRead);
  46.         Version           := fsOut.ReadByte;
  47.         BackupNumber      := fsOut.ReadWord;
  48.         BackupFolderName  := fsOut.ReadAnsiString;
  49.         fsOut.Read(StartDateTime, SizeOf(TDateTime));
  50.         fsOut.Read(EndDateTime, SizeOf(TDateTime));
  51.         Completed         := fsOut.ReadByte;
  52.         fsOut.Free;
  53.       finally;
  54.         Result := vDataFile;
  55.       end;
  56.     end;
  57. end;    
  58.  
  59.  


wp

  • Hero Member
  • *****
  • Posts: 6491
Re: [SOLVED] Store Date /Time in Binary File
« Reply #8 on: December 03, 2019, 01:24:29 pm »
Just as an excercise for working with streams: If there were not a "string" variable in the TDataFile record declaration you could simply do a very fast single write:

Code: Pascal  [Select]
  1.  Stream.Write(vDataFile, SizeOf(vDataFile))

The problem is, however, that a "string" is a pointer, and this routine would only write the address of the string data to the stream, not the data themselves. However, when you know that BackupFolderName will never be longer than, say, 60 characters, then you can redeclare the BackupFolderName as a short string, String[60], and this trick will work because it writes the string data directly (of course, this will write unnecessary characters when the string is shorter):

Code: Pascal  [Select]
  1. type
  2.   TDataFile = Record
  3.     Version          : Byte;      
  4.     BackupNumber     : Word;      
  5.     BackupFolderName : String[60];
  6.     StartDateTime    : TDateTime;
  7.     EndDateTime      : TDateTime;
  8.     Completed        : Byte;      
  9.   end;
  10.  
  11. procedure Tbackup.WriteDataFile(DFPath:String; DFName:String);
  12. var
  13.   fsOut    : TFileStream;
  14.   vDataFile : TDataFile;
  15. begin
  16.   with vDataFile do
  17.     begin
  18.       Version           := 1;
  19.       BackupNumber      := 0;
  20.       BackupFolderName  := DFName;
  21.       StartDateTime     := Now;
  22.       EndDateTime       := 0;
  23.       Completed         := 0;
  24.     end;
  25.     fsOut := TFileStream.Create(DFPath+DFName+'.bck', fmCreate);
  26.     try
  27.       fsOut.Write(vData, SizeOf(FDataFile));
  28.     finally
  29.       fs.Out.Free;
  30.     end;
  31. end;
  32.  
  33. function Tbackup.ReadDataFile(DFPath:String; DFName:String):TDataFile;
  34. var
  35.   fsOut: TFileStream;
  36.   n: Integer;
  37. begin
  38.   fsOut := TFileStream.Create(DFPath+DFName+'.bck', fmOpenRead);
  39.   try
  40.     n := fsOut.Read(fsOut, SizeOf(TDataFile));
  41.     if n <> SizeOf(TDataFile) then
  42.       raise Exception.Create('TDataFile record could not read correctly');
  43.   finally
  44.     vsOut.Free;
  45.   end;
  46. end;    

In the reading function you'll notice that the return value of the stream's Read method is checked -- this is the number of bytes actually read. If this value is different from the value expected, the SizeOf() value, then something is wrong (file damaged, somebody fiddled around with a hex editor, etc.).

Another remark: Directly writing the record to the file stream, makes the file very dependent on the version, you cannot add other fields to the record later because reading and writing may run out of sync. If you must provide new record fields you must provide a Version flag (or maybe the Version field of the record is just for this purpose?), read the version info first and then decide which record type actually will be read. Or you select a more flexible, descriptive file format such as ini, or xml.
« Last Edit: December 03, 2019, 01:31:40 pm by wp »
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

TheLastCayen

  • New Member
  • *
  • Posts: 46
Re: [SOLVED] Store Date /Time in Binary File
« Reply #9 on: December 03, 2019, 04:29:20 pm »
Thank you for the extra information wp:) I had no idea String[60] would work in this scenario. Before I found WriteAnsiString, I was using an array[0..60] of char :)

For the version variable, yes, the purpose of this variable is to be backward compatible with the data file if I choose to modify it. I am using a binary because  I do not want the user to mess with the file. I will encrypt the string, add a few validations and add a case to check the version(to be honest I don't think I will need it but who knows... I prefer to be ready)...  This is my plan;)

Really appreciate your help:)

ASerge

  • Hero Member
  • *****
  • Posts: 1423
Re: [SOLVED] Store Date /Time in Binary File
« Reply #10 on: December 03, 2019, 06:03:58 pm »
To avoid possible problems due to different alignment method in different versions of the compiler / OS, it is better to add packed before record.

TheLastCayen

  • New Member
  • *
  • Posts: 46
Re: [SOLVED] Store Date /Time in Binary File
« Reply #11 on: December 04, 2019, 04:12:58 pm »
Thank you ASerge, I added packed