{ Simple trimming functions }

function DateOf(const AValue: TDateTime): TDateTime; inline;
function TimeOf(const AValue: TDateTime): TDateTime; inline;

{ Misc functions }

function IsInLeapYear(const AValue: TDateTime): Boolean;
function IsPM(const AValue: TDateTime): Boolean; inline;
function IsAM(const AValue: TDateTime): Boolean;
function IsValidDate(const AYear, AMonth, ADay: Word): Boolean;
function IsValidTime(const AHour, AMinute, ASecond, AMilliSecond: Word): Boolean;
function IsValidDateTime(const AYear, AMonth, ADay, AHour, AMinute, ASecond,
  AMilliSecond: Word): Boolean; inline;
function IsValidDateDay(const AYear, ADayOfYear: Word): Boolean;
function IsValidDateWeek(const AYear, AWeekOfYear,                    {ISO 8601}
  ADayOfWeek: Word): Boolean;
function IsValidDateMonthWeek(const AYear, AMonth, AWeekOfMonth,     {ISO 8601x}
  ADayOfWeek: Word): Boolean;
function WeeksInYear(const AValue: TDateTime): Word; inline;          {ISO 8601}
function WeeksInAYear(const AYear: Word): Word;                       {ISO 8601}
function DaysInYear(const AValue: TDateTime): Word; inline;
function DaysInAYear(const AYear: Word): Word; inline;
function DaysInMonth(const AValue: TDateTime): Word;
function DaysInAMonth(const AYear, AMonth: Word): Word;
function Today: TDateTime;
function Yesterday: TDateTime;
function Tomorrow: TDateTime;
function IsToday(const AValue: TDateTime): Boolean;
function IsSameDay(const AValue, ABasis: TDateTime): Boolean;

{ Pick-a-field functions }

function YearOf(const AValue: TDateTime): Word;
function MonthOf(const AValue: TDateTime): Word;
function WeekOf(const AValue: TDateTime): Word;                       {ISO 8601}
function DayOf(const AValue: TDateTime): Word;
function HourOf(const AValue: TDateTime): Word;
function MinuteOf(const AValue: TDateTime): Word;
function SecondOf(const AValue: TDateTime): Word;
function MilliSecondOf(const AValue: TDateTime): Word;

{ Start/End functions }

function StartOfTheYear(const AValue: TDateTime): TDateTime;
function EndOfTheYear(const AValue: TDateTime): TDateTime;
function StartOfAYear(const AYear: Word): TDateTime;
function EndOfAYear(const AYear: Word): TDateTime;

function StartOfTheMonth(const AValue: TDateTime): TDateTime;
function EndOfTheMonth(const AValue: TDateTime): TDateTime;
function StartOfAMonth(const AYear, AMonth: Word): TDateTime;
function EndOfAMonth(const AYear, AMonth: Word): TDateTime;

function StartOfTheWeek(const AValue: TDateTime): TDateTime;          {ISO 8601}
function EndOfTheWeek(const AValue: TDateTime): TDateTime;            {ISO 8601}
function StartOfAWeek(const AYear, AWeekOfYear: Word;                 {ISO 8601}
  const ADayOfWeek: Word = 1): TDateTime;
function EndOfAWeek(const AYear, AWeekOfYear: Word;                   {ISO 8601}
  const ADayOfWeek: Word = 7): TDateTime;

function StartOfTheDay(const AValue: TDateTime): TDateTime; inline;
function EndOfTheDay(const AValue: TDateTime): TDateTime;
function StartOfADay(const AYear, AMonth, ADay: Word): TDateTime; overload;
function EndOfADay(const AYear, AMonth, ADay: Word): TDateTime; overload;
function StartOfADay(const AYear, ADayOfYear: Word): TDateTime; overload;
function EndOfADay(const AYear, ADayOfYear: Word): TDateTime; overload;

{ This of that functions }

function MonthOfTheYear(const AValue: TDateTime): Word; inline;
function WeekOfTheYear(const AValue: TDateTime): Word; overload;      {ISO 8601}
function WeekOfTheYear(const AValue: TDateTime;                       {ISO 8601}
  var AYear: Word): Word; overload;
