{ DateUtil unit Copyright (C) 2005-2017 Lagunov Aleksey alexs@yandex.ru and Lazarus team original conception from rx library for Delphi (c) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version with the following modification: As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules,and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. } unit rxdateutil; {$I rx.inc} interface function CurrentYear: Word; function IsLeapYear(AYear: Integer): Boolean; function DaysPerMonth(AYear, AMonth: Integer): Integer; function FirstDayOfPrevMonth: TDateTime; function LastDayOfPrevMonth: TDateTime; function FirstDayOfNextMonth: TDateTime; function ExtractDay(ADate: TDateTime): Word; function ExtractMonth(ADate: TDateTime): Word; function ExtractYear(ADate: TDateTime): Word; function IncDate(ADate: TDateTime; Days, Months, Years: Integer): TDateTime; function IncDay(ADate: TDateTime; Delta: Integer): TDateTime; function IncMonth(ADate: TDateTime; Delta: Integer): TDateTime; function IncYear(ADate: TDateTime; Delta: Integer): TDateTime; function ValidDate(ADate: TDateTime): Boolean; procedure DateDiff(Date1, Date2: TDateTime; var Days, Months, Years: Word); function MonthsBetween(Date1, Date2: TDateTime): Double; function DaysInPeriod(Date1, Date2: TDateTime): Longint; { Count days between Date1 and Date2 + 1, so if Date1 = Date2 result = 1 } function DaysBetween(Date1, Date2: TDateTime): Longint; { The same as previous but if Date2 < Date1 result = 0 } function IncTime(ATime: TDateTime; Hours, Minutes, Seconds, MSecs: Integer): TDateTime; function IncHour(ATime: TDateTime; Delta: Integer): TDateTime; function IncMinute(ATime: TDateTime; Delta: Integer): TDateTime; function IncSecond(ATime: TDateTime; Delta: Integer): TDateTime; function IncMSec(ATime: TDateTime; Delta: Integer): TDateTime; function CutTime(ADate: TDateTime): TDateTime; { Set time to 00:00:00:00 } type TDateOrder = (doMDY, doDMY, doYMD); TDayOfWeekName = (Sun, Mon, Tue, Wed, Thu, Fri, Sat); TDaysOfWeek = set of TDayOfWeekName; { String to date conversions } function GetDateOrder(const DateFormat: string): TDateOrder; function MonthFromName(const S: string; MaxLen: Byte): Byte; function StrToDateDef(const S: string; Default: TDateTime): TDateTime; function StrToDateFmt(const DateFormat, S: string): TDateTime; function StrToDateFmtDef(const DateFormat, S: string; Default: TDateTime): TDateTime; function DefDateFormat(FourDigitYear: Boolean): string; function DefDateMask(BlanksChar: Char; FourDigitYear: Boolean): string; function NvlDate(DateValue, DefaultValue: TDateTime): TDateTime; {$IFDEF WIN32} function FormatLongDate(Value: TDateTime): string; function FormatLongDateTime(Value: TDateTime): string; {$ENDIF} const DefaultDateOrder = doDMY; {$IFDEF USE_FOUR_DIGIT_YEAR} var FourDigitYear: Boolean; {$ELSE} function FourDigitYear: Boolean; {$ENDIF USE_FOUR_DIGIT_YEAR} const CenturyOffset: Byte = 60; NullDate: TDateTime = 0; implementation uses DateUtils, SysUtils, RXStrUtils, rxdconst{, DBConsts }{$IFDEF WIN32}, Windows{$ENDIF}; function NvlDate(DateValue, DefaultValue: TDateTime): TDateTime; begin if DateValue = NullDate then Result := DefaultValue else Result := DateValue; end; function IsLeapYear(AYear: Integer): Boolean; begin Result := (AYear mod 4 = 0) and ((AYear mod 100 <> 0) or (AYear mod 400 = 0)); end; function DaysPerMonth(AYear, AMonth: Integer): Integer; const DaysInMonth: array[1..12] of Integer = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); begin Result := DaysInMonth[AMonth]; if (AMonth = 2) and IsLeapYear(AYear) then Inc(Result); { leap-year Feb is special } end; function FirstDayOfNextMonth: TDateTime; var Year, Month, Day: Word; begin DecodeDate(Date, Year, Month, Day); Day := 1; if Month < 12 then Inc(Month) else begin Inc(Year); Month := 1; end; Result := EncodeDate(Year, Month, Day); end; function FirstDayOfPrevMonth: TDateTime; var Year, Month, Day: Word; begin DecodeDate(Date, Year, Month, Day); Day := 1; if Month > 1 then Dec(Month) else begin Dec(Year); Month := 12; end; Result := EncodeDate(Year, Month, Day); end; function LastDayOfPrevMonth: TDateTime; var D: TDateTime; Year, Month, Day: Word; begin D := FirstDayOfPrevMonth; DecodeDate(D, Year, Month, Day); Day := DaysPerMonth(Year, Month); Result := EncodeDate(Year, Month, Day); end; function ExtractDay(ADate: TDateTime): Word; var M, Y: Word; begin DecodeDate(ADate, Y, M, Result); end; function ExtractMonth(ADate: TDateTime): Word; var D, Y: Word; begin DecodeDate(ADate, Y, Result, D); end; function ExtractYear(ADate: TDateTime): Word; var D, M: Word; begin DecodeDate(ADate, Result, M, D); end; function IncDate(ADate: TDateTime; Days, Months, Years: Integer): TDateTime; var D, M, Y: Word; Day, Month, Year: Longint; begin DecodeDate(ADate, Y, M, D); Year := Y; Month := M; Day := D; Inc(Year, Years); Inc(Year, Months div 12); Inc(Month, Months mod 12); if Month < 1 then begin Inc(Month, 12); Dec(Year); end else if Month > 12 then begin Dec(Month, 12); Inc(Year); end; if Day > DaysPerMonth(Year, Month) then Day := DaysPerMonth(Year, Month); Result := EncodeDate(Year, Month, Day) + Days + Frac(ADate); end; procedure DateDiff(Date1, Date2: TDateTime; var Days, Months, Years: Word); { Corrected by Anatoly A. Sanko (2:450/73) } var DtSwap: TDateTime; Day1, Day2, Month1, Month2, Year1, Year2: Word; begin if Date1 > Date2 then begin DtSwap := Date1; Date1 := Date2; Date2 := DtSwap; end; DecodeDate(Date1, Year1, Month1, Day1); DecodeDate(Date2, Year2, Month2, Day2); Years := Year2 - Year1; Months := 0; Days := 0; if Month2 < Month1 then begin Inc(Months, 12); Dec(Years); end; Inc(Months, Month2 - Month1); if Day2 < Day1 then begin Inc(Days, DaysPerMonth(Year1, Month1)); if Months = 0 then begin Dec(Years); Months := 11; end else Dec(Months); end; Inc(Days, Day2 - Day1); end; function IncDay(ADate: TDateTime; Delta: Integer): TDateTime; begin Result := ADate + Delta; end; function IncMonth(ADate: TDateTime; Delta: Integer): TDateTime; begin Result := IncDate(ADate, 0, Delta, 0); end; function IncYear(ADate: TDateTime; Delta: Integer): TDateTime; begin Result := IncDate(ADate, 0, 0, Delta); end; function MonthsBetween(Date1, Date2: TDateTime): Double; var D, M, Y: Word; begin DateDiff(Date1, Date2, D, M, Y); Result := 12 * Y + M; if (D > 1) and (D < 7) then Result := Result + 0.25 else if (D >= 7) and (D < 15) then Result := Result + 0.5 else if (D >= 15) and (D < 21) then Result := Result + 0.75 else if (D >= 21) then Result := Result + 1; end; function IsValidDate(Y, M, D: Word): Boolean; begin Result := (Y >= 1) and (Y <= 9999) and (M >= 1) and (M <= 12) and (D >= 1) and (D <= DaysPerMonth(Y, M)); end; function ValidDate(ADate: TDateTime): Boolean; var Year, Month, Day: Word; begin try DecodeDate(ADate, Year, Month, Day); Result := IsValidDate(Year, Month, Day); except Result := False; end; end; function DaysInPeriod(Date1, Date2: TDateTime): Longint; begin if ValidDate(Date1) and ValidDate(Date2) then Result := Abs(Trunc(Date2) - Trunc(Date1)) + 1 else Result := 0; end; function DaysBetween(Date1, Date2: TDateTime): Longint; begin Result := Trunc(Date2) - Trunc(Date1) + 1; if Result < 0 then Result := 0; end; function IncTime(ATime: TDateTime; Hours, Minutes, Seconds, MSecs: Integer): TDateTime; begin Result := ATime + (Hours div 24) + (((Hours mod 24) * 3600000 + Minutes * 60000 + Seconds * 1000 + MSecs) / MSecsPerDay); if Result < 0 then Result := Result + 1; end; function IncHour(ATime: TDateTime; Delta: Integer): TDateTime; begin Result := IncTime(ATime, Delta, 0, 0, 0); end; function IncMinute(ATime: TDateTime; Delta: Integer): TDateTime; begin Result := IncTime(ATime, 0, Delta, 0, 0); end; function IncSecond(ATime: TDateTime; Delta: Integer): TDateTime; begin Result := IncTime(ATime, 0, 0, Delta, 0); end; function IncMSec(ATime: TDateTime; Delta: Integer): TDateTime; begin Result := IncTime(ATime, 0, 0, 0, Delta); end; function CutTime(ADate: TDateTime): TDateTime; begin Result := Trunc(ADate); end; function CurrentYear: Word; var SystemTime: TSystemTime; begin GetLocalTime(SystemTime); Result := SystemTime.Year; end; { String to date conversions. Copied from SYSUTILS.PAS unit. } procedure ScanBlanks(const S: string; var Pos: Integer); var I: Integer; begin I := Pos; while (I <= Length(S)) and (S[I] = ' ') do Inc(I); Pos := I; end; function ScanNumber(const S: string; MaxLength: Integer; var Pos: Integer; var Number: Longint): Boolean; var I: Integer; N: Word; begin Result := False; ScanBlanks(S, Pos); I := Pos; N := 0; while (I <= Length(S)) and (Longint(I - Pos) < MaxLength) and (S[I] in ['0'..'9']) and (N < 1000) do begin N := N * 10 + (Ord(S[I]) - Ord('0')); Inc(I); end; if I > Pos then begin Pos := I; Number := N; Result := True; end; end; function ScanChar(const S: string; var Pos: Integer; Ch: Char): Boolean; begin Result := False; ScanBlanks(S, Pos); if (Pos <= Length(S)) and (S[Pos] = Ch) then begin Inc(Pos); Result := True; end; end; {$IFDEF RX_D3} procedure ScanToNumber(const S: string; var Pos: Integer); begin while (Pos <= Length(S)) and not (S[Pos] in ['0'..'9']) do begin if S[Pos] in LeadBytes then Inc(Pos); Inc(Pos); end; end; {$ENDIF} function GetDateOrder(const DateFormat: string): TDateOrder; var I: Integer; begin Result := DefaultDateOrder; I := 1; while I <= Length(DateFormat) do begin case Chr(Ord(DateFormat[I]) and $DF) of {$IFDEF RX_D3} 'E': Result := doYMD; {$ENDIF} 'Y': Result := doYMD; 'M': Result := doMDY; 'D': Result := doDMY; else Inc(I); Continue; end; Exit; end; Result := DefaultDateOrder; { default } end; function ExpandYear(Year: Integer): Integer; var N: Longint; begin Result := Year; if Result < 100 then begin N := CurrentYear - CenturyOffset; Inc(Result, N div 100 * 100); if (CenturyOffset > 0) and (Result < N) then Inc(Result, 100); end; end; function ScanDate(const S, DateFormat: string; var Pos: Integer; var Y, M, D: Integer): Boolean; var DateOrder: TDateOrder; N1, N2, N3: Longint; begin Result := False; Y := 0; M := 0; D := 0; DateOrder := GetDateOrder(DateFormat); if DefaultFormatSettings.ShortDateFormat[1] = 'g' then { skip over prefix text } ScanToNumber(S, Pos); if not (ScanNumber(S, MaxInt, Pos, N1) and ScanChar(S, Pos, DefaultFormatSettings.DateSeparator) and ScanNumber(S, MaxInt, Pos, N2)) then Exit; if ScanChar(S, Pos, DateSeparator) then begin if not ScanNumber(S, MaxInt, Pos, N3) then Exit; case DateOrder of doMDY: begin Y := N3; M := N1; D := N2; end; doDMY: begin Y := N3; M := N2; D := N1; end; doYMD: begin Y := N1; M := N2; D := N3; end; end; Y := ExpandYear(Y); end else begin Y := CurrentYear; if DateOrder = doDMY then begin D := N1; M := N2; end else begin M := N1; D := N2; end; end; ScanChar(S, Pos, DefaultFormatSettings.DateSeparator); ScanBlanks(S, Pos); (* {$IFDEF RX_D3} if SysLocale.FarEast and (System.Pos('ddd', ShortDateFormat) <> 0) then begin { ignore trailing text } if ShortTimeFormat[1] in ['0'..'9'] then { stop at time digit } ScanToNumber(S, Pos) else { stop at time prefix } repeat while (Pos <= Length(S)) and (S[Pos] <> ' ') do Inc(Pos); ScanBlanks(S, Pos); until (Pos > Length(S)) or (AnsiCompareText(TimeAMString, Copy(S, Pos, Length(TimeAMString))) = 0) or (AnsiCompareText(TimePMString, Copy(S, Pos, Length(TimePMString))) = 0); end; {$ENDIF RX_D3} *) Result := IsValidDate(Y, M, D) and (Pos > Length(S)); end; function MonthFromName(const S: string; MaxLen: Byte): Byte; begin if Length(S) > 0 then for Result := 1 to 12 do begin if (Length(DefaultFormatSettings.LongMonthNames[Result]) > 0) and (AnsiCompareText(Copy(S, 1, MaxLen), Copy(DefaultFormatSettings.LongMonthNames[Result], 1, MaxLen)) = 0) then Exit; end; Result := 0; end; procedure ExtractMask(const Format, S: string; Ch: Char; Cnt: Integer; var I: Integer; Blank, Default: Integer); var Tmp: string[20]; J, L: Integer; begin I := Default; Ch := UpCase(Ch); L := Length(Format); if Length(S) < L then L := Length(S) else if Length(S) > L then Exit; J := Pos(MakeStr(Ch, Cnt), AnsiUpperCase(Format)); if J <= 0 then Exit; Tmp := ''; while (UpCase(Format[J]) = Ch) and (J <= L) do begin if S[J] <> ' ' then Tmp := Tmp + S[J]; Inc(J); end; if Tmp = '' then I := Blank else if Cnt > 1 then begin I := MonthFromName(Tmp, Length(Tmp)); if I = 0 then I := -1; end else I := StrToIntDef(Tmp, -1); end; function ScanDateStr(const Format, S: string; var D, M, Y: Integer): Boolean; var Pos: Integer; begin ExtractMask(Format, S, 'm', 3, M, -1, 0); { short month name? } if M = 0 then ExtractMask(Format, S, 'm', 1, M, -1, 0); ExtractMask(Format, S, 'd', 1, D, -1, 1); ExtractMask(Format, S, 'y', 1, Y, -1, CurrentYear); Y := ExpandYear(Y); Result := IsValidDate(Y, M, D); if not Result then begin Pos := 1; Result := ScanDate(S, Format, Pos, Y, M, D); end; end; function InternalStrToDate(const DateFormat, S: string; var Date: TDateTime): Boolean; var D, M, Y: Integer; begin if S = '' then begin Date := NullDate; Result := True; end else begin Result := ScanDateStr(DateFormat, S, D, M, Y); if Result then try Date := EncodeDate(Y, M, D); except Result := False; end; end; end; function StrToDateFmt(const DateFormat, S: string): TDateTime; begin if not InternalStrToDate(DateFormat, S, Result) then raise EConvertError.CreateFmt({$IFDEF RX_D3} SInvalidDate {$ELSE} LoadStr(SInvalidDate) {$ENDIF}, [S]); end; function StrToDateDef(const S: string; Default: TDateTime): TDateTime; begin if not InternalStrToDate(DefaultFormatSettings.ShortDateFormat, S, Result) then Result := Trunc(Default); end; function StrToDateFmtDef(const DateFormat, S: string; Default: TDateTime): TDateTime; begin if not InternalStrToDate(DateFormat, S, Result) then Result := Trunc(Default); end; function DefDateFormat(FourDigitYear: Boolean): string; begin if FourDigitYear then begin case GetDateOrder(DefaultFormatSettings.ShortDateFormat) of doMDY: Result := 'MM/DD/YYYY'; doDMY: Result := 'DD/MM/YYYY'; doYMD: Result := 'YYYY/MM/DD'; end; end else begin case GetDateOrder(DefaultFormatSettings.ShortDateFormat) of doMDY: Result := 'MM/DD/YY'; doDMY: Result := 'DD/MM/YY'; doYMD: Result := 'YY/MM/DD'; end; end; end; function DefDateMask(BlanksChar: Char; FourDigitYear: Boolean): string; begin if FourDigitYear then begin case GetDateOrder(DefaultFormatSettings.ShortDateFormat) of doMDY, doDMY: Result := '!99/99/9999;1;'; doYMD: Result := '!9999/99/99;1;'; end; end else begin case GetDateOrder(DefaultFormatSettings.ShortDateFormat) of doMDY, doDMY: Result := '!99/99/99;1;'; doYMD: Result := '!99/99/99;1;'; end; end; if Result <> '' then Result := Result + BlanksChar; end; {$IFDEF WIN32} function FormatLongDate(Value: TDateTime): string; var Buffer: array[0..1023] of Char; SystemTime: TSystemTime; begin {$IFDEF RX_D3} DateTimeToSystemTime(Value, SystemTime); {$ELSE} with SystemTime do begin DecodeDate(Value, wYear, wMonth, wDay); DecodeTime(Value, wHour, wMinute, wSecond, wMilliseconds); end; {$ENDIF} SetString(Result, Buffer, GetDateFormat(GetThreadLocale, DATE_LONGDATE, @SystemTime, nil, Buffer, SizeOf(Buffer) - 1)); Result := TrimRight(Result); end; function FormatLongDateTime(Value: TDateTime): string; begin if Value <> NullDate then Result := FormatLongDate(Value) + FormatDateTime(' tt', Value) else Result := ''; end; {$ENDIF WIN32} {$IFNDEF USE_FOUR_DIGIT_YEAR} function FourDigitYear: Boolean; begin Result := Pos('YYYY', AnsiUpperCase(DefaultFormatSettings.ShortDateFormat)) > 0; end; {$ENDIF} {$IFDEF USE_FOUR_DIGIT_YEAR} initialization FourDigitYear := Pos('YYYY', AnsiUpperCase(ShortDateFormat)) > 0; {$ENDIF} end.