Componentes.Terceros.jvcl/official/3.36/archive/JvDateUtil.pas
2009-02-27 12:23:32 +00:00

764 lines
18 KiB
ObjectPascal

{-----------------------------------------------------------------------------
The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.
The Original Code is: JvDateUtil.PAS, released on 2002-07-04.
The Initial Developers of the Original Code are: Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 1997, 1998 Fedor Koshevnikov, Igor Pavluk and Serge Korolev
Copyright (c) 2001,2002 SGB Software
All Rights Reserved.
Last Modified: 2002-07-04
You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net
Known Issues:
-----------------------------------------------------------------------------}
{$I JVCL.INC}
unit JvDateUtil;
interface
{$IFDEF COMPILER6}
uses
RTLConsts;
{$ELSE
{$IFDEF COMPILER7_UP}
uses
SysConst;
{$ENDIF}
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;
{$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;
{$IFDEF WIN32}
NullDate: TDateTime = {-693594} 0;
{$ELSE}
NullDate: TDateTime = 0;
{$ENDIF}
implementation
uses
{$IFDEF WIN32}
Windows,
{$ENDIF}
SysUtils, Consts,
JvStrUtils;
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.wYear;
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 COMPILER3_UP}
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 COMPILER3_UP}
'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 CurrentMonth: Word;
var
SystemTime: TSystemTime;
begin
GetLocalTime(SystemTime);
Result := SystemTime.wMonth;
end;
{Modified}
function ExpandYear(Year: Integer): Integer;
var
N: Longint;
begin
if Year = -1 then
Result := CurrentYear
else
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;
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);
{$IFDEF COMPILER3_UP}
if ShortDateFormat[1] = 'g' then { skip over prefix text }
ScanToNumber(S, Pos);
{$ENDIF COMPILER3_UP}
if not (ScanNumber(S, MaxInt, Pos, N1) and ScanChar(S, Pos, 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, DateSeparator);
ScanBlanks(S, Pos);
{$IFDEF COMPILER3_UP}
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 COMPILER3_UP}
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(LongMonthNames[Result]) > 0) and
(AnsiCompareText(Copy(S, 1, MaxLen),
Copy(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);
if M = -1 then
M := CurrentMonth;
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
{$IFDEF COMPILER3_UP}
raise EConvertError.CreateFmt(SInvalidDate, [S]);
{$ELSE}
raise EConvertError.CreateFmt(LoadStr(SInvalidDate), [S]);
{$ENDIF}
end;
function StrToDateDef(const S: string; Default: TDateTime): TDateTime;
begin
if not InternalStrToDate(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(ShortDateFormat) of
doMDY:
Result := 'MM/DD/YYYY';
doDMY:
Result := 'DD/MM/YYYY';
doYMD:
Result := 'YYYY/MM/DD';
end;
end
else
begin
case GetDateOrder(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(ShortDateFormat) of
doMDY, doDMY:
Result := '!99/99/9999;1;';
doYMD:
Result := '!9999/99/99;1;';
end;
end
else
begin
case GetDateOrder(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 COMPILER3_UP}
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(ShortDateFormat)) > 0;
end;
{$ENDIF}
{$IFDEF USE_FOUR_DIGIT_YEAR}
initialization
FourDigitYear := Pos('YYYY', AnsiUpperCase(ShortDateFormat)) > 0;
{$ENDIF}
end.