function DayOfTheYear(const AValue: TDateTime): Word;
function HourOfTheYear(const AValue: TDateTime): Word;
function MinuteOfTheYear(const AValue: TDateTime): LongWord;
function SecondOfTheYear(const AValue: TDateTime): LongWord;
function MilliSecondOfTheYear(const AValue: TDateTime): Int64;

function WeekOfTheMonth(const AValue: TDateTime): Word; overload;    {ISO 8601x}
function WeekOfTheMonth(const AValue: TDateTime; var AYear,          {ISO 8601x}
  AMonth: Word): Word; overload;
function DayOfTheMonth(const AValue: TDateTime): Word; inline;
function HourOfTheMonth(const AValue: TDateTime): Word;
function MinuteOfTheMonth(const AValue: TDateTime): Word;
function SecondOfTheMonth(const AValue: TDateTime): LongWord;
function MilliSecondOfTheMonth(const AValue: TDateTime): LongWord;

function DayOfTheWeek(const AValue: TDateTime): Word;                 {ISO 8601}
function HourOfTheWeek(const AValue: TDateTime): Word;                {ISO 8601}
function MinuteOfTheWeek(const AValue: TDateTime): Word;              {ISO 8601}
function SecondOfTheWeek(const AValue: TDateTime): LongWord;          {ISO 8601}
function MilliSecondOfTheWeek(const AValue: TDateTime): LongWord;     {ISO 8601}

function HourOfTheDay(const AValue: TDateTime): Word; inline;
function MinuteOfTheDay(const AValue: TDateTime): Word;
function SecondOfTheDay(const AValue: TDateTime): LongWord;
function MilliSecondOfTheDay(const AValue: TDateTime): LongWord;

function MinuteOfTheHour(const AValue: TDateTime): Word; inline;
function SecondOfTheHour(const AValue: TDateTime): Word;
function MilliSecondOfTheHour(const AValue: TDateTime): LongWord;

function SecondOfTheMinute(const AValue: TDateTime): Word; inline;
function MilliSecondOfTheMinute(const AValue: TDateTime): LongWord;

function MilliSecondOfTheSecond(const AValue: TDateTime): Word; inline;

{ Range checking functions }

function WithinPastYears(const ANow, AThen: TDateTime;
  const AYears: Integer): Boolean; inline;
function WithinPastMonths(const ANow, AThen: TDateTime;
  const AMonths: Integer): Boolean; inline;
function WithinPastWeeks(const ANow, AThen: TDateTime;
  const AWeeks: Integer): Boolean; inline;
function WithinPastDays(const ANow, AThen: TDateTime;
  const ADays: Integer): Boolean; inline;
function WithinPastHours(const ANow, AThen: TDateTime;
  const AHours: Int64): Boolean; inline;
function WithinPastMinutes(const ANow, AThen: TDateTime;
  const AMinutes: Int64): Boolean; inline;
function WithinPastSeconds(const ANow, AThen: TDateTime;
  const ASeconds: Int64): Boolean; inline;
function WithinPastMilliSeconds(const ANow, AThen: TDateTime;
  const AMilliSeconds: Int64): Boolean; inline;

{ Range query functions }

function YearsBetween(const ANow, AThen: TDateTime): Integer;
function MonthsBetween(const ANow, AThen: TDateTime): Integer;
function WeeksBetween(const ANow, AThen: TDateTime): Integer;
function DaysBetween(const ANow, AThen: TDateTime): Integer;
function HoursBetween(const ANow, AThen: TDateTime): Int64;
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
function SecondsBetween(const ANow, AThen: TDateTime): Int64;
function MilliSecondsBetween(const ANow, AThen: TDateTime): Int64;

{ InRange functions }

function DateTimeInRange(ADateTime: TDateTime; AStartDateTime, AEndDateTime: TDateTime; aInclusive: Boolean = True): Boolean;
function DateInRange(ADate: TDate; AStartDate, AEndDate: TDate; AInclusive: Boolean = True): Boolean;
function TimeInRange(ATime: TTime; AStartTime, AEndTime: TTime; AInclusive: Boolean = True): Boolean;


