Is it possible to get the FileAge to the millisecond? - delphi

I am trying to get the time stamp of a file when it was last modified to the millisecond. Currently if I use:
FileAge(Filename);
I will get the last modified date/time to a second. For example: 12:00:54. Is it possible to get the last modified date and time with milliseconds?

Use TFile.GetLastWriteTime or TFile.GetLastWriteTimeUtc from the System.IOUtils unit.
If the system supports your desired accuracy then these functions will provide dates to that accuracy. Of course it all depends on the file system in use.

There are two versions of FileAge():
function FileAge(const FileName: string): Integer;
This version is deprecated.
On Windows, it uses GetFileAttributesEx() and/or FindFirstFile() to retrieve the timestamp as a FILETIME (which has 100-nanosecond precision), but then coverts it to a DOS-encoded integer using FileTimeToDosDateTime(), which has seconds precision.
On POSIX, it uses stat(), which returns timestamps as seconds elapsed since the epoch.
function FileAge(const FileName: string; out FileDateTime: TDateTime; FollowLink: Boolean = True): Boolean;
On Windows, it retreives the same timestamp as the deprecated version, but coverts the FILETIME to a SYSTEMTIME and then to a TDateTime using milliseconds precision.
On POSIX, it uses stat(), and thus has seconds precision.

Related

Delphi Firemonkey get UTC time

How does one get the UTC time in Firemonkey?
I tried this code from another Stackoverflow answer but it appears GetSystemTime() is not available in FMX.
function NowUTC: TDateTime;
Var UTC: TSystemTime;
begin
GetSystemTime(UTC);
Result := SystemTimeToDateTime(UTC);
end;
If you add DateUtils to the uses clause, you can use the TTimeZone class, its Local class property, and the ToUniversalTime method:
ShowMessage(DateTimeToStr(TTimeZone.Local.ToUniversalTime(Now)));
TTimeZone.Local.ToUniversalTime(Now) doesn't work during DST change on 30. October 3:00 >> 2:00. Beacause during changing, there is 2:00-3:00 time twice and to get correct UTC, You have to set ForceDaylight in function ToUniversalTime to true/false during transient time.
There is a TDateTime helper in DateUtils unit, which reflects this DST transient time on a UNIX (Android) system too and return the correct UTC time. On Unix are used functions gettimeofday and gmtime_r.
result:=TDateTime.NowUTC;

How do I extract just the time from a datetimepicker component in Delphi?

I have a datetimepicker component in a Delphi form and I would like to just get the Time. When i look at the date in debug mode I see 42544.621701, and I would like just to get 0.621701 without the date value.
You can use the Frac() function:
var
Time: TTime;
...
Time := Frac(DateTimePicker1.DateTime);
Or, you can use the System.DateUtils.TimeOf() function, which is merely an inlined wrapper around Frac() with a more descriptive name:
uses
..., DateUtils;
var
Time: TTime;
...
Time := TimeOf(DateTimePicker1.DateTime);
The question is not actually about a date time picker. The control returns you a date time value. You are looking for a way to extract just the time portion. Do that with the TimeOf function from the System.DateUtils unit.
MyTime := TimeOf(MyDateTime);
EditHoo.Text:=timetostr(DateTimePicker2.Time);

? about Anatomy of a Double in general and in Delphi specifically

