{ Double Commander ------------------------------------------------------------------------- Date and time functions. Copyright (C) 2009-2012 Przemysław Nagay (cobines@gmail.com) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA } unit DCDateTimeUtils; {$mode objfpc}{$H+} interface uses Classes, SysUtils, DCBasicTypes {$IF DEFINED(MSWINDOWS)} , Windows {$ELSEIF DEFINED(UNIX)} , unixutil {$ENDIF} ; function FileTimeToDateTime(FileTime : DCBasicTypes.TFileTime) : TDateTime; function DateTimeToFileTime(DateTime : TDateTime) : DCBasicTypes.TFileTime; {en Converts system specific UTC time to local time. } function FileTimeToLocalFileTime(const FileTime: DCBasicTypes.TFileTime; out LocalFileTime: DCBasicTypes.TFileTime): LongBool; {en Converts system specific local time to UTC time. } function LocalFileTimeToFileTime(const LocalFileTime: DCBasicTypes.TFileTime; out FileTime: DCBasicTypes.TFileTime): LongBool; {en Converts Windows UTC file time to Windows local file time. @param(lpFileTime TWinFileTime structure containing the UTC-based file time) @param(lpLocalFileTime TWinFileTime structure to receive the converted local file time) @returns(The function returns @true if successful, @false otherwise) } function WinFileTimeToLocalFileTime(const FileTime: TWinFileTime; out LocalFileTime: TWinFileTime): LongBool; {en Converts Windows local file time to Windows UTC file time. @param(lpLocalFileTime TWinFileTime structure that specifies the local file time) @param(lpFileTime TWinFileTime structure to receive the converted UTC-based file time) @returns(The function returns @true if successful, @false otherwise) } function WinLocalFileTimeToFileTime(const LocalFileTime: TWinFileTime; out FileTime: TWinFileTime): LongBool; {en Converts Windows UTC file time to a file time in TDateTime format. @param(ft TWinFileTime structure containing the UTC-based file time) @returns(File time in TDateTime format) } function WinFileTimeToDateTime(ft : TWinFileTime) : TDateTime; {en Converts a file time in TDateTime format to Windows UTC file time. @param(dt File time in TDateTime format) @returns(Windows UTC-based file time) } function DateTimeToWinFileTime(dt : TDateTime) : TWinFileTime; function DosFileTimeToDateTime(const DosTime: TDosFileTime): TDateTime; function DateTimeToDosFileTime(const DateTime: TDateTime): TDosFileTime; {$IFDEF MSWINDOWS} function WinFileTimeToDateTime(ft : Windows.FILETIME) : TDateTime; inline; overload; function WinToDosTime(const WinTime: Windows.FILETIME; var DosTime: TDosFileTime): LongBool; overload; function DosToWinTime(const DosTime: TDosFileTime; var WinTime: Windows.FILETIME): LongBool; overload; function WinToDosTime(const WinTime: TWinFileTime; var DosTime: TDosFileTime): LongBool; function DosToWinTime(const DosTime: TDosFileTime; var WinTime: TWinFileTime): LongBool; {$ENDIF} function UnixFileTimeToDateTime(UnixTime: TUnixFileTime) : TDateTime; function DateTimeToUnixFileTime(DateTime: TDateTime) : TUnixFileTime; function UnixFileTimeToDosTime(UnixTime: TUnixFileTime): TDosFileTime; function UnixFileTimeToWinTime(UnixTime: TUnixFileTime): TWinFileTime; function GetTimeZoneBias: LongInt; {en Converts a month short name to month number. @param(ShortMonthName Month short name) @param(Default Default month number) @returns(Month number) } function MonthToNumberDef(const ShortMonthName: String; Default: Word): Word; {en Converts a year short record to year long record if need (10 -> 2010). @param(Year Year short record) @returns(Year long record) } function YearShortToLong(Year: Word): Word; function TwelveToTwentyFour(Hour: Word; Modifier: AnsiString): Word; function FileTimeCompare(SourceTime, TargetTime: TDateTime; NtfsShift: Boolean): Integer; type EDateOutOfRange = class(EConvertError) private FDateTime: TDateTime; public constructor Create(ADateTime: TDateTime); property DateTime: TDateTime read FDateTime; end; implementation uses DateUtils; const { Short names of months. } ShortMonthNames: TMonthNameArray = ('Jan','Feb','Mar','Apr','May','Jun', 'Jul','Aug','Sep','Oct','Nov','Dec'); SecsPerHour = SecsPerMin * MinsPerHour; {$IF DEFINED(MSWINDOWS)} var WinTimeZoneBias: LongInt; {$ENDIF} function AdjustUnixFileTime(const FileTime: DCBasicTypes.TFileTime; out AdjustedFileTime: DCBasicTypes.TFileTime; AdjustValue: Int64): Boolean; begin if AdjustValue < 0 then begin if FileTime < DCBasicTypes.TFileTime(-AdjustValue) then begin AdjustedFileTime := 0; Result := False; end else begin AdjustedFileTime := FileTime - DCBasicTypes.TFileTime(-AdjustValue); Result := True; end; end else begin if High(FileTime) - FileTime < DCBasicTypes.TFileTime(AdjustValue) then begin AdjustedFileTime := High(FileTime); Result := False; end else begin AdjustedFileTime := FileTime + DCBasicTypes.TFileTime(AdjustValue); Result := True; end; end; end; function AdjustWinFileTime(const FileTime: TWinFileTime; out AdjustedFileTime: TWinFileTime; AdjustValue: Int64): Boolean; begin if AdjustValue < 0 then begin if FileTime < DCBasicTypes.TWinFileTime(-AdjustValue) then begin AdjustedFileTime := 0; Result := False; end else begin AdjustedFileTime := FileTime - DCBasicTypes.TWinFileTime(-AdjustValue); Result := True; end; end else begin if High(FileTime) - FileTime < DCBasicTypes.TWinFileTime(AdjustValue) then begin AdjustedFileTime := High(FileTime); Result := False; end else begin AdjustedFileTime := FileTime + DCBasicTypes.TWinFileTime(AdjustValue); Result := True; end; end; end; function FileTimeToDateTime(FileTime : DCBasicTypes.TFileTime) : TDateTime; {$IF DEFINED(MSWINDOWS)} begin Result := WinFileTimeToDateTime(FileTime); end; {$ELSEIF DEFINED(UNIX)} var Hrs, Mins, Secs : Word; TodaysSecs : DCBasicTypes.TFileTime; begin FileTimeToLocalFileTime(FileTime, FileTime); TodaysSecs := FileTime mod SecsPerDay; Hrs := Word(TodaysSecs div SecsPerHour); TodaysSecs := TodaysSecs - (Hrs * SecsPerHour); Mins := Word(TodaysSecs div SecsPerMin); Secs := Word(TodaysSecs - (Mins * SecsPerMin)); Result := UnixEpoch + // Epoch start + (FileTime div SecsPerDay) + // Number of days + EncodeTime(Hrs, Mins, Secs, 0); // Time end; {$ELSE} begin Result := 0; end; {$ENDIF} function DateTimeToFileTime(DateTime : TDateTime) : DCBasicTypes.TFileTime; {$IF DEFINED(MSWINDOWS)} begin Result := DateTimeToWinFileTime(DateTime); end; {$ELSEIF DEFINED(UNIX)} var Hrs, Mins, Secs, MSecs : Word; Dt, Tm : TDateTime; BigTime: QWord; begin Dt := Trunc(DateTime); Tm := DateTime - Dt; if Dt < UnixEpoch then raise EDateOutOfRange.Create(DateTime) else {$PUSH}{$Q-} BigTime := Trunc(Dt - UnixEpoch) * SecsPerDay; {$POP} DecodeTime(Tm, Hrs, Mins, Secs, MSecs); {$PUSH}{$Q-} BigTime := BigTime + QWord(Hrs * SecsPerHour) + QWord(Mins * SecsPerMin) + Secs; {$POP} {$IFDEF cpu32} if BigTime > High(DCBasicTypes.TFileTime) then raise EDateOutOfRange.Create(DateTime) else {$ENDIF} LocalFileTimeToFileTime(BigTime, Result); end; {$ELSE} begin Result := 0; end; {$ENDIF} function FileTimeToLocalFileTime(const FileTime: DCBasicTypes.TFileTime; out LocalFileTime: DCBasicTypes.TFileTime): LongBool; {$IFDEF MSWINDOWS} begin Result := Windows.FileTimeToLocalFileTime(@Windows.FILETIME(FileTime), @Windows.FILETIME(LocalFileTime)); end; {$ELSE} begin Result := AdjustUnixFileTime(FileTime, LocalFileTime, Tzseconds); end; {$ENDIF} function LocalFileTimeToFileTime(const LocalFileTime: DCBasicTypes.TFileTime; out FileTime: DCBasicTypes.TFileTime): LongBool; {$IFDEF MSWINDOWS} begin Result := Windows.LocalFileTimeToFileTime(@Windows.FILETIME(LocalFileTime), @Windows.FILETIME(FileTime)); end; {$ELSE} begin Result := AdjustUnixFileTime(LocalFileTime, FileTime, -Tzseconds); end; {$ENDIF} function WinFileTimeToLocalFileTime(const FileTime: TWinFileTime; out LocalFileTime: TWinFileTime): LongBool; {$IFDEF MSWINDOWS} begin Result := Windows.FileTimeToLocalFileTime(@Windows.FILETIME(FileTime), @Windows.FILETIME(LocalFileTime)); end; {$ELSE} begin Result := AdjustWinFileTime(FileTime, LocalFileTime, 10000000 * Int64(TZSeconds)); end; {$ENDIF} function WinLocalFileTimeToFileTime(const LocalFileTime: TWinFileTime; out FileTime: TWinFileTime): LongBool; {$IFDEF MSWINDOWS} begin Result := Windows.LocalFileTimeToFileTime(@Windows.FILETIME(LocalFileTime), @Windows.FILETIME(FileTime)); end; {$ELSE} begin Result := AdjustWinFileTime(LocalFileTime, FileTime, -10000000 * Int64(TZSeconds)); end; {$ENDIF} function WinFileTimeToDateTime(ft : TWinFileTime) : TDateTime; begin WinFileTimeToLocalFileTime(ft,ft); Result := (ft / 864000000000.0) - 109205.0; end; function DateTimeToWinFileTime(dt : TDateTime) : TWinFileTime; begin Result := Round((dt + 109205.0) * 864000000000.0); WinLocalFileTimeToFileTime(Result, Result); end; function DosFileTimeToDateTime(const DosTime: TDosFileTime): TDateTime; var Yr, Mo, Dy : Word; Hr, Mn, S : Word; FileDate, FileTime : Word; begin FileDate := LongRec(DosTime).Hi; FileTime := LongRec(DosTime).Lo; Yr := FileDate shr 9 + 1980; Mo := FileDate shr 5 and 15; if Mo < 1 then Mo := 1; if Mo > 12 then Mo := 12; Dy := FileDate and 31; if Dy < 1 then Dy := 1; if Dy > DaysInAMonth(Yr, Mo) then Dy := DaysInAMonth(Yr, Mo); Hr := FileTime shr 11; if Hr > 23 then Hr := 23; Mn := FileTime shr 5 and 63; if Mn > 59 then Mn := 59; S := FileTime and 31 shl 1; if S > 59 then S := 59; Result := ComposeDateTime(EncodeDate(Yr, Mo, Dy), EncodeTime(Hr, Mn, S, 0)); end; function DateTimeToDosFileTime(const DateTime: TDateTime): TDosFileTime; var Yr, Mo, Dy : Word; Hr, Mn, S, MS: Word; begin DecodeDate(DateTime, Yr, Mo, Dy); if (Yr < 1980) or (Yr > 2107) then // outside DOS file date year range Yr := 1980; DecodeTime(DateTime, Hr, Mn, S, MS); LongRec(Result).Lo := (S shr 1) or (Mn shl 5) or (Hr shl 11); LongRec(Result).Hi := Dy or (Mo shl 5) or (Word(Yr - 1980) shl 9); end; {$IFDEF MSWINDOWS} function WinFileTimeToDateTime(ft : Windows.FILETIME) : TDateTime; begin Result := WinFileTimeToDateTime(TWinFileTime(ft)); end; function WinToDosTime(const WinTime: Windows.FILETIME; var DosTime: TDosFileTime): LongBool; var lft : Windows.TFILETIME; begin Result:= Windows.FileTimeToLocalFileTime(@Windows.FILETIME(WinTime), @lft) and Windows.FileTimeToDosDateTime(@lft, @LongRec(Dostime).Hi, @LongRec(DosTime).Lo); end; function DosToWinTime(const DosTime: TDosFileTime; var WinTime: Windows.FILETIME): LongBool; var lft : Windows.TFILETIME; begin Result := Windows.DosDateTimeToFileTime(LongRec(DosTime).Hi, LongRec(DosTime).Lo, @lft) and Windows.LocalFileTimeToFileTime(@lft, @Windows.FILETIME(WinTime)); end; function WinToDosTime(const WinTime: TWinFileTime; var DosTime: TDosFileTime): LongBool; var lft : Windows.TFILETIME; begin Result:= Windows.FileTimeToLocalFileTime(@Windows.FILETIME(WinTime), @lft) and Windows.FileTimeToDosDateTime(@lft, @LongRec(Dostime).Hi, @LongRec(DosTime).Lo); end; function DosToWinTime(const DosTime: TDosFileTime; var WinTime: TWinFileTime): LongBool; var lft : Windows.TFILETIME; begin Result := Windows.DosDateTimeToFileTime(LongRec(DosTime).Hi, LongRec(DosTime).Lo, @lft) and Windows.LocalFileTimeToFileTime(@lft, @Windows.FILETIME(WinTime)); end; {$ENDIF} function UnixFileTimeToDateTime(UnixTime: TUnixFileTime) : TDateTime; var Hrs, Mins, Secs : Word; TodaysSecs : LongInt; {$IFDEF MSWINDOWS} LocalWinFileTime, WinFileTime: TWinFileTime; {$ENDIF} {$IFDEF UNIX} LocalUnixTime: TUnixFileTime; {$ENDIF} begin {$IFDEF UNIX} if FileTimeToLocalFileTime(UnixTime, LocalUnixTime) then UnixTime := LocalUnixTime; {$ENDIF} TodaysSecs := UnixTime mod SecsPerDay; Hrs := TodaysSecs div SecsPerHour; TodaysSecs := TodaysSecs - (Hrs * SecsPerHour); Mins := TodaysSecs div SecsPerMin; Secs := TodaysSecs - (Mins * SecsPerMin); Result := UnixDateDelta + (UnixTime div SecsPerDay) + EncodeTime(Hrs, Mins, Secs, 0); {$IFDEF MSWINDOWS} // Convert universal to local TDateTime. WinFileTime := DateTimeToWinFileTime(Result); if FileTimeToLocalFileTime(WinFileTime, LocalWinFileTime) then WinFileTime := LocalWinFileTime; Result := WinFileTimeToDateTime(WinFileTime); {$ENDIF} end; function DateTimeToUnixFileTime(DateTime : TDateTime): TUnixFileTime; var Hrs, Mins, Secs, MSecs : Word; Dt, Tm : TDateTime; {$IFDEF MSWINDOWS} LocalWinFileTime, WinFileTime: TWinFileTime; {$ENDIF} {$IFDEF UNIX} UnixTime: TUnixFileTime; {$ENDIF} begin {$IFDEF MSWINDOWS} // Convert local to universal TDateTime. LocalWinFileTime := DateTimeToWinFileTime(DateTime); if LocalFileTimeToFileTime(LocalWinFileTime, WinFileTime) then LocalWinFileTime := WinFileTime; DateTime := WinFileTimeToDateTime(LocalWinFileTime); {$ENDIF} Dt := Trunc(DateTime); Tm := DateTime - Dt; if Dt < UnixDateDelta then Result := 0 else Result := Trunc(Dt - UnixDateDelta) * SecsPerDay; DecodeTime(Tm, Hrs, Mins, Secs, MSecs); Result := Result + (Hrs * SecsPerHour) + (Mins * SecsPerMin) + Secs; {$IFDEF UNIX} if LocalFileTimeToFileTime(Result, UnixTime) then Result := UnixTime; {$ENDIF} end; function UnixFileTimeToDosTime(UnixTime: TUnixFileTime): TDosFileTime; begin Result := DateTimeToDosFileTime(UnixFileTimeToDateTime(UnixTime)); end; function UnixFileTimeToWinTime(UnixTime: TUnixFileTime): TWinFileTime; var WinFileTime: TWinFileTime; begin WinFileTime := $019DB1DED53E8000; // Unix epoch start if not AdjustWinFileTime(WinFileTime, Result, 10000000 * Int64(UnixTime)) then Result := WinFileTime; end; function GetTimeZoneBias: LongInt; begin {$IF DEFINED(MSWINDOWS)} Result := WinTimeZoneBias; {$ELSEIF DEFINED(UNIX)} Result := -Tzseconds div 60; {$ELSE} Result := 0; {$ENDIF} end; function MonthToNumberDef(const ShortMonthName: String; Default: Word): Word; var I: Word; begin Result:= Default; if ShortMonthName = EmptyStr then Exit; for I:= 1 to 12 do if SameText(ShortMonthName, ShortMonthNames[I]) then Exit(I); end; function YearShortToLong(Year: Word): Word; begin Result:= Year; if (Year < 100) then begin if (Year < 80) then Result:= Year + 2000 else Result:= Year + 1900; end; end; function TwelveToTwentyFour(Hour: Word; Modifier: AnsiString): Word; begin if Modifier = EmptyStr then Exit(Hour); case LowerCase(Modifier[1]) of 'a': begin if (Hour = 12) then Result:= 0; end; 'p': begin if (Hour < 12) then Result:= Hour + 12; end; end; end; function FileTimeCompare(SourceTime, TargetTime: TDateTime; NtfsShift: Boolean): Integer; const TimeDiff = 3100 / MSecsPerDay; NtfsDiff = MinsPerHour * SecsPerMin; var FileTimeDiff, NtfsTimeDiff: TDateTime; begin FileTimeDiff:= SourceTime - TargetTime; if NtfsShift then begin NtfsTimeDiff:= FileTimeDiff - NtfsDiff; if (NtfsTimeDiff > -TimeDiff) and (NtfsTimeDiff < TimeDiff) then Exit(0); NtfsTimeDiff:= FileTimeDiff + NtfsDiff; if (NtfsTimeDiff > -TimeDiff) and (NtfsTimeDiff < TimeDiff) then Exit(0); end; if (FileTimeDiff > -TimeDiff) and (FileTimeDiff < TimeDiff) then Result:= 0 else if FileTimeDiff > 0 then Result:= +1 else if FileTimeDiff < 0 then Result:= -1; end; { EDateOutOfRange } constructor EDateOutOfRange.Create(ADateTime: TDateTime); begin inherited Create(EmptyStr); FDateTime := ADateTime; end; {$IF DEFINED(MSWINDOWS)} procedure InitTimeZoneBias; var TZInfo: TTimeZoneInformation; begin case GetTimeZoneInformation(@TZInfo) of TIME_ZONE_ID_UNKNOWN: WinTimeZoneBias := TZInfo.Bias; TIME_ZONE_ID_STANDARD: WinTimeZoneBias := TZInfo.Bias + TZInfo.StandardBias; TIME_ZONE_ID_DAYLIGHT: WinTimeZoneBias := TZInfo.Bias + TZInfo.DaylightBias; else WinTimeZoneBias := 0; end; end; initialization InitTimeZoneBias; {$ENDIF} end.