{ Range spanning functions }
{ YearSpan and MonthSpan are approximates, not exact but pretty darn close }
function YearSpan(const ANow, AThen: TDateTime): Double;
function MonthSpan(const ANow, AThen: TDateTime): Double;
function WeekSpan(const ANow, AThen: TDateTime): Double;
function DaySpan(const ANow, AThen: TDateTime): Double;
function HourSpan(const ANow, AThen: TDateTime): Double;
function MinuteSpan(const ANow, AThen: TDateTime): Double;
function SecondSpan(const ANow, AThen: TDateTime): Double;
function MilliSecondSpan(const ANow, AThen: TDateTime): Double;

{ Increment/decrement datetime fields }

function IncYear(const AValue: TDateTime;
  const ANumberOfYears: Integer = 1): TDateTime; inline;
// function IncMonth is in SysUtils
function IncWeek(const AValue: TDateTime;
  const ANumberOfWeeks: Integer = 1): TDateTime; inline;
function IncDay(const AValue: TDateTime;
  const ANumberOfDays: Integer = 1): TDateTime; inline;
function IncHour(const AValue: TDateTime;
  const ANumberOfHours: Int64 = 1): TDateTime; inline;
function IncMinute(const AValue: TDateTime;
  const ANumberOfMinutes: Int64 = 1): TDateTime; inline;
function IncSecond(const AValue: TDateTime;
  const ANumberOfSeconds: Int64 = 1): TDateTime; inline;
function IncMilliSecond(const AValue: TDateTime;
  const ANumberOfMilliSeconds: Int64 = 1): TDateTime;

{ Unified encode/decode functions that deal with all datetime fields at once }

function EncodeDateTime(const AYear, AMonth, ADay, AHour, AMinute, ASecond,
  AMilliSecond: Word): TDateTime;
procedure DecodeDateTime(const AValue: TDateTime; out AYear, AMonth, ADay,
  AHour, AMinute, ASecond, AMilliSecond: Word);

{ Encode/decode functions that work with week of year and day of week }

function EncodeDateWeek(const AYear, AWeekOfYear: Word;               {ISO 8601}
  const ADayOfWeek: Word = 1): TDateTime;
procedure DecodeDateWeek(const AValue: TDateTime; out AYear,          {ISO 8601}
  AWeekOfYear, ADayOfWeek: Word);

{ Encode/decode functions that work with day of year }

function EncodeDateDay(const AYear, ADayOfYear: Word): TDateTime;
procedure DecodeDateDay(const AValue: TDateTime; out AYear, ADayOfYear: Word);

{ Encode/decode functions that work with week of month }

function EncodeDateMonthWeek(const AYear, AMonth, AWeekOfMonth,      {ISO 8601x}
  ADayOfWeek: Word): TDateTime;
procedure DecodeDateMonthWeek(const AValue: TDateTime;               {ISO 8601x}
  out AYear, AMonth, AWeekOfMonth, ADayOfWeek: Word);

{ The following functions are similar to the above ones except these don't
  generated exceptions on failure, they return false instead }

function TryEncodeDateTime(const AYear, AMonth, ADay, AHour, AMinute, ASecond,
  AMilliSecond: Word; out AValue: TDateTime): Boolean;
function TryEncodeDateWeek(const AYear, AWeekOfYear: Word;            {ISO 8601}
  out AValue: TDateTime; const ADayOfWeek: Word = 1): Boolean;
function TryEncodeDateDay(const AYear, ADayOfYear: Word;
  out AValue: TDateTime): Boolean;
function TryEncodeDateMonthWeek(const AYear, AMonth, AWeekOfMonth,   {ISO 8601x}
  ADayOfWeek: Word; var AValue: TDateTime): Boolean;

{ Recode functions for datetime fields }

function RecodeYear(const AValue: TDateTime; const AYear: Word): TDateTime;
function RecodeMonth(const AValue: TDateTime; const AMonth: Word): TDateTime;
function RecodeDay(const AValue: TDateTime; const ADay: Word): TDateTime;
function RecodeHour(const AValue: TDateTime; const AHour: Word): TDateTime;
function RecodeMinute(const AValue: TDateTime; const AMinute: Word): TDateTime;
function RecodeSecond(const AValue: TDateTime; const ASecond: Word): TDateTime;
function RecodeMilliSecond(const AValue: TDateTime;
  const AMilliSecond: Word): TDateTime;

