restemplate/indy/Protocols/IdDateTimeStamp.pas

1469 lines
42 KiB
Plaintext
Raw Blame History

{
$Project$
$Workfile$
$Revision$
$DateUTC$
$Id$
This file is part of the Indy (Internet Direct) project, and is offered
under the dual-licensing agreement described on the Indy website.
(http://www.indyproject.org/)
Copyright:
(c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
$Log$
}
{
Rev 1.3 2004.02.03 5:45:04 PM czhower
Name changes
Rev 1.2 1/21/2004 1:57:38 PM JPMugaas
InitComponent
Rev 1.1 10/12/2003 2:01:46 PM BGooijen
Compiles in DotNet
Rev 1.0 11/14/2002 02:16:44 PM JPMugaas
2002-Feb-07 Pete Mee
- Modified interface: GetAsRFC882 is now GetAsRFC822. ;-)
- Fixed GetAsTTimeStamp (was way out).
2001-Nov-10 Pete Mee
- Added SetFromDOSDateTime.
2001-Mar-29 Pete Mee
- Fixed bug in SetFromRFC822. As luck would have it, my PC has changed
to BST from GMT, so I caught the error. Change use of GMTToLocalDateTime
to StrInternetToDateTime.
2001-Mar-27 Pete Mee
- Added GetTimeZoneHour, GetTimeZoneMinutes, GetTimeZoneAsString and
corresponding properties, TimeZoneHour, TimeZoneMinutes and TimeZoneAsString.
- Added SetFromRFC822 and SetFromISO8601.
2001-Mar-26 Pete Mee
- Fixed bug in AddDays. Was adding an extra day in the wrong centuary.
- Fixed bug in AddDays. Was not altering the year with large additions.
2001-Mar-23 Pete Mee
- Fixed bug in SubtractMilliseconds.
- GetBeatOfDay is more accurate (based on milliseconds instead of seconds).
2001-Mar-21 Pete Mee
- Altered Day, Seond and Millisecond properties to use their respective
Set methods.
- Added SetTimeZone, Zero, ZeroTime and ZeroDate.
- Altered SetYear and SetDay to cope with the value 0.
2000-Sep-16 Pete Mee
- SetYear no longer accepts zero but instead simply exits.
2000-Aug-01 Pete Mee
- Fix bugs in AddDays & SubtractDays. Depending on the day of the year, the
calculations could have been incorrect. Now 'rounds off' to the nearest year
before any other calculation.
2000-Jul-28 Pete Mee
- Fix bugs in AddDays & SubtractDays. 3 days in 400 years lost, 1 day in 100
years lost.
2000-May-11 Pete Mee
- Added GetAsRFC822, GetAsISO8601
2000-May-03 Pete Mee
- Added detection of Day, Week and Month (various formats).
2000-May-02 Pete Mee
- Started TIdDateTimeStamp
}
unit IdDateTimeStamp;
{
Development notes:
The Calendar used is the Gregorian Calendar (modern western society). This
Calendar's use started somtime in the 1500s but wasn't adopted by some countries
until the early 1900s. No attempt is made to cope with any other Calendars.
No attempt is made to cope with any Atomic time quantity less than a leap
year (i.e., an exact number of seconds per day and an exact number of days
per year / leap year - no leap seconds, no 1/4 days, etc).
The implementation revolves around the Milliseconds, Seconds, Days and Years.
The heirarchy is as follows:
Milliseconds modify seconds. (0-999 Milliseconds)
Seconds modify days. (0-59 Seconds)
Days modify years. (1-365/366 Days)
Years modify years. (..., -2, -1, 1, ...)
All other time units are translated into necessary component parts. I.e.,
a week is 7 days, and hour is 3600 seconds, a minute is 60 seconds, etc...
The implementation could be easily expanded to represent decades, centuries,
nanoseconds, and beyond in both directions. Milliseconds are included to
provide easy conversion from TTimeStamp and back (and hence TDateTime). The
current component is designed to give good functionality for the majority (if
not all) of Internet component requirements (including Swatch's Internet Time).
It is also not limited to the 2038 bug of many of today's OSs (32-bit signed
number of seconds from 1st Jan 1970 = 19th Jan 2038 03:14:07, or there abouts).
NB: This implementation is factors slower than those of the TDateTime and
TTimeStamp components of standard Delphi. It's main use lies in the conversion
to / from ISO 8601 and RFC 822 formats as well as dates ranging beyond 2037 and
before 1970 (though TTimeStamp is capable here). It's also the only date component
I'm aware of that complies with RFC 2550 "Y10K and Beyond"... one of those RFCs in
the same category as RFC 1149, IP over Avian Carriers. ;-)
Pete Mee
}
{
ToDo: Allow localisation date / time strings generated (i.e., to zone name).
ToDo: Rework SetFromRFC822 as it is (marginally) limited by it's
conversion to TDateTime.
ToDo: Conversion between Time Zones.
}
interface
{$i IdCompilerDefines.inc}
uses
IdGlobal,
IdBaseComponent;
const
// Some basic constants
IdMilliSecondsInSecond = 1000;
IdSecondsInMinute = 60;
IdMinutesInHour = 60;
IdHoursInDay = 24;
IdDaysInWeek = 7;
IdDaysInYear = 365;
IdDaysInLeapYear = 366;
IdYearsInShortLeapYearCycle = 4;
IdDaysInShortLeapYearCycle = IdDaysInLeapYear + (IdDaysInYear * 3);
IdDaysInShortNonLeapYearCycle = IdDaysInYear * IdYearsInShortLeapYearCycle;
IdDaysInFourYears = IdDaysInShortLeapYearCycle;
IdYearsInCentury = 100;
IdDaysInCentury = (25 * IdDaysInFourYears) - 1;
IdDaysInLeapCentury = IdDaysInCentury + 1;
IdYearsInLeapYearCycle = 400;
IdDaysInLeapYearCycle = IdDaysInCentury * 4 + 1;
IdMonthsInYear = 12;
// Beat time is Swatch's "Internet Time" http://www.swatch.com/ {Do not Localize}
IdBeatsInDay = 1000;
// Some compound constants
IdHoursInHalfDay = IdHoursInDay div 2;
IdSecondsInHour = IdSecondsInMinute * IdMinutesInHour;
IdSecondsInDay = IdSecondsInHour * IdHoursInDay;
IdSecondsInHalfDay = IdSecondsInHour * IdHoursInHalfDay;
IdSecondsInWeek = IdDaysInWeek * IdSecondsInDay;
IdSecondsInYear = IdSecondsInDay * IdDaysInYear;
IdSecondsInLeapYear = IdSecondsInDay * IdDaysInLeapYear;
IdMillisecondsInMinute = IdSecondsInMinute * IdMillisecondsInSecond;
IdMillisecondsInHour = IdSecondsInHour * IdMillisecondsInSecond;
IdMillisecondsInDay = IdSecondsInDay * IdMillisecondsInSecond;
IdMillisecondsInWeek = IdSecondsInWeek * IdMillisecondsInSecond;
SShortMonthNameJan = 'Jan';
SShortMonthNameFeb = 'Feb';
SShortMonthNameMar = 'Mar';
SShortMonthNameApr = 'Apr';
SShortMonthNameMay = 'May';
SShortMonthNameJun = 'Jun';
SShortMonthNameJul = 'Jul';
SShortMonthNameAug = 'Aug';
SShortMonthNameSep = 'Sep';
SShortMonthNameOct = 'Oct';
SShortMonthNameNov = 'Nov';
SShortMonthNameDec = 'Dec';
SLongMonthNameJan = 'January';
SLongMonthNameFeb = 'February';
SLongMonthNameMar = 'March';
SLongMonthNameApr = 'April';
SLongMonthNameMay = 'May';
SLongMonthNameJun = 'June';
SLongMonthNameJul = 'July';
SLongMonthNameAug = 'August';
SLongMonthNameSep = 'September';
SLongMonthNameOct = 'October';
SLongMonthNameNov = 'November';
SLongMonthNameDec = 'December';
SShortDayNameSun = 'Sun';
SShortDayNameMon = 'Mon';
SShortDayNameTue = 'Tue';
SShortDayNameWed = 'Wed';
SShortDayNameThu = 'Thu';
SShortDayNameFri = 'Fri';
SShortDayNameSat = 'Sat';
SLongDayNameSun = 'Sunday';
SLongDayNameMon = 'Monday';
SLongDayNameTue = 'Tuesday';
SLongDayNameWed = 'Wednesday';
SLongDayNameThu = 'Thursday';
SLongDayNameFri = 'Friday';
SLongDayNameSat = 'Saturday';
IdDaysInMonth : array[1..IdMonthsInYear] of byte =
(
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31
);
IdMonthNames : array[0..IdMonthsInYear] of string =
( '', {Do not Localize}
SLongMonthNameJan, SLongMonthNameFeb, SLongMonthNameMar,
SLongMonthNameApr, SLongMonthNameMay, SLongMonthNameJun,
SLongMonthNameJul, SLongMonthNameAug, SLongMonthNameSep,
SLongMonthNameOct, SLongMonthNameNov, SLongMonthNameDec );
IdMonthShortNames : array[0..IdMonthsInYear] of string =
( '', // Used for GetMonth {Do not Localize}
SShortMonthNameJan, SShortMonthNameFeb, SShortMonthNameMar,
SShortMonthNameApr, SShortMonthNameMay, SShortMonthNameJun,
SShortMonthNameJul, SShortMonthNameAug, SShortMonthNameSep,
SShortMonthNameOct, SShortMonthNameNov, SShortMonthNameDec );
IdDayNames : array[0..IdDaysInWeek] of string =
( '', SLongDayNameSun, SLongDayNameMon, SLongDayNameTue, {Do not Localize}
SLongDayNameWed, SLongDayNameThu, SLongDayNameFri,
SLongDayNameSat );
IdDayShortNames : array[0..IdDaysInWeek] of string =
( '', SShortDayNameSun, SShortDayNameMon, SShortDayNameTue, {Do not Localize}
SShortDayNameWed, SShortDayNameThu, SShortDayNameFri,
SShortDayNameSat );
// Area Time Zones
TZ_NZDT = 13; // New Zealand Daylight Time
TZ_IDLE = 12; // International Date Line East
TZ_NZST = TZ_IDLE;// New Zealand Standard Time
TZ_NZT = TZ_IDLE; // New Zealand Time
TZ_EADT = 11; // Eastern Australian Daylight Time
TZ_GST = 10; // Guam Standard Time / Russia Zone 9
TZ_JST = 9; // Japan Standard Time / Russia Zone 8
TZ_CCT = 8; // China Coast Time / Russia Zone 7
TZ_WADT = TZ_CCT; // West Australian Daylight Time
TZ_WAST = 7; // West Australian Standard Time / Russia Zone 6
TZ_ZP6 = 6; // Chesapeake Bay / Russia Zone 5
TZ_ZP5 = 5; // Chesapeake Bay / Russia Zone 4
TZ_ZP4 = 4; // Russia Zone 3
TZ_BT = 3; // Baghdad Time / Russia Zone 2
TZ_EET = 2; // Eastern European Time / Russia Zone 1
TZ_MEST = TZ_EET; // Middle European Summer Time
TZ_MESZ = TZ_EET; // Middle European Summer Zone
TZ_SST = TZ_EET; // Swedish Summer Time
TZ_FST = TZ_EET; // French Summer Time
TZ_CET = 1; // Central European Time
TZ_FWT = TZ_CET; // French Winter Time
TZ_MET = TZ_CET; // Middle European Time
TZ_MEWT = TZ_CET; // Middle European Winter Time
TZ_SWT = TZ_CET; // Swedish Winter Time
TZ_GMT = 0; // Greenwich Meanttime
TZ_UT = TZ_GMT; // Universla Time
TZ_UTC = TZ_GMT; // Universal Time Co-ordinated
TZ_WET = TZ_GMT; // Western European Time
TZ_WAT = -1; // West Africa Time
TZ_BST = TZ_WAT; // British Summer Time
TZ_AT = -2; // Azores Time
TZ_ADT = -3; // Atlantic Daylight Time
TZ_AST = -4; // Atlantic Standard Time
TZ_EDT = TZ_AST; // Eastern Daylight Time
TZ_EST = -5; // Eastern Standard Time
TZ_CDT = TZ_EST; // Central Daylight Time
TZ_CST = -6; // Central Standard Time
TZ_MDT = TZ_CST; // Mountain Daylight Time
TZ_MST = -7; // Mountain Standard Time
TZ_PDT = TZ_MST; // Pacific Daylight Time
TZ_PST = -8; // Pacific Standard Time
TZ_YDT = TZ_PST; // Yukon Daylight Time
TZ_YST = -9; // Yukon Standard Time
TZ_HDT = TZ_YST; // Hawaii Daylight Time
TZ_AHST = -10; // Alaska-Hawaii Standard Time
TZ_CAT = TZ_AHST;// Central Alaska Time
TZ_HST = TZ_AHST; // Hawaii Standard Time
TZ_EAST = TZ_AHST;// East Australian Standard Time
TZ_NT = -11; // -None-
TZ_IDLW = -12; // International Date Line West
// Military Time Zones
TZM_A = TZ_WAT;
TZM_Alpha = TZM_A;
TZM_B = TZ_AT;
TZM_Bravo = TZM_B;
TZM_C = TZ_ADT;
TZM_Charlie = TZM_C;
TZM_D = TZ_AST;
TZM_Delta = TZM_D;
TZM_E = TZ_EST;
TZM_Echo = TZM_E;
TZM_F = TZ_CST;
TZM_Foxtrot = TZM_F;
TZM_G = TZ_MST;
TZM_Golf = TZM_G;
TZM_H = TZ_PST;
TZM_Hotel = TZM_H;
TZM_J = TZ_YST;
TZM_Juliet = TZM_J;
TZM_K = TZ_AHST;
TZM_Kilo = TZM_K;
TZM_L = TZ_NT;
TZM_Lima = TZM_L;
TZM_M = TZ_IDLW;
TZM_Mike = TZM_M;
TZM_N = TZ_CET;
TZM_November = TZM_N;
TZM_O = TZ_EET;
TZM_Oscar = TZM_O;
TZM_P = TZ_BT;
TZM_Papa = TZM_P;
TZM_Q = TZ_ZP4;
TZM_Quebec = TZM_Q;
TZM_R = TZ_ZP5;
TZM_Romeo = TZM_R;
TZM_S = TZ_ZP6;
TZM_Sierra = TZM_S;
TZM_T = TZ_WAST;
TZM_Tango = TZM_T;
TZM_U = TZ_CCT;
TZM_Uniform = TZM_U;
TZM_V = TZ_JST;
TZM_Victor = TZM_V;
TZM_W = TZ_GST;
TZM_Whiskey = TZM_W;
TZM_X = TZ_NT;
TZM_XRay = TZM_X;
TZM_Y = TZ_IDLE;
TZM_Yankee = TZM_Y;
TZM_Z = TZ_GMT;
TZM_Zulu = TZM_Z;
type
{ TODO: I'm sure these are stored in a unit elsewhere... need to find out } {Do not Localize}
TDays = (TDaySun, TDayMon, TDayTue, TDayWed, TDayThu, TDayFri, TDaySat);
TMonths = (TMthJan, TMthFeb, TMthMar, TMthApr, TMthMay, TMthJun,
TMthJul, TMthAug, TMthSep, TMthOct, TMthNov, TMthDec);
TIdDateTimeStamp = class(TIdBaseComponent)
protected
FDay : Integer;
FIsLeapYear : Boolean;
FMillisecond : Integer;
FSecond : Integer;
FTimeZone : Integer; // Number of minutes + / - from GMT / UTC
FYear : Integer;
procedure CheckLeapYear;
procedure SetDateFromISO8601(AString : String);
procedure SetTimeFromISO8601(AString : String);
procedure InitComponent; override;
public
procedure AddDays(ANumber : UInt32);
procedure AddHours(ANumber : UInt32);
procedure AddMilliseconds(ANumber : UInt32);
procedure AddMinutes(ANumber : UInt32);
procedure AddMonths(ANumber : UInt32);
procedure AddSeconds(ANumber : UInt32);
procedure AddTDateTime(ADateTime : TDateTime);
procedure AddTIdDateTimeStamp(AIdDateTime : TIdDateTimeStamp);
procedure AddTTimeStamp(ATimeStamp : TIdDateTimeStamp);
procedure AddWeeks(ANumber : UInt32);
procedure AddYears(ANumber : UInt32);
function GetAsISO8601Calendar : String;
function GetAsISO8601Ordinal : String;
function GetAsISO8601Week : String;
function GetAsRFC822 : String;
{TODO : function GetAsRFC977DateTime : String;}
function GetAsTDateTime : TDateTime;
function GetAsTTimeStamp : TIdDateTimeStamp;
function GetAsTimeOfDay : String; // HH:MM:SS
function GetBeatOfDay : Integer;
function GetDaysInYear : Integer;
function GetDayOfMonth : Integer;
function GetDayOfWeek : Integer;
function GetDayOfWeekName : String;
function GetDayOfWeekShortName : String;
function GetHourOf12Day : Integer;
function GetHourOf24Day : Integer;
function GetIsMorning : Boolean;
function GetMinuteOfDay : Integer;
function GetMinuteOfHour : Integer;
function GetMonthOfYear : Integer;
function GetMonthName : String;
function GetMonthShortName : String;
function GetSecondsInYear : Integer;
function GetSecondOfMinute : Integer;
function GetTimeZoneAsString: String;
function GetTimeZoneHour: Integer;
function GetTimeZoneMinutes: Integer;
function GetWeekOfYear : Integer;
procedure SetFromDOSDateTime(ADate, ATime : Word);
procedure SetFromISO8601(AString : String);
procedure SetFromRFC822(AString : String);
procedure SetFromTDateTime(ADateTime : TDateTime);
procedure SetFromTTimeStamp(ATimeStamp : TIdDateTimeStamp);
procedure SetDay(ANumber : Integer);
procedure SetMillisecond(ANumber : Integer);
procedure SetSecond(ANumber : Integer);
procedure SetTimeZone(const Value: Integer);
procedure SetYear(ANumber : Integer);
procedure SubtractDays(ANumber : UInt32);
procedure SubtractHours(ANumber : UInt32);
procedure SubtractMilliseconds(ANumber : UInt32);
procedure SubtractMinutes(ANumber : UInt32);
procedure SubtractMonths(ANumber : UInt32);
procedure SubtractSeconds(ANumber : UInt32);
procedure SubtractTDateTime(ADateTime : TDateTime);
procedure SubtractTIdDateTimeStamp(AIdDateTime : TIdDateTimeStamp);
procedure SubtractTTimeStamp(ATimeStamp : TIdDateTimeStamp);
procedure SubtractWeeks(ANumber : UInt32);
procedure SubtractYears(ANumber : UInt32);
procedure Zero;
procedure ZeroDate;
procedure ZeroTime;
property AsISO8601Calendar : String read GetAsISO8601Calendar;
property AsISO8601Ordinal : String read GetAsISO8601Ordinal;
property AsISO8601Week : String read GetAsISO8601Week;
property AsRFC822 : String read GetAsRFC822;
property AsTDateTime : TDateTime read GetAsTDateTime;
property AsTTimeStamp : TIdDateTimeStamp read GetAsTTimeStamp;
property AsTimeOfDay : String read GetAsTimeOfDay;
property BeatOfDay : Integer read GetBeatOfDay;
property Day : Integer read FDay write SetDay;
property DaysInYear : Integer read GetDaysInYear;
property DayOfMonth : Integer read GetDayOfMonth;
property DayOfWeek : Integer read GetDayOfWeek;
property DayOfWeekName : String read GetDayOfWeekName;
property DayOfWeekShortName : String read GetDayOfWeekShortName;
property HourOf12Day : Integer read GetHourOf12Day;
property HourOf24Day : Integer read GetHourOf24Day;
property IsLeapYear : Boolean read FIsLeapYear;
property IsMorning : Boolean read GetIsMorning;
property Millisecond : Integer read FMillisecond write SetMillisecond;
property MinuteOfDay : Integer read GetMinuteOfDay;
property MinuteOfHour : Integer read GetMinuteOfHour;
property MonthOfYear : Integer read GetMonthOfYear;
property MonthName : String read GetMonthName;
property MonthShortName : String read GetMonthShortName;
property Second : Integer read FSecond write SetSecond;
property SecondsInYear : Integer read GetSecondsInYear;
property SecondOfMinute : Integer read GetSecondOfMinute;
property TimeZone : Integer read FTimeZone write SetTimeZone;
property TimeZoneHour : Integer read GetTimeZoneHour;
property TimeZoneMinutes : Integer read GetTimeZoneMinutes;
property TimeZoneAsString : String read GetTimeZoneAsString;
property Year : Integer read FYear write SetYear;
property WeekOfYear : Integer read GetWeekOfYear;
end;
implementation
uses
IdGlobalProtocols,
IdStrings,
SysUtils;
const
MaxWeekAdd : UInt32 = $FFFFFFFF div IdDaysInWeek;
MaxMinutesAdd : UInt32 = $FFFFFFFF div IdSecondsInMinute;
DIGITS : String = '0123456789'; {Do not Localize}
function LocalDateTimeToTimeStamp(ADateTime: TDateTime): TIdDateTimeStamp;
var
Year,
Month,
Day,
Hour,
Minute,
Second,
MSec: Word;
begin
DecodeDate(ADateTime, Year, Month, Day);
DecodeTime(ADateTime, Hour, Minute, Second, MSec);
Result := TIdDateTimeStamp.Create;
Result.Zero;
Result.AddYears(Year);
Result.AddMonths(Month);
Result.AddDays(Day);
Result.AddHours(Hour);
Result.AddMinutes(Minute);
Result.AddSeconds(Second);
Result.AddMilliseconds(MSec);
end;
procedure ValidateTimeStamp(const ATimeStamp: TIdDateTimeStamp);
begin
IdGlobal.ToDo('ValidateTimeStamp() function in IdDateTimeStamp.pas is not implemented yet'); {do not localize}
// if (ATimeStamp.Time < 0) or (ATimeStamp.Date <= 0) then
// EIdExceptionBase.CreateFmt('''%d.%d'' is not a valid timestamp', [ATimeStamp.Date, ATimeStamp.Time]);
end;
function LocalTimeStampToDateTime(const ATimeStamp: TIdDateTimeStamp): TDateTime;
begin
ValidateTimeStamp(ATimeStamp);
Result := EncodeDate(ATimeStamp.Year, ATimeStamp.MonthOfYear, ATimeStamp.DayOfMonth) +
EncodeTime(ATimeStamp.HourOf24Day, ATimeStamp.MinuteOfHour, ATimeStamp.SecondOfMinute, ATimeStamp.Millisecond);
end;
///////////////////
// TIdDateTimeStamp
///////////////////
procedure TIdDateTimeStamp.InitComponent;
begin
inherited InitComponent;
Zero;
FTimeZone := 0;
end;
procedure TIdDateTimeStamp.AddDays;
var
i : Integer;
begin
// First 'round off' the current day of the year. This is done to prevent {Do not Localize}
// miscalculations in leap years and also as an optimisation for small
// increments.
if (ANumber > UInt32(DaysInYear - FDay)) and (not (FDay = 1)) then begin
ANumber := ANumber - UInt32(DaysInYear - FDay);
FDay := 0;
AddYears(1);
end else begin
// The number of days added is contained within this year.
FDay := FDay + Integer(ANumber);
if FDay > DaysInYear then
begin
ANumber := FDay;
FDay := 0;
AddDays(ANumber);
end;
Exit;
end;
if ANumber >= IdDaysInLeapYearCycle then begin
i := ANumber div IdDaysInLeapYearCycle;
AddYears(i * IdYearsInLeapYearCycle);
ANumber := ANumber - UInt32(i * IdDaysInLeapYearCycle);
end;
if ANumber >= IdDaysInLeapCentury then begin
while ANumber >= IDDaysInLeapCentury do begin
i := FYear div 100;
if i mod 4 = 3 then begin
// Going forward through a 'leap' century {Do not Localize}
AddYears(IdYearsInCentury);
ANumber := ANumber - UInt32(IdDaysInLeapCentury);
end else begin
AddYears(IdYearsInCentury);
ANumber := ANumber - UInt32(IdDaysInCentury);
end;
end;
end;
if ANumber >= IdDaysInShortLeapYearCycle then begin
i := ANumber div IdDaysInShortLeapYearCycle;
AddYears(i * IdYearsInShortLeapYearCycle);
ANumber := ANumber - UInt32(i * IdDaysInShortLeapYearCycle);
end;
i := GetDaysInYear;
while Integer(ANumber) > i do begin
AddYears(1);
Dec(ANumber, i);
i := GetDaysInYear;
end;
if FDay + Integer(ANumber) > i then begin
AddYears(1);
Dec(ANumber, i - FDay);
FDay := ANumber;
end else begin
Inc(FDay, ANumber);
end;
end;
procedure TIdDateTimeStamp.AddHours;
var
i : UInt32;
begin
i := ANumber div IdHoursInDay;
AddDays(i);
Dec(ANumber, i * IdHoursInDay);
AddSeconds(ANumber * IdSecondsInHour);
end;
procedure TIdDateTimeStamp.AddMilliseconds;
var
i : UInt32;
begin
i := ANumber div IdMillisecondsInDay;
if i > 0 then begin
AddDays(i);
Dec(ANumber, i * IdMillisecondsInDay);
end;
i := ANumber div IdMillisecondsInSecond;
if i > 0 then begin
AddSeconds(i);
Dec(ANumber, i * IdMillisecondsInSecond);
end;
Inc(FMillisecond, ANumber);
while FMillisecond > IdMillisecondsInSecond do begin
// Should only happen once...
AddSeconds(1);
Dec(FMillisecond, IdMillisecondsInSecond);
end;
end;
procedure TIdDateTimeStamp.AddMinutes;
begin
// Convert down to seconds
while ANumber > MaxMinutesAdd do begin
AddSeconds(MaxMinutesAdd);
Dec(ANumber, MaxMinutesAdd);
end;
AddSeconds(ANumber * IdSecondsInMinute);
end;
procedure TIdDateTimeStamp.AddMonths;
var
i : Integer;
begin
i := ANumber div IdMonthsInYear;
AddYears(i);
Dec(ANumber, i * IdMonthsInYear);
i := MonthOfYear;
while ANumber > 0 do begin
if i = 12 then begin
i := 1;
end;
if (i = 2) and (IsLeapYear) then begin
AddDays(IdDaysInMonth[i] + 1);
end else begin
AddDays(IdDaysInMonth[i]);
end;
Dec(ANumber);
Inc(i);
end;
end;
procedure TIdDateTimeStamp.AddSeconds;
var
i : UInt32;
begin
i := ANumber Div IdSecondsInDay;
if i > 0 then begin
AddDays(i);
ANumber := ANumber - (i * IdSecondsInDay);
end;
Inc(FSecond, ANumber);
while FSecond > IdSecondsInDay do begin
// Should only ever happen once...
AddDays(1);
Dec(FSecond, IdSecondsInDay);
end;
end;
procedure TIdDateTimeStamp.AddTDateTime;
begin
// todo:
// AddTTimeStamp(DateTimeToTimeStamp(ADateTime));
end;
procedure TIdDateTimeStamp.AddTIdDateTimeStamp;
begin
{ TODO : Check for accuracy }
AddYears(AIdDateTime.Year);
AddDays(AIdDateTime.Day);
AddSeconds(AIdDateTime.Second);
AddMilliseconds(AIdDateTime.Millisecond);
end;
procedure TIdDateTimeStamp.AddTTimeStamp;
begin
AddTIdDateTimeStamp(ATimeStamp);
end;
procedure TIdDateTimeStamp.AddWeeks;
begin
// Cannot add years as there are not exactly 52 weeks in the year and there
// is no exact match between weeks and the 400 year leap cycle
// Convert down to days...
while ANumber > MaxWeekAdd do begin
AddDays(MaxWeekAdd);
Dec(ANumber, MaxWeekAdd);
end;
AddDays(ANumber * IdDaysInWeek);
end;
procedure TIdDateTimeStamp.AddYears;
begin
{TODO: Capture overflow because adding UInt32 to Integer }
if (FYear <= -1) and (Integer(ANumber) >= -FYear) then begin
Inc(ANumber);
end;
Inc(FYear, ANumber);
CheckLeapYear;
end;
procedure TIdDateTimeStamp.CheckLeapYear;
begin
// Nested if done to prevent unnecessary calcs on slower machines
if FYear mod 4 = 0 then begin
if FYear mod 100 = 0 then begin
if FYear mod 400 = 0 then begin
FIsLeapYear := True;
end else begin
FIsLeapYear := False;
end;
end else begin
FIsLeapYear := True;
end;
end else begin
FIsLeapYear := False;
end;
{TODO : If (FIsLeapYear = false) and (FDay = IdDaysInLeapYear) then begin
and, do what?
}
end;
function TIdDateTimeStamp.GetAsISO8601Calendar : String;
begin
Result := IntToStr(FYear) + '-' {Do not Localize}
+ IntToStr(MonthOfYear) + '-' {Do not Localize}
+ IntToStr(DayOfMonth) + 'T' {Do not Localize}
+ AsTimeOfDay;
end;
function TIdDateTimeStamp.GetAsISO8601Ordinal : String;
begin
Result := IntToStr(FYear) + '-' {Do not Localize}
+ IntToStr(FDay) + 'T' {Do not Localize}
+ AsTimeOfDay;
end;
function TIdDateTimeStamp.GetAsISO8601Week : String;
begin
Result := IntToStr(FYear) + '-W' {Do not Localize}
+ IntToStr(WeekOfYear) + '-' {Do not Localize}
+ IntToStr(DayOfWeek) + 'T' {Do not Localize}
+ AsTimeOfDay;
end;
function TIdDateTimeStamp.GetAsRFC822 : String;
begin
Result := IdDayShortNames[DayOfWeek] + ', ' {Do not Localize}
+ IntToStr(DayOfMonth) + ' ' {Do not Localize}
+ IdMonthShortNames[MonthOfYear] + ' ' {Do not Localize}
+ IntToStr(Year) + ' ' {Do not Localize}
+ AsTimeOfDay + ' ' {Do not Localize}
+ TimeZoneAsString;
end;
function TIdDateTimeStamp.GetAsTDateTime : TDateTime;
begin
Result := LocalTimeStampToDateTime(GetAsTTimeStamp);
end;
function TIdDateTimeStamp.GetAsTTimeStamp : TIdDateTimeStamp;
begin
Result := Self;
end;
function TIdDateTimeStamp.GetAsTimeOfDay : String;
begin
Result := IndyFormat('%.2d:%.2d:%.2d', {Do not localize}
[HourOf24Day, MinuteOfHour, SecondOfMinute]);
end;
function TIdDateTimeStamp.GetBeatOfDay : Integer;
var
i64 : Int64;
DTS : TIdDateTimeStamp;
begin
// Check
if FTimeZone <> TZ_MET then
begin
// Rather than messing about with this instance, create
// a new one.
DTS := TIdDateTimeStamp.Create;
try
DTS.SetYear(FYear);
DTS.SetDay(FDay);
DTS.SetSecond(FSecond);
DTS.SetMillisecond(FMillisecond);
DTS.SetTimeZone(TZ_MET);
DTS.AddMinutes( (TZ_MET * IdMinutesInHour) - FTimeZone);
Result := DTS.GetBeatOfDay;
finally
DTS.Free;
end;
end else
begin
i64 := (FSecond * IdMillisecondsInSecond) + FMillisecond;
i64 := i64 * IdBeatsInDay;
i64 := i64 div IdMillisecondsInDay;
Result := Integer(i64);
end;
end;
function TIdDateTimeStamp.GetDaysInYear : Integer;
begin
if IsLeapYear then begin
Result := IdDaysInLeapYear;
end else begin
Result := IdDaysInYear;
end;
end;
function TIdDateTimeStamp.GetDayOfMonth : Integer;
var
count, mnth, days : Integer;
begin
mnth := MonthOfYear;
if IsLeapYear and (mnth > 2) then begin
days := 1;
end else begin
days := 0;
end;
for count := 1 to mnth - 1 do begin
Inc(days, IdDaysInMonth[count]);
end;
days := Day - days;
if days < 0 then begin
Result := 0;
end else begin
Result := days;
end;
end;
function TIdDateTimeStamp.GetDayOfWeek : Integer;
var
a, y, m, d, mnth : Integer;
begin
// Thanks to the "FAQ About Calendars" by Claus T<>ndering for this algorithm
// http://www.tondering.dk/claus/calendar.html
mnth := MonthOfYear;
a := (14 - mnth) div 12;
y := Year - a;
m := mnth + (12 * a) - 2;
d := DayOfMonth + y + (y div 4) - (y div 100) + (y div 400) + ((31 * m) div 12);
d := d mod 7;
Result := d + 1;
end;
function TIdDateTimeStamp.GetDayOfWeekName : String;
begin
result := IdDayNames[GetDayOfWeek];
end;
function TIdDateTimeStamp.GetDayOfWeekShortName : String;
begin
result := IdDayShortNames[GetDayOfWeek];
end;
function TIdDateTimeStamp.GetHourOf12Day : Integer;
var
hr : Integer;
begin
hr := GetHourOf24Day;
if hr > IdHoursInHalfDay then begin
Dec(hr, IdHoursInHalfDay);
end;
Result := hr;
end;
function TIdDateTimeStamp.GetHourOf24Day : Integer;
begin
Result := Second div IdSecondsInHour;
end;
function TIdDateTimeStamp.GetIsMorning : Boolean;
begin
Result := Second <= (IdSecondsInHalfDay + 1);
end;
function TIdDateTimeStamp.GetMinuteOfDay : Integer;
begin
Result := Second div IdSecondsInMinute;
end;
function TIdDateTimeStamp.GetMinuteOfHour : Integer;
begin
Result := GetMinuteOfDay - (IdMinutesInHour * GetHourOf24Day);
end;
function TIdDateTimeStamp.GetMonthOfYear : Integer;
var
AddOne, Count : Byte;
Today : Integer;
begin
Result := 0;
if IsLeapYear then begin
AddOne := 1;
end else begin
AddOne := 0;
end;
Today := Day;
Count := 1;
while Count <> 13 do begin
if Count = 2 then begin
if Today > IdDaysInMonth[Count] + AddOne then begin
Dec(Today, IdDaysInMonth[Count] + AddOne);
end else begin
Result := Count;
Break;
end;
end else begin
if Today > IdDaysInMonth[Count] then begin
Dec(Today, IdDaysInMonth[Count]);
end else begin
Result := Count;
Break;
end;
end;
Inc(Count);
end;
end;
function TIdDateTimeStamp.GetMonthName : String;
begin
Result := IdMonthNames[MonthOfYear];
end;
function TIdDateTimeStamp.GetMonthShortName : String;
begin
Result := IdMonthShortNames[MonthOfYear];
end;
function TIdDateTimeStamp.GetSecondsInYear : Integer;
begin
if IsLeapYear then begin
Result := IdSecondsInLeapYear;
end else begin
Result := IdSecondsInYear;
end;
end;
function TIdDateTimeStamp.GetSecondOfMinute : Integer;
begin
Result := Second - (GetMinuteOfDay * IdSecondsInMinute);
end;
function TIdDateTimeStamp.GetTimeZoneAsString: String;
var
i : Integer;
begin
i := GetTimeZoneHour;
if i < 0 then begin
if i < -9 then begin
Result := IntToStr(i);
end else begin
Result := '-0' + IntToStr(Abs(i)); {Do not Localize}
end;
end
else if i <= 9 then begin
Result := '+0' + IntToStr(i); {Do not Localize}
end else
begin
Result := '+' + IntToStr(i); {Do not Localize}
end;
i := GetTimeZoneMinutes;
if i <= 9 then begin
Result := Result + '0'; {Do not Localize}
end;
Result := Result + IntToStr(i);
end;
function TIdDateTimeStamp.GetTimeZoneHour: Integer;
begin
Result := FTimeZone div 60;
end;
function TIdDateTimeStamp.GetTimeZoneMinutes: Integer;
begin
Result := Abs(FTimeZone) mod 60;
end;
function TIdDateTimeStamp.GetWeekOfYear : Integer;
var
w : Integer;
DT : TIdDateTimeStamp;
begin
DT := TIdDateTimeStamp.Create;
try
DT.SetYear(Year);
w := DT.DayOfWeek; // Get the first day of this year & hence number of
// days of the first week that are in the previous year
w := w + Day - 2; // Get complete weeks
w := w div 7;
Result := w + 1;
finally
DT.Free;
end;
end;
procedure TIdDateTimeStamp.SetFromDOSDateTime(ADate, ATime: Word);
begin
Zero;
SetYear(1980);
AddYears(ADate shr 9);
AddMonths(((ADate and $1E0) shr 5) - 1);
AddDays((ADate and $1F) - 1);
AddHours(ATime shr 11);
AddMinutes((ATime and $7E0) shr 5);
AddSeconds((ATime and $1F) - 1);
end;
procedure TIdDateTimeStamp.SetDateFromISO8601(AString: String);
var
i, week : Integer;
s : String;
begin
// AString should be in one of three formats:
// Calender - YYYY-MM-DD
// Ordinal - YYYY-XXX where XXX is the day of the year
// Week - YYYY-WXX-D where W is a literal and XX is the week of the year.
i := IndyPos('-', AString); {Do not Localize}
if i > 0 then
begin
s := Trim(Copy(AString, 1, i - 1));
AString := Trim(Copy(AString, i + 1, MaxInt));
i := FindFirstNotOf('0123456789', s); {Do not Localize}
if i = 0 then
begin
SetYear(IndyStrToInt(s));
if Length(AString) > 0 then
begin
i := IndyPos('-', AString); {Do not Localize}
if TextStartsWith(AString, 'W') then {Do not Localize}
begin
// Week format
s := Trim(Copy(AString, 2, i - 2));
AString := Trim(Copy(AString, i + 1, MaxInt));
week := -1;
i := -1;
if (Length(AString) > 0) and (FindFirstNotOf(DIGITS, AString) = 0) then
begin
i := IndyStrToInt(AString);
end;
if (Length(s) > 0) and (FindFirstNotOf(DIGITS, AString) = 0) then
begin
week := IndyStrToInt(s);
end;
if (week > 0) and (i >= 0) then
begin
Dec(week);
FDay := 1 + (IdDaysInWeek * week);
// Now have the correct week of the year
if i < GetDayOfWeek then begin
SubtractDays(GetDayOfWeek - i);
end else begin
AddDays(i - GetDayOfWeek);
end;
end;
end
else if i > 0 then
begin
// Calender format
s := Trim(Copy(AString, 1, i - 1));
AString := Trim(Copy(AString, i + 1, MaxInt));
// Set the day first due to internal format.
if (Length(AString) > 0) and (FindFirstNotOf(DIGITS, s) = 0) then
begin
SetDay(IndyStrToInt(AString));
end;
// Add the months.
if (Length(s) > 0) and (FindFirstNotOf(DIGITS, s) = 0) then
begin
AddMonths(IndyStrToInt(s) - 1);
end;
end else
begin
// Ordinal format
i := FindFirstNotOf(DIGITS, AString);
if i = 0 then begin
SetDay(IndyStrToInt(AString));
end;
end;
end;
end;
end;
end;
procedure TIdDateTimeStamp.SetTimeFromISO8601(AString: String);
var
i : Integer;
Hour, Minute : String;
begin
// AString should be in the format of HH:MM:SS where : is a literal.
i := IndyPos(':', AString); {Do not Localize}
Hour := Trim(Copy(AString, 1, i - 1));
AString := Trim(Copy(AString, i + 1, MaxInt));
i := IndyPos(':', AString); {Do not Localize}
Minute := Trim(Copy(AString, 1, i - 1));
AString := Trim(Copy(AString, i + 1, MaxInt));
// Set seconds first due to internal format.
if (Length(AString) > 0) and (FindFirstNotOf(DIGITS, AString) = 0) then
begin
SetSecond(IndyStrToInt(AString));
end;
if (Length(Minute) > 0) and (FindFirstNotOf(DIGITS, Minute) = 0) then
begin
AddMinutes(IndyStrToInt(Minute));
end;
if (Length(Hour) > 0) and (FindFirstNotOf(DIGITS, Hour) = 0) then
begin
AddHours(IndyStrToInt(Hour));
end;
end;
procedure TIdDateTimeStamp.SetFromISO8601(AString: String);
var
i : Integer;
begin
Zero;
i := IndyPos('T', AString); {Do not Localize}
if i > 0 then
begin
SetDateFromISO8601(Trim(Copy(AString, 1, i - 1)));
SetTimeFromISO8601(Trim(Copy(AString, i + 1, MaxInt)));
end else
begin
SetDateFromISO8601(AString);
SetTimeFromISO8601(AString);
end;
end;
procedure TIdDateTimeStamp.SetFromRFC822(AString: String);
begin
SetFromTDateTime(StrInternetToDateTime(AString))
end;
procedure TIdDateTimeStamp.SetFromTDateTime(ADateTime : TDateTime);
var
LStamp: TIdDateTimeStamp;
begin
LStamp := LocalDateTimeToTimeStamp(ADateTime);
try
SetFromTTimeStamp(LStamp);
finally
FreeAndNil(LStamp);
end;
end;
procedure TIdDateTimeStamp.SetFromTTimeStamp(ATimeStamp : TIdDateTimeStamp);
begin
FDay := ATimeStamp.Day;
FMillisecond := ATimeStamp.Millisecond;
FIsLeapYear := ATimeStamp.IsLeapYear;
FSecond := ATimeStamp.Second;
FTimeZone := ATimeStamp.TimeZone;
FYear := ATimeStamp.Year;
end;
procedure TIdDateTimeStamp.SetDay(ANumber : Integer);
begin
if ANumber > 0 then begin
FDay := 0;
AddDays(ANumber);
end else begin
FDay := 1;
end;
end;
procedure TIdDateTimeStamp.SetMillisecond(ANumber : Integer);
begin
FMillisecond := 0;
AddMilliseconds(ANumber);
end;
procedure TIdDateTimeStamp.SetSecond(ANumber : Integer);
begin
FSecond := 0;
AddSeconds(ANumber);
end;
procedure TIdDateTimeStamp.SetTimeZone(const Value: Integer);
begin
FTimeZone := Value;
end;
procedure TIdDateTimeStamp.SetYear(ANumber : Integer);
begin
If ANumber = 0 then begin
FYear := 1;
end else begin
FYear := ANumber;
end;
CheckLeapYear;
end;
procedure TIdDateTimeStamp.SubtractDays(ANumber : UInt32);
var
i : Integer;
begin
if ANumber = 0 then begin
Exit;
end;
// First remove the number of days in this year. As with AddDays this
// is both an optimisation and a fix for calculations that begin in leap years.
if ANumber >= UInt32(FDay - 1) then begin
ANumber := ANumber - UInt32(FDay - 1);
FDay := 1;
end else begin
FDay := FDay - Integer(ANumber);
end;
// Subtract the number of whole leap year cycles = 400 years
if ANumber >= IdDaysInLeapYearCycle then begin
i := ANumber div IdDaysInLeapYearCycle;
SubtractYears(i * IdYearsInLeapYearCycle);
ANumber := ANumber - UInt32(i * IdDaysInLeapYearCycle);
end;
// Next subtract the centuries, checking for the century that is passed through
if ANumber >= IdDaysInLeapCentury then begin
while ANumber >= IdDaysInLeapCentury do begin
i := FYear div 100;
if i mod 4 = 0 then begin
// Going back through a 'leap' century {Do not Localize}
SubtractYears(IdYearsInCentury);
ANumber := ANumber - UInt32(IdDaysInLeapCentury);
end else begin
SubtractYears(IdYearsInCentury);
ANumber := ANumber - UInt32(IdDaysInCentury);
end;
end;
end;
// Subtract multiples of 4 ("Short" Leap year cycle)
if ANumber >= IdDaysInShortLeapYearCycle then begin
while ANumber >= IdDaysInShortLeapYearCycle do begin
// Round off current year to nearest four.
i := (FYear shr 2) shl 2;
if SysUtils.IsLeapYear(i) then begin
// Normal
SubtractYears(IdYearsInShortLeapYearCycle);
ANumber := ANumber - UInt32(IdDaysInShortLeapYearCycle);
end else begin
// Subtraction crosses a 100-year (but not 400-year) boundary. Add the
// same number of years, but one less day.
SubtractYears(IdYearsInShortLeapYearCycle);
ANumber := ANumber - UInt32(IdDaysInShortNonLeapYearCycle);
end;
end;
end;
// Now the individual years
while ANumber > UInt32(DaysInYear) do begin
SubtractYears(1);
Dec(ANumber, DaysInYear);
if Self.IsLeapYear then begin
// Correct the assumption of a non-leap year
AddDays(1);
end;
end;
// and finally the remainders
if ANumber >= UInt32(FDay) then begin
SubtractYears(1);
ANumber := ANumber - UInt32(FDay);
Day := DaysInYear - Integer(ANumber);
end else begin
Dec(FDay, ANumber);
end;
end;
procedure TIdDateTimeStamp.SubtractHours(ANumber : UInt32);
var
i : UInt32;
begin
i := ANumber div IdHoursInDay;
SubtractDays(i);
Dec(ANumber, i * IdHoursInDay);
SubtractSeconds(ANumber * IdSecondsInHour);
end;
procedure TIdDateTimeStamp.SubtractMilliseconds(ANumber : UInt32);
var
i : UInt32;
begin
if ANumber = 0 then begin
Exit;
end;
i := ANumber div IdMillisecondsInDay;
SubtractDays(i);
Dec(ANumber, i * IdMillisecondsInDay);
i := ANumber div IdMillisecondsInSecond;
SubtractSeconds(i);
Dec(ANumber, i * IdMillisecondsInSecond);
Dec(FMillisecond, ANumber);
while FMillisecond <= 0 do begin
SubtractSeconds(1);
// FMillisecond is already negative, so add it.
FMillisecond := IdMillisecondsInSecond + FMillisecond;
end;
end;
procedure TIdDateTimeStamp.SubtractMinutes(ANumber : UInt32);
begin
// Down size to seconds
while ANumber > MaxMinutesAdd do begin
SubtractSeconds(MaxMinutesAdd * IdSecondsInMinute);
Dec(ANumber, MaxMinutesAdd);
end;
SubtractSeconds(ANumber * IdSecondsInMinute);
end;
procedure TIdDateTimeStamp.SubtractMonths(ANumber : UInt32);
var
i : Integer;
begin
i := ANumber div IdMonthsInYear;
SubtractYears(i);
Dec(ANumber, i * IdMonthsInYear);
while ANumber > 0 do begin
i := MonthOfYear;
if i = 1 then begin
i := 13;
end;
if (i = 3) and (IsLeapYear) then begin
SubtractDays(IdDaysInMonth[2] + 1);
end else begin
SubtractDays(IdDaysInMonth[i - 1]);
end;
Dec(ANumber);
end;
end;
procedure TIdDateTimeStamp.SubtractSeconds(ANumber : UInt32);
var
i : UInt32;
begin
if ANumber = 0 then begin
Exit;
end;
i := ANumber div IdSecondsInDay;
SubtractDays(i);
Dec(ANumber, i * IdSecondsInDay);
Dec(FSecond, ANumber);
If FSecond < 0 then begin
SubtractDays(1);
FSecond := IdSecondsInDay + FSecond;
end;
end;
procedure TIdDateTimeStamp.SubtractTDateTime(ADateTime : TDateTime);
var LStamp : TIdDateTimeStamp;
begin
LStamp := LocalDateTimeToTimeStamp(ADateTime);
try
SubtractTIdDateTimeStamp(LStamp);
finally
FreeAndNil(LStamp);
end;
end;
procedure TIdDateTimeStamp.SubtractTIdDateTimeStamp(AIdDateTime : TIdDateTimeStamp);
begin
{ TODO : Check for accuracy }
SubtractYears(AIdDateTime.Year);
SubtractDays(AIdDateTime.Day);
SubtractSeconds(AIdDateTime.Second);
SubtractMilliseconds(AIdDateTime.Millisecond);
end;
procedure TIdDateTimeStamp.SubtractTTimeStamp(ATimeStamp : TIdDateTimeStamp);
begin
SubtractTIdDateTimeStamp(ATimeStamp);
end;
procedure TIdDateTimeStamp.SubtractWeeks(ANumber : UInt32);
begin
if ANumber = 0 then begin
Exit;
end;
// Down size to subtracting Days
while ANumber > MaxWeekAdd do begin
SubtractDays(MaxWeekAdd * IdDaysInWeek);
Dec(ANumber, MaxWeekAdd * IdDaysInWeek);
end;
SubtractDays(ANumber * IdDaysInWeek);
end;
procedure TIdDateTimeStamp.SubtractYears(ANumber : UInt32);
begin
if (FYear > 0) and (ANumber >= UInt32(FYear)) then begin
Inc(ANumber);
end;
FYear := FYear - Integer(ANumber);
CheckLeapYear;
end;
procedure TIdDateTimeStamp.Zero;
begin
ZeroDate;
ZeroTime;
FTimeZone := 0;
end;
procedure TIdDateTimeStamp.ZeroDate;
begin
SetYear(1);
SetDay(1);
end;
procedure TIdDateTimeStamp.ZeroTime;
begin
SetSecond(0);
SetMillisecond(0);
end;
end.