I understand that doubles have a sign (1 bit), exponent (11 bits) and fraction (52 bits) (according to Wikipedia anyway).
I have three questions.
1) How do I use these three parts to convert them to a number I can use (both the Integral and fractional parts)?
2) How are the three parts stored in a double within Delphi?
3) Once I know the integral and fractional parts, I'm really using them as a date and time with the date being the integral part being an offset in days and the fractional being an offset, I think, in milliseconds from midnight. So really its two numbers I am trying to derive from the double, days and milliseconds. The question is, is there a way to separate the two into ints? that is, day offset into int (or longint) and milliseconds into another int (or longint) using standard math functions?
I found out earlier that hex representations of dates I have stored were indeed Delphi doubles (see below). But I don't know how the double is represented
----------- answer to an earlier question --------------------------
Effectively these strings looks like the Hex representation for the Delphi TDatetime type which is an alias for the Double type (8 bytes), Where the integral part of the TDateTime value is the number of days that have passed since 12/30/1899 and the fractional part of the TDateTime value is fraction of a 24 hour day that has elapsed.
in Delphi you can parse such values using the HexToBin function like so
{$APPTYPE CONSOLE}
uses
System.Classes,
System.SysUtils;
function Decode(const HexStr: AnsiString) : TDateTime;
begin
Assert(Length(HexStr)=16, 'Lenght must be 16');
HexToBin(PAnsiChar(HexStr), #Result, SizeOf(TDateTime));
end;
begin
try
Writeln(DateTimeToStr(Decode('a297780add3ee440')));
Writeln(DateTimeToStr(Decode('5c6c320bdd3ee440')));
Writeln(DateTimeToStr(Decode('67b176e0dd3ee440')));
Writeln(DateTimeToStr(Decode('38a7e155bc42e440')));
Writeln(DateTimeToStr(Decode('d94ee458bc42e440')));
Writeln(DateTimeToStr(Decode('d22af9134989dc40')));
Writeln(DateTimeToStr(Decode('d4fb7863c542e440')));
Writeln(DateTimeToStr(Decode('e501c962c542e440')));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
ReadLn;
end.
This will return
07-07-2013 21:46:50
07-07-2013 21:46:58
07-07-2013 22:24:27
07-08-2013 21:15:05
07-08-2013 21:15:37
01-01-1980 3:24:15
08-08-2013 4:02:29
08-08-2013 4:02:21
You're misunderstanding what the relevant parts of a TDateTime contain.
The integral part (the part to the left of the decimal point) is the number of days that have elapsed since December 30, 1899, as you state. However, the fractional part (the part to the right of the decimal), is simply the decimal representation of the time that has passed since 00:00:00 0001 (the start of that day).
In other words,
0.25 12/30/1899 06:00:00 (6:00 AM)
0.5 12/30/1899 12:00:00 (noon)
0.75 12/30/1899 18:00:00 (6:00 PM)
The simplest way to work with Delphi's TDateTime and it's various parts is to let the RTL handle it for you. To separate out the individual portions of the time, use DecodeTime. To retrieve the individual parts of a date, use DecodeDate (both from the 'SysUtils' unit:
var
Hr, Min, Sec: Word;
Yr, Mon, Day: Word;
DecodeTime(Now, Hr, Min, Sec);
DecodeDate(Now, Yr, Mon, Day);
To retrieve them all at once, use the DateUtils unit and DecodeDateTime instead:
DecodeDateTime(Now, Yr, Mon, Day, Hr, Min, Sec);
If you really do want just the individual parts (the left and right sides of the decimal point), then as David says you just use Int and Frac
A Delphi double value is a standard IEEE754 double, using the standard representation. All languages do this for various reasons. Not least of which is that the underlying hardware operates of IEEE754 types.
You can pull a double value apart using the TDoubleHelper introduced in XE3. The documentation shows you how: http://docwiki.embarcadero.com/Libraries/en/System.TDoubleHelper. For older Delphi versions you would use TDoubleRec instead.
Now, splitting the value into its significand and exponent will not help you. You want the integer part and the fractional part which are entirely different things. Get them using the standard functions Int() and Frac().
However, when you do this, you still won't have what you need. The fractional part is a floating point number between 0 and 1. That doesn't sound much like a number of milliseconds. So I believe that you do not yet fully understand this representation.
Perhaps what you have is nothing more than a TDateTime! In which case you use the standard functions from the RTL to decode it.
I've just read your earlier question. You are indeed on the wrong track. Read again the answer. Specifically:
Where the integral part of the TDateTime
value is the number of days that have passed since
12/30/1899 and the fractional part of the TDateTime value is
fraction of a 24 hour day that has elapsed.
So, in your VB code, convert the hex string, or binary, whichever you have, into a double. Then get the integer part and the fractional part and you are done..

Avoiding Locale Conflicts when storing TDateTime's in Floats?

I got the TDateTime thing fixed by using Floating vars to store them in a file. However, now I face a new problem: Invalid Floating Point - Most likely because of the Comma Separator.
How can I set the default separator in my program? Or is there any other way around?
You can use a TFormatSettings record to specify the decimal separator when you call StrToFloat and FloatToStr. You have to decide what to use and stick to that. Here is sample code with a .
var
d: TDateTime;
s: string;
fs: TFormatSettings;
begin
d := Now();
fs.DecimalSeparator := '.';
s := FloatToStr(d, fs);
end;
Another option would be to use the XML standard date time format. Delphi has some functions in XSBuiltIns to do the conversion from TDateTime to string and back. You will have a time zone offset in the value so if you move your persisted TDateTime from one time zone to another you may have some unwanted behavior. It depends on the usage of the value.
var
d: TDateTime;
s: string;
begin
d := Now();
s := DateTimeToXMLTime(d);
d := XMLTimeToDateTime(s);
end;
As Mikael suggested, there are a many ways to do this. To re-cap you wish to store a TDateTime to a file in textual format and be able to restore this value successfully irrespective of the locale on which the restoration happens.
Option 1
When storing, call FloatToStr, say, but force a '.' for the decimal separator through the TFormatSettings parameter. On restore, use StrToFloat with the same TFormatSettings.
Option 2
Encode the 8 byte TDateTime value using base 64. This has the downside that it renders the value unreadable.
Option 3
Similar to option 1, but encode the TDateTime by calling DateTimeToStr and explicitly passing a TFormatSettings that does not rely on anything in the locale – so do not rely on the locale's date or time separators, instead force your own. To reverse call StrToDateTime with an identical TFormatSettings record.

Change today's date, advancing one month and setting the systemtime

I would like a code sample for a function that takes a tDateTime and an integer as input and sets the system time using setlocaltime after advancing that tDateTime by (int) months. The time should stay the same.
pseudo code example
SetNewTime(NOW,2);
The issues I'm running into are rather frustrating. I cannot use incmonth or similar with a tDateTime, only a tDate, etc.
Below is a complete command-line program that works for me. Tested in Delphi 5 and 2007. Why do you say IncMonth does not work for TDateTime?
program OneMonth;
{$APPTYPE CONSOLE}
uses
SysUtils,
Windows,
Messages;
procedure SetLocalSystemTime(settotime: TDateTime);
var
SystemTime : TSystemTime;
begin
DateTimeToSystemTime(settotime,SystemTime);
SetLocalTime(SystemTime);
//tell windows that the time changed
PostMessage(HWND_BROADCAST,WM_TIMECHANGE,0,0);
end;
begin
try
SetLocalSystemTime(IncMonth(Now,1));
except on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.
IncMonth should work with a TDateTime:
function IncMonth ( const StartDate : TDateTime {; NumberOfMonths : Integer = 1} ) : TDateTime;
Keep in mind a TDate is really just a TDateTime that by convention your ignore the fraction on.
Based on your pseudocode:
procedure SetNewTime(aDateTime: TDateTime; aMonths: Integer);
var
lSystemTime: TSystemTime;
begin
DateTimeToSystemTime(aDateTime, lSystemTime);
Inc(lSystemTime.wMonth, aMonths);
setSystemTime(lSystemTime);
end;
setSystemTime uses UTC time, so you have to adjust for your time zone. The bias is the number of minutes your machine's timezone differs from UTC. This adjusts the date properly on my system:
procedure SetNewTime(aDateTime: TDateTime; aMonths: Integer);
var
lSystemTime: TSystemTime;
lTimeZone: TTimeZoneInformation;
begin
GetTimeZoneInformation(lTimeZone);
aDateTime := aDateTime + (lTimeZone.Bias / 1440);
DateTimeToSystemTime(aDateTime, lSystemTime);
Inc(lSystemTime.wMonth, aMonths);
setSystemTime(lSystemTime);
end;
There isn't enough information to provide a definitive answer to your question.
Consider what you would want to happen if the day of the current month doesn't exist in your future month. Say, January 31 + 1 month. (7 months of the year have 31 days and the rest have fewer.) You have the same problem if you increment the year and the starting date is February 29 on a leap year. So there can't be a universal IncMonth or IncYear function that will work consistantly on all dates.
For anyone interested, I heartily recommend Julian Bucknall's article on the complexities that are inherent in this type of calculation
on how to calculate the number of months and days between two dates.
The following is the only generic date increment functions possible that do not introduce anomolies into generic date math. But it only accomplishes this by shifting the responsibility back onto the programmer who presumably has the exact requirements of the specific application he/she is programming.
IncDay - Add a or subtract a number of days.
IncWeek - Add or subtract a number of weeks.
But if you must use the built in functions then at least be sure that they do what you want them to do. Have a look at the DateUtils and SysUtils units. Having the source code to these functions is one of the coolest aspects of Delphi. Having said that, here is the complete list of built in functions:
IncDay - Add a or subtract a number of days.
IncWeek - Add or subtract a number of weeks.
IncMonth - Add a or subtract a number of months.
IncYear - Add a or subtract a number of years.
As for the second part of your question, how to set the system date & time using a TDatetime, the following shamelessly stolen code from another post will do the job once you have a TDatetime that has the value you want:
procedure SetSystemDateTime(aDateTime: TDateTime);
var
lSystemTime: TSystemTime;
lTimeZone: TTimeZoneInformation;
begin
GetTimeZoneInformation(lTimeZone);
aDateTime := aDateTime + (lTimeZone.Bias / 1440);
DateTimeToSystemTime(aDateTime, lSystemTime);
setSystemTime(lSystemTime);
end;

Resources