function RecodeDate(const AValue: TDateTime; const AYear, AMonth,
  ADay: Word): TDateTime;
function RecodeTime(const AValue: TDateTime; const AHour, AMinute, ASecond,
  AMilliSecond: Word): TDateTime;
function RecodeDateTime(const AValue: TDateTime; const AYear, AMonth, ADay,
  AHour, AMinute, ASecond, AMilliSecond: Word): TDateTime;

{ The following function is similar to the above one except it doesn't
  generated an exception on failure, it return false instead }

function TryRecodeDateTime(const AValue: TDateTime; const AYear, AMonth, ADay,
  AHour, AMinute, ASecond, AMilliSecond: Word; out AResult: TDateTime): Boolean;

{ Fuzzy comparison }

function CompareDateTime(const A, B: TDateTime): TValueRelationship;
function SameDateTime(const A, B: TDateTime): Boolean;
function CompareDate(const A, B: TDateTime): TValueRelationship;
function SameDate(const A, B: TDateTime): Boolean;
function CompareTime(const A, B: TDateTime): TValueRelationship;
function SameTime(const A, B: TDateTime): Boolean;

{ For a given date these functions tell you the which day of the week of the
  month (or year).  If its a Thursday, they will tell you if its the first,
  second, etc Thursday of the month (or year).  Remember, even though its
  the first Thursday of the year it doesn't mean its the first week of the
  year.  See ISO 8601 above for more information. }

function NthDayOfWeek(const AValue: TDateTime): Word;

procedure DecodeDayOfWeekInMonth(const AValue: TDateTime; out AYear, AMonth,
  ANthDayOfWeek, ADayOfWeek: Word);

function EncodeDayOfWeekInMonth(const AYear, AMonth, ANthDayOfWeek,
  ADayOfWeek: Word): TDateTime;
function TryEncodeDayOfWeekInMonth(const AYear, AMonth, ANthDayOfWeek,
  ADayOfWeek: Word; out AValue: TDateTime): Boolean;

{ Error reporting }

procedure InvalidDateTimeError(const AYear, AMonth, ADay, AHour, AMinute,
  ASecond, AMilliSecond: Word; const ABaseDate: TDateTime = 0);
procedure InvalidDateWeekError(const AYear, AWeekOfYear, ADayOfWeek: Word);
procedure InvalidDateDayError(const AYear, ADayOfYear: Word);
procedure InvalidDateMonthWeekError(const AYear, AMonth, AWeekOfMonth,
  ADayOfWeek: Word);
procedure InvalidDayOfWeekInMonthError(const AYear, AMonth, ANthDayOfWeek,
  ADayOfWeek: Word);

{ Julian and Modified Julian Date conversion support }
{ Be aware that not all Julian Dates (or MJD) are encodable as a TDateTime }

function DateTimeToJulianDate(const AValue: TDateTime): Double;
function JulianDateToDateTime(const AValue: Double): TDateTime;
function TryJulianDateToDateTime(const AValue: Double;
  out ADateTime: TDateTime): Boolean;

function DateTimeToModifiedJulianDate(const AValue: TDateTime): Double;
function ModifiedJulianDateToDateTime(const AValue: Double): TDateTime;
function TryModifiedJulianDateToDateTime(const AValue: Double;
  out ADateTime: TDateTime): Boolean;

{ Unix date conversion support }

function DateTimeToUnix(const AValue: TDateTime; AInputIsUTC: Boolean = True): Int64;
function UnixToDateTime(const AValue: Int64; AReturnUTC: Boolean = True): TDateTime;

{ Constants used in this unit }

