function DateTimeToFileTime(FileTime: TDateTime): TFileTime;
var
LocalFileTime, Ft: TFileTime;
SystemTime: TSystemTime;
begin
Result.dwLowDateTime := 0;
Result.dwHighDateTime := 0;
DateTimeToSystemTime(FileTime, SystemTime);
SystemTimeToFileTime(SystemTime, LocalFileTime);
LocalFileTimeToFileTime(LocalFileTime, Ft);
Result := Ft;
end;
function ExtractShortDate(ATimeIn: TDateTime): string;
// Convert DateTime to short date string
begin
Result := FormatDateTime('mm/dd/yyyy', ATimeIn);
end;
function ExtractTime(ATimeIn: TDateTime): string;
// Convert DateTime to am/pm time string
begin
Result := FormatDateTime('hh:mm AM/PM', ATimeIn);
end;
function GetDateFileModified(AFileName: string): string;
// Return the file modified date as a string in local time
var
SR: TSearchRec;
UTCTime: Windows.TFileTime;
GMTST: Windows.TSystemTime;
LocalST: Windows.TSystemTime;
ModifyDT: TDateTime;
TZ: Windows._TIME_ZONE_INFORMATION;
begin
Result := '';
if FindFirst(AFileName, faAnyFile, SR) = 0 then
begin
UTCTime := SR.FindData.ftLastWriteTime;
if FileTimeToSystemTime(UTCTime, GMTST) then
begin
// Get Timezone Information
if GetTimeZoneInformation(TZ) <> 0 then
if SystemTimeToTzSpecificLocalTime(#TZ, GMTST, LocalST) then
begin
ModifyDT := SystemTimeToDateTime(LocalST);
Result := ExtractShortDate(ModifyDT) + ' ' + ExtractTime(ModifyDT);
end
else
begin
TaskMessageDlg('Unable To Convert Time', 'Unable to convert SystemTime To LocalTime',
mtInformation, [mbOk], 0);
Result := '';
exit;
end;
end
else
begin
TaskMessageDlg('Unable To Convert Time', 'Unable to convert FileTime To SystemTime',
mtInformation, [mbOk], 0);
Result := '';
exit;
end;
end
else
TaskMessageDlg('File Not Found', ExtractFileName(AFileName) + ' does not exist.',
mtInformation, [mbOk], 0);
FindClose(SR);
end;
The original code posted did not return the correct time. The original code was replaced with working code so that others may find this beneficial.
Update: The code provides the correct time now thanks to all that assisted.
The problem is highlighted in the MSDN docs for FileTimeToLocalFileTime:
FileTimeToLocalFileTime uses the current settings for the time zone and daylight saving time. Therefore, if it is daylight saving time, this function will take daylight saving time into account, even if the time you are converting is in standard time. You can use the following sequence of functions as an alternative.
FileTimeToSystemTime / SystemTimeToTzSpecificLocalTime / SystemTimeToFileTime
You need to use the three functions specified whenever you look at a file after a daylight savings change that was created before the change (but this method of course also works when you and the file creation are both on the same side of the daylight savings change).
Related
Was playing with dateutils and did some experimenting.
procedure TForm1.Button1Click(Sender: TObject);
var
fromDate, toDate : TDateTime;
begin
fromDate := cxDateEdit1.Date ;
toDate := cxDateEdit2.Date ;
Label1.Caption := 'Hour difference '+IntToStr(HoursBetween(toDate, fromDate))+' hours';
Label2.Caption := 'Minute difference '+IntToStr(MinutesBetween(toDate, fromDate))+' minutes';
end;
How can I get a time difference result in a label caption like hh/mm (example 01:05) ???
A TDateTime is intended to be used with absolute dates and times. Instead you might consider TTimeSpan from the System.TimeSpan unit.
uses
System.TimeSpan;
....
var
d1, d2: TDateTime;
Span: TTimeSpan;
str: string;
....
d1 := ...;
d2 := ...;
Span := TTimeSpan.Subtract(d2, d1);
str := Format('%.2d:%.2d', [Span.Hours, Span.Minutes]));
This assumes that the span is less than a day. But then the format of your output seems to build in that very assumption.
Whether or not this is really any better than simply subtracting two date time values I am not so sure.
SysUtils.FormatDateTime has many useful TDateTime to string conversions:
Label3.Caption := 'Time difference [hh:mm] '+FormatDateTime('hh:nn',toDate-fromDate);
As an alternative, use the result from MinutesBetween:
var
minutes: Integer;
...
minutes := MinutesBetween(toDate,FromDate);
Label3.Caption :=
'Time difference [hh:mm] '+Format('%.2d:%.2d',[minutes div 60,minutes mod 60]);
Is possible to convert
'Thu Jul 17 17:20:38 2014'
with this function? Tried my best, but no result. This format uses justin.tv API, for twitch.tv i use code below and it works. Thanks for help.
var
t1, t2: Tdate;
dzien: integer;
begin
t1 := StrToDateTime('"2014-07-21T12:49:08Z"');
t2 := TTimeZone.Local.ToUniversalTime(Now);
dzien := trunc(t2 - t1);
if dzien > 0 then
Result := (Format('%d days, %s', [dzien, FormatDateTime('hh:nn:ss',
Frac(t2 - t1))]))
else
Result := (Format('%s', [FormatDateTime('hh:nn:ss', Frac(t2 - t1))]));
end;
It is easy enough to parse the string yourself. Like this:
uses
Types, SysUtils, DateUtils, StrUtils;
function DecodeJustinTvDateTime(const Value: string): TDateTime;
function MonthNumber(const MonthStr: string): Integer;
var
FormatSettings: TFormatSettings;
begin
FormatSettings := TFormatSettings.Create('en-us');
for Result := low(FormatSettings.ShortMonthNames) to high(FormatSettings.ShortMonthNames) do begin
if SameText(MonthStr, FormatSettings.ShortMonthNames[Result]) then begin
exit;
end;
end;
raise EConvertError.Create('Unrecognised month name');
end;
var
items: TStringDynArray;
Day, Month, Year, Time, Hour, Minute, Second: string;
begin
items := SplitString(Value, ' ');
if Length(items)<>5 then begin
raise EConvertError.Create('Unrecognised date time format');
end;
// items[0] is day of the week which we can ignore
Month := items[1];
Day := items[2];
Time := items[3];
Year := items[4];
items := SplitString(Time, ':');
Assert(Length(items)=3);
if Length(items)<>3 then begin
raise EConvertError.Create('Unrecognised time format');
end;
Hour := items[0];
Minute := items[1];
Second := items[2];
Result := EncodeDateTime(
StrToInt(Year),
MonthNumber(Month),
StrToInt(Day),
StrToInt(Hour),
StrToInt(Minute),
StrToInt(Second),
0
);
end;
The error checking here is a little lame and you might care to improve on it.
procedure TForm6.Button1Click(Sender: TObject);
var
t1: TDateTime;
ts:TFormatSettings;
begin
ts:=TFormatSettings.Create;
ts.ShortDateFormat:='yyyy-MM-dd';
ts.DateSeparator:='-';
ts.TimeSeparator:=':';
t1 := StrToDateTime('2014-07-21T12:49:08Z',ts);
end;
t1 contains date and time from your string.
ISO 8601 describes a so called basic date format that does not use the dashes:
20140507 is a valid representation of the more readable 2014-05-07.
Is there a Delphi RTL function that can interpret that basic format and convert it to a TDateTime value?
I tried
function TryIso2Date(const _s: string; out _Date: TDateTime): Boolean;
var
Settings: TFormatSettings;
begin
Settings := GetUserDefaultLocaleSettings;
Settings.DateSeparator := #0;
Settings.ShortDateFormat := 'yyyymmdd';
Result := TryStrToDate(_s, Date, Settings);
end;
TryIso2Date('20140507', dt);
but it did not work because the DateSeparator could not be found in the string.
The only solution I so far came up with (other than writing the parsing code myself) is adding the missing dashes before calling TryStrToDate:
function TryIso2Date(const _s: string; out _Date: TDateTime): Boolean;
var
Settings: TFormatSettings;
s: string;
begin
Settings := GetUserDefaultLocaleSettings;
Settings.DateSeparator := #0;
Settings.ShortDateFormat := 'yyyy-mm-dd';
s := Copy(_s,1,4) + '-' + Copy(_s, 5,2) + '-' + Copy(_s, 7);
Result := TryStrToDate(_s, Date, Settings);
end;
TryIso2Date('20140507', dt);
This works, but it feels rather clumsy.
This is Delphi XE6, so it should have the most recent RTL possible.
You can use Copy to pull out the values as you already do. And then you just need to encode the date:
function TryIso8601BasicToDate(const Str: string; out Date: TDateTime): Boolean;
var
Year, Month, Day: Integer;
begin
Assert(Length(Str)=8);
Result := TryStrToInt(Copy(Str, 1, 4), Year);
if not Result then
exit;
Result := TryStrToInt(Copy(Str, 5, 2), Month);
if not Result then
exit;
Result := TryStrToInt(Copy(Str, 7, 2), Day);
if not Result then
exit;
Result := TryEncodeDate(Year, Month, Day, Date);
end;
I have a problem getting file modified time as julian date. My Delphi 2010 looks like this:
AHandle := FindFirstFile(PChar('C:\*'), FindData);
if (AHandle <> INVALID_HANDLE_VALUE) then
begin
repeat
if (FindData.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY = 0) then
ModifJulianDate := FileTimeToJulianDate(FindData.ftLastWriteTime);
until Not FindNextFile(AHandle, FindData);
end;
The problem is: FileTimeToJulianDate() returned result has one hour offset, ie. if the file's modified time is 10:01, this code returns 9:01!!
How can I fix this?
The functionality that you are looking for, assuming your answer is accurate, is most easily implemented using FileTimeToLocalFileTime.
function UTCFileTimeToLocalDateTime(const UTCFileTime: TFileTime): TDateTime;
var
LocalFileTime: TFileTime;
LocalSystemTime: TSystemTime;
begin
if not FileTimeToLocalFileTime(UTCFileTime, LocalFileTime) then
RaiseLastOSError;
if not FileTimeToSystemTime(LocalFileTime, LocalSystemTime) then
RaiseLastOSError;
Result := SystemTimeToDateTime(LocalSystemTime);
end;
Or if you prefer to use SystemTimeToTzSpecificLocalTime then do so like this:
function UTCFileTimeToLocalDateTime(const UTCFileTime: TFileTime): TDateTime;
var
UTCSystemTime, LocalSystemTime: TSystemTime;
begin
if not FileTimeToSystemTime(UTCFileTime, UTCSystemTime) then
RaiseLastOSError;
if not SystemTimeToTzSpecificLocalTime(nil, UTCSystemTime, LocalSystemTime) then
RaiseLastOSError;
Result := SystemTimeToDateTime(LocalSystemTime);
end;
With the help of LU RD & the code posted here, I managed to get what I wanted, in case someone need something like this, here's my code:
function GetDateFileModified(UTCTime : TFileTime{FindData : TWin32FindData}) : TDateTime;
// Return the file modified date as a string in local time
var
GMTsystemTime : Windows.TSystemTime;
LocalSysTime : Windows.TSystemTime;
TimeZone : Windows._TIME_ZONE_INFORMATION;
begin
Result := 0;
// Get Timezone Information
GetTimeZoneInformation(TimeZone);
// UTCTime := FindData.ftLastWriteTime;
if FileTimeToSystemTime(UTCTime, GMTsystemTime) then
begin
SystemTimeToTzSpecificLocalTime(#TimeZone, GMTsystemTime, LocalSysTime);
Result := SystemTimeToDateTime(LocalSysTime);
end;
end;
I need to calculate the elapsed time (nicely formatted) between now and a file's last modification date/time, ie. something like this, only in my case, the difference can be in days, months or even years.
I tried this:
var
TimeDiff : Double;
begin
TimeDiff := Now - FileAgeEx('C:\my-file.txt');
if (TimeDiff >= 1) then
Caption := FormatDateTime('dd hh:nn:ss', TimeDiff)
else
Caption := FormatDateTime('hh:nn:ss', TimeDiff);
end;
But (1) it doesn't work and (2) I'd like a better formatting.
Ultimately my goal is to have something like this:
Time Diff < 1 day ==> display this: 12:00:01
Time Diff >= 1 day ==> display this: 25 days, 12:00:01
Time Diff >= 1 year ==> display this: 2 years, 3 months, 10 days, 12:00:01
Anyone knows how can I do that?
Thanks!
The main problem would appear to be getting hold of the last modified time of the file. I use the following code:
function LastWriteTime(const FileName: string): TFileTime;
var
AttributeData: TWin32FileAttributeData;
begin
if not GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, #AttributeData) then
RaiseLastOSError;
Result := AttributeData.ftLastWriteTime;
end;
function UTCFileTimeToSystemTime(const FileTime: TFileTime): TSystemTime;
//returns equivalent time in current locality, taking account of daylight saving
var
LocalFileTime: Windows.TFileTime;
begin
Windows.FileTimeToLocalFileTime(FileTime, LocalFileTime);
Windows.FileTimeToSystemTime(LocalFileTime, Result);
end;
function UTCFileTimeToDateTime(const FileTime: TFileTime): TDateTime;
begin
Result := SystemTimeToDateTime(UTCFileTimeToSystemTime(FileTime));
end;
You call LastWriteTime to get the last modified time in file time format. Then call UTCFileTimeToDateTime to convert into TDateTime accounting for the prevailing local time zone of the machine. You can then compare that value with Now.
As regards the formatting, you already appear to know how to do that. You basic approach will work and you just need to flesh out the details.
In the comments you say that
FormatDateTime('dd hh:nn:ss', 2.9);
shows a 1 for the day when you would expect a 2. The problem is that this function formats dates rather than time intervals. The value 2.9 is not treated as an elapsed time, rather it is treated as an absolute date/time, 2.9 days after the Delphi epoch. I would use Trunc and Frac to obtain number of days, and the part of days respectively, and work from there.
Days := Trunc(TimeDiff);
Time := Frac(TimeDiff);
The following code, extracted directly from my codebase, may give you some pointers. Note that its input is in seconds, but it should set you on the right path.
function CorrectPlural(const s: string; Count: Integer): string;
begin
Result := IntToStr(Count) + ' ' + s;
if Count<>1 then begin
Result := Result + 's';
end;
end;
function HumanReadableTime(Time: Double): string;
//Time is in seconds
const
SecondsPerMinute = 60;
SecondsPerHour = 60*SecondsPerMinute;
SecondsPerDay = 24*SecondsPerHour;
SecondsPerWeek = 7*SecondsPerDay;
SecondsPerYear = 365*SecondsPerDay;
var
Years, Weeks, Days, Hours, Minutes, Seconds: Int64;
begin
Try
Years := Trunc(Time/SecondsPerYear);
Time := Time - Years*SecondsPerYear;
Weeks := Trunc(Time/SecondsPerWeek);
Time := Time - Weeks*SecondsPerWeek;
Days := Trunc(Time/SecondsPerDay);
Time := Time - Days*SecondsPerDay;
Hours := Trunc(Time/SecondsPerHour);
Time := Time - Hours*SecondsPerHour;
Minutes := Trunc(Time/SecondsPerMinute);
Time := Time - Minutes*SecondsPerMinute;
Seconds := Trunc(Time);
if Years>5000 then begin
Result := IntToStr(Round(Years/1000))+' millennia';
end else if Years>500 then begin
Result := IntToStr(Round(Years/100))+' centuries';
end else if Years>0 then begin
Result := CorrectPlural('year', Years) + ' ' + CorrectPlural('week', Weeks);
end else if Weeks>0 then begin
Result := CorrectPlural('week', Weeks) + ' ' + CorrectPlural('day', Days);
end else if Days>0 then begin
Result := CorrectPlural('day', Days) + ' ' + CorrectPlural('hour', Hours);
end else if Hours>0 then begin
Result := CorrectPlural('hour', Hours) + ' ' + CorrectPlural('minute', Minutes);
end else if Minutes>0 then begin
Result := CorrectPlural('minute', Minutes);
end else begin
Result := CorrectPlural('second', Seconds);
end;
Except
Result := 'an eternity';
End;
end;