const
  DaysPerWeek = 7;
  WeeksPerFortnight = 2;
  MonthsPerYear = 12;
  YearsPerDecade = 10;
  YearsPerCentury = 100;
  YearsPerMillennium = 1000;

  DayMonday = 1;
  DayTuesday = 2;
  DayWednesday = 3;
  DayThursday = 4;
  DayFriday = 5;
  DaySaturday = 6;
  DaySunday = 7;

  MonthJanuary = 1;
  MonthFebruary = 2;
  MonthMarch = 3;
  MonthApril = 4;
  MonthMay = 5;
  MonthJune = 6;
  MonthJuly = 7;
  MonthAugust = 8;
  MonthSeptember = 9;
  MonthOctober = 10;
  MonthNovember = 11;
  MonthDecember = 12;

 
  OneHour = 1 / HoursPerDay;
  {$HPPEMIT OPENNAMESPACE}
  {$EXTERNALSYM OneHour}
  {$HPPEMIT 'extern const System::Extended OneHour /*= 4.166667E-02*/;'}
  OneMinute = 1 / MinsPerDay;
  {$EXTERNALSYM OneMinute}
  {$HPPEMIT 'extern const System::Extended OneMinute /*= 6.944444E-04*/;'}
  OneSecond = 1 / SecsPerDay;
  {$EXTERNALSYM OneSecond}
  {$HPPEMIT 'extern const System::Extended OneSecond /*= 1.157407E-05*/;'}
  OneMillisecond = 1 / MSecsPerDay;
  {$EXTERNALSYM OneMillisecond}
  {$HPPEMIT 'extern const System::Extended OneMillisecond /*= 1.157407E-08*/;'}

  EpochAsJulianDate = 2415018.5;
  {$EXTERNALSYM EpochAsJulianDate}
  {$HPPEMIT 'extern const System::Extended EpochAsJulianDate /*= 2.415018E+06*/;'}
  {$HPPEMIT CLOSENAMESPACE}
  EpochAsUnixDate   = -2209161600;

  { This is actual days per year but you need to know if it's a leap year}
  DaysPerYear: array [Boolean] of Word = (365, 366);

  { Used in RecodeDate, RecodeTime and RecodeDateTime for those datetime }
  {  fields you want to leave alone }
  RecodeLeaveFieldAsIs = High(Word);

{ Global variable used in this unit }

var
  { average over a 4 year span }
  ApproxDaysPerMonth: Double = 30.4375;
  ApproxDaysPerYear: Double  = 365.25;

  { The above are the average days per month/year over a normal 4 year period. }
  { We use these approximations because they are more accurate for the next }
  {  century or so.  After that you may want to switch over to these 400 year }
  {  approximations... }
  {    ApproxDaysPerMonth = 30.436875 }
  {    ApproxDaysPerYear  = 365.2425 }

type
  { Exception type used in this unit to signal that a given local time is situated in the
    invalid period (usually the missing hour during the DST time-shift). }
  ELocalTimeInvalid = class(Exception);
  EDateTimeException = class(Exception);

  { Specifies the type of a date/time value }
  TLocalTimeType = (
    { Identifies a date/time value when DST rules are not in effect }
    lttStandard,
    { Identifies a date/time value when DST rules are in effect }
    lttDaylight,
    { Identifies a date/time value situated in the ambiguous interval (ex. in the repeating hour) }
    lttAmbiguous,
    { Identifies a date/time value situated in the invalid interval (in the missing hour) }
    lttInvalid
  );

  { Use TTimeZone to gain access to a set of methods that can be used to convert between
    local time (as seen by the current user of the time zone) and universal time. }
  TTimeZone = class abstract
  private
    { Contains the "local" time zone. Instantiated at unit initialization }
    class var FLocal: TTimeZone;

    { Called during initialization. Initializes the cache (empty) and the local time zone }
    class constructor Create;

    { Called during finalization. Destroys the internal cache and contained time zones (inclusing the local one) }
    class destructor Destroy;

    { Returns the time zone抯 current abbreviated name }
    function GetAbbreviationForNow: string; inline;

    { Returns the time zone抯 current display name }
    function GetDisplayNameForNow: string; inline;

    { Calculates the UTC offset for the given local time. The UTC offset represents the amount of time that
      should be subtracted from the given local time to obtain universal time. }
    function GetUtcOffsetInSeconds(const ADateTime: TDateTime; const ForceDaylight: Boolean): Int64;

    { Returns the current time zone's UTC offset }
    function GetCurrentUtcOffset: TTimeSpan; inline;
  protected
    { Override in derived classes to supply offset information about a local time. }
    procedure DoGetOffsetsAndType(
      const ADateTime: TDateTime; out AOffset, ADstSave: Int64; out AType: TLocalTimeType); virtual; abstract;

    { Override in derived classes to supply a TZ name for a given local time. }
    function DoGetDisplayName(const ADateTime: TDateTime; const ForceDaylight: Boolean): string; virtual; abstract;

    { Override to return the time zone ID. }
    function DoGetID: string; virtual; abstract;
  public
    { Calculates the UTC offset for the given local time. The UTC offset represents the amount of time that should
      be subtracted from the given local time to obtain universal time. ForceDaylight specifes how
      ambiguous time is treated. }
    function GetUtcOffset(const ADateTime: TDateTime; const ForceDaylight: Boolean = False): TTimeSpan; inline;

    { Converts a given local time to universal time. ForceDaylight specifes how
      ambiguous time is treated. }
    function ToUniversalTime(const ADateTime: TDateTime; const ForceDaylight: Boolean = False): TDateTime; inline;

    { Converts a given universal time to local time. }
    function ToLocalTime(const ADateTime: TDateTime): TDateTime;

    { Returns the full name (localized if possible) for the time zone based on the given date/time. }
    function GetDisplayName(const ADateTime: TDateTime; const ForceDaylight: Boolean = False): string;

    { Returns an abbreviated name that can be used to position the local time in relation to UTC.
      Non-localized, always specified, and has the form: GMT[+|-HH[:MM]]. }
    function GetAbbreviation(const ADateTime: TDateTime; const ForceDaylight: Boolean = False): string;

    { Returns the type of a given date/time in this time zone. }
    function GetLocalTimeType(const ADateTime: TDateTime): TLocalTimeType; inline;

    { Checks whether the given local date/time is situated outside the DST period. ForceDaylight
      specifes how ambiguous time is treated. }
    function IsStandardTime(const ADateTime: TDateTime; const ForceDaylight: Boolean = False): Boolean;

    { Checks whether the given local date/time is invalid (situated within the limbo hour(s)) }
    function IsInvalidTime(const ADateTime: TDateTime): Boolean; inline;

    { Checks whether the given local date/time is ambiguous (situated within the repeating hour(s)) }
    function IsAmbiguousTime(const ADateTime: TDateTime): Boolean; inline;

    { Checks whether the given local date/time is situated in the DST period. ForceDaylight
      specifes how ambiguous time is treated. }
    function IsDaylightTime(const ADateTime: TDateTime; const ForceDaylight: Boolean = False): Boolean;

    { Retunrs the time zone identification string. }
    property ID: string read DoGetID;

    { Specifies the display name (localized if possible) of this time zone. }
    property DisplayName: string read GetDisplayNameForNow;

    { Current time zone's abbreviation. Non-localized, always specified, and has the form: GMT[+|-HH[:MM]]. }
    property Abbreviation: string read GetAbbreviationForNow;

    { Specifies the time zone's current UTC offset. }
    property UtcOffset: TTimeSpan read GetCurrentUtcOffset;

    { Specifies the local time zone. Use this property to obtain a TTimeZone instance that identifies
      current user's time zone. }
    class property Local: TTimeZone read FLocal;
  end;

/// <summary>
/// Converts an ISO8601 encoded date/time string to TDateTime. The result is in local time, if AReturnUTC is false.
/// </summary>
/// <example>
/// <para> "2013-10-18T20:36:22.966Z"  </para>
/// <para> This is 20:36 UTC time (Zulu). If爐he local timezone is MEST (UTC+2), then resulting time will be 22:36. </para>
/// </example>
function ISO8601ToDate(const AISODate: string; AReturnUTC: Boolean = True): TDateTime;

/// <summary> Converts a TDateTime value to ISO8601 format. </summary>
/// <param name="ADate"> A TDateTime value </param>
/// <param name="AInputIsUTC">
/// If AInputIsUTC is true, then the resulting ISO8601 string will show the exact same time as ADate has. </param>
/// <returns> ISO8601 representation of ADate. The resulting營SO string will be in UTC, i.e. will have a Z (Zulu) post
/// fix. If AInputIsUTC = true, then the time portion of ADate爓ill not be modified. </returns>
function DateToISO8601(const ADate: TDateTime; AInputIsUTC: Boolean = True): string;