delphimvcframework/lib/dmustache/mormot.core.datetime.pas
2024-04-29 15:40:45 +02:00

3653 lines
116 KiB
ObjectPascal

/// Framework Core Low-Level Date and Time Support
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.core.datetime;
{
*****************************************************************************
Date and Time definitions and process shared by all framework units
- ISO-8601 Compatible Date/Time Text Encoding
- TSynDate / TSynDateTime / TSynSystemTime High-Level objects
- TUnixTime / TUnixMSTime POSIX Epoch Compatible 64-bit date/time
- TTimeLog efficient 64-bit custom date/time encoding
- TTextDateWriter supporting date/time ISO-8601 serialization
- TValuePUtf8Char text value wrapper record
*****************************************************************************
}
interface
{$I mormot.defines.inc}
uses
sysutils,
classes,
mormot.core.base,
mormot.core.os,
mormot.core.unicode,
mormot.core.text;
{ ************ ISO-8601 Compatible Date/Time Text Encoding }
const
/// UTF-8 encoded \uFFF1 special code to mark ISO-8601 SQLDATE in JSON
// - e.g. '"\uFFF12012-05-04"' pattern
// - Unicode special char U+FFF1 is UTF-8 encoded as EF BF B1 bytes
// - as generated by DateToSql/DateTimeToSql/TimeLogToSql functions, and
// expected by the TExtractInlineParameters decoder
JSON_SQLDATE_MAGIC_C = $b1bfef;
/// UTF-8 encoded \uFFF1 special code to mark ISO-8601 SQLDATE in JSON
// - e.g. '"\uFFF12012-05-04"' pattern
JSON_SQLDATE_MAGIC_STR: string[3] = #$ef#$bf#$b1;
/// '"' + UTF-8 encoded \uFFF1 special code to mark ISO-8601 SQLDATE in JSON
JSON_SQLDATE_MAGIC_QUOTE_C = ord('"') + cardinal(JSON_SQLDATE_MAGIC_C) shl 8;
/// '"' + UTF-8 encoded \uFFF1 special code to mark ISO-8601 SQLDATE in JSON
// - defined as a ShortString constant to be used as:
// ! AddShorter(JSON_SQLDATE_MAGIC_QUOTE_STR);
JSON_SQLDATE_MAGIC_QUOTE_STR: string[4] = '"'#$ef#$bf#$b1;
/// Date/Time conversion from ISO-8601
// - handle 'YYYYMMDDThhmmss' and 'YYYY-MM-DD hh:mm:ss' format
// - will also recognize '.sss' milliseconds suffix, if any
function Iso8601ToDateTime(const S: RawByteString): TDateTime; overload;
{$ifdef HASINLINE}inline;{$endif}
/// Date/Time conversion from ISO-8601
// - handle 'YYYYMMDDThhmmss' and 'YYYY-MM-DD hh:mm:ss' format
// - could have been written e.g. by DateTimeToIso8601Text()
// - will also recognize '.sss' milliseconds suffix, if any
// - if L is left to default 0, it will be computed from StrLen(P)
function Iso8601ToDateTimePUtf8Char(P: PUtf8Char; L: integer = 0): TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// Date/Time conversion from ISO-8601
// - handle 'YYYYMMDDThhmmss' and 'YYYY-MM-DD hh:mm:ss' format, with potentially
// shorten versions has handled by the ISO-8601 standard (e.g. 'YYYY')
// - will also recognize '.sss' milliseconds suffix, if any
// - any ending/trailing single quote will be removed
// - if L is left to default 0, it will be computed from StrLen(P)
procedure Iso8601ToDateTimePUtf8CharVar(P: PUtf8Char; L: integer;
var result: TDateTime);
/// Date/Time conversion from strict ISO-8601 content
// - recognize 'YYYY-MM-DDThh:mm:ss[.sss]' or 'YYYY-MM-DD' or 'Thh:mm:ss[.sss]'
// patterns, as e.g. generated by TJsonWriter.AddDateTime() or RecordSaveJson()
// - will also recognize '.sss' milliseconds suffix, if any
function Iso8601CheckAndDecode(P: PUtf8Char; L: integer;
var Value: TDateTime): boolean;
/// test if P^ contains a valid ISO-8601 text encoded value
// - calls internally Iso8601ToTimeLogPUtf8Char() and returns true if contains
// at least a valid year (YYYY)
function IsIso8601(P: PUtf8Char; L: integer): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// Time conversion from ISO-8601 (with no Date part)
// - handle 'hhmmss' and 'hh:mm:ss' format
// - will also recognize '.sss' milliseconds suffix, if any
// - if L is left to default 0, it will be computed from StrLen(P)
function Iso8601ToTimePUtf8Char(P: PUtf8Char; L: integer = 0): TDateTime; overload;
{$ifdef HASINLINE}inline;{$endif}
/// Time conversion from ISO-8601 (with no Date part)
// - handle 'hhmmss' and 'hh:mm:ss' format
// - will also recognize '.sss' milliseconds suffix, if any
// - if L is left to default 0, it will be computed from StrLen(P)
procedure Iso8601ToTimePUtf8CharVar(P: PUtf8Char; L: integer;
var result: TDateTime);
/// Time conversion from ISO-8601 (with no Date part)
// - recognize 'hhmmss' and 'hh:mm:ss' format into H,M,S variables
// - will also recognize '.sss' milliseconds suffix, if any, into MS
// - if L is left to default 0, it will be computed from StrLen(P)
function Iso8601ToTimePUtf8Char(P: PUtf8Char; L: integer;
var H, M, S, MS: cardinal): boolean; overload;
/// Date conversion from ISO-8601 (with no Time part)
// - recognize 'YYYY-MM-DD' and 'YYYYMMDD' format into Y,M,D variables
// - if L is left to default 0, it will be computed from StrLen(P)
function Iso8601ToDatePUtf8Char(P: PUtf8Char; L: integer;
var Y, M, D: cardinal): boolean;
/// Interval date/time conversion from simple text
// - expected format does not match ISO-8601 Time intervals format, but Oracle
// interval litteral representation, i.e. '+/-D HH:MM:SS'
// - e.g. IntervalTextToDateTime('+0 06:03:20') will return 0.25231481481 and
// IntervalTextToDateTime('-20 06:03:20') -20.252314815
// - as a consequence, negative intervals will be written as TDateTime values:
// !DateTimeToIso8601Text(IntervalTextToDateTime('+0 06:03:20'))='T06:03:20'
// !DateTimeToIso8601Text(IntervalTextToDateTime('+1 06:03:20'))='1899-12-31T06:03:20'
// !DateTimeToIso8601Text(IntervalTextToDateTime('-2 06:03:20'))='1899-12-28T06:03:20'
function IntervalTextToDateTime(Text: PUtf8Char): TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// Interval date/time conversion from simple text
// - expected format does not match ISO-8601 Time intervals format, but Oracle
// interval litteral representation, i.e. '+/-D HH:MM:SS'
// - e.g. '+1 06:03:20' will return 1.25231481481
procedure IntervalTextToDateTimeVar(Text: PUtf8Char; var result: TDateTime);
/// basic Date/Time conversion into ISO-8601
// - use 'YYYYMMDDThhmmss' format if not Expanded
// - use 'YYYY-MM-DDThh:mm:ss' format if Expanded
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
// - if QuotedChar is not default #0, will (double) quote the resulted text
// - you may rather use DateTimeToIso8601Text() to handle 0 or date-only values
function DateTimeToIso8601(D: TDateTime; Expanded: boolean; FirstChar: AnsiChar = 'T';
WithMS: boolean = false; QuotedChar: AnsiChar = #0): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// raw basic Date/Time conversion into ISO-8601 RawUtf8
procedure DateTimeToIso8601Var(D: TDateTime; Expanded, WithMS: boolean;
FirstChar, QuotedChar: AnsiChar; var Result: RawUtf8);
/// raw basic Date/Time conversion into ISO-8601 shortstring
function DateTimeToIso8601Short(D: TDateTime; Expanded: boolean = true;
WithMS: boolean = false; FirstChar: AnsiChar = 'T';
QuotedChar: AnsiChar = #0): TShort31;
/// basic Date/Time conversion into ISO-8601
// - use 'YYYYMMDDThhmmss' format if not Expanded
// - use 'YYYY-MM-DDThh:mm:ss' format if Expanded
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
// - if QuotedChar is not default #0, will (double) quote the resulted text
// - you may rather use DateTimeToIso8601Text() to handle 0 or date-only values
// - returns the number of chars written to P^ buffer
function DateTimeToIso8601(P: PUtf8Char; D: TDateTime; Expanded: boolean;
FirstChar: AnsiChar = 'T'; WithMS: boolean = false;
QuotedChar: AnsiChar = #0): integer; overload;
/// basic Date conversion into ISO-8601
// - use 'YYYYMMDD' format if not Expanded
// - use 'YYYY-MM-DD' format if Expanded
function DateToIso8601(Date: TDateTime; Expanded: boolean): RawUtf8; overload;
/// basic Date conversion into ISO-8601
// - use 'YYYYMMDD' format if not Expanded
// - use 'YYYY-MM-DD' format if Expanded
function DateToIso8601(Y, M, D: cardinal; Expanded: boolean): RawUtf8; overload;
/// basic Date period conversion into ISO-8601
// - will convert an elapsed number of days as ISO-8601 text
// - use 'YYYYMMDD' format if not Expanded
// - use 'YYYY-MM-DD' format if Expanded
function DaysToIso8601(Days: cardinal; Expanded: boolean): RawUtf8;
/// basic Time conversion into ISO-8601
// - use 'Thhmmss' format if not Expanded
// - use 'Thh:mm:ss' format if Expanded
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
function TimeToIso8601(Time: TDateTime; Expanded: boolean; FirstChar: AnsiChar = 'T';
WithMS: boolean = false): RawUtf8;
/// Write a Date to P^ Ansi buffer
// - if Expanded is false, 'YYYYMMDD' date format is used
// - if Expanded is true, 'YYYY-MM-DD' date format is used
function DateToIso8601PChar(P: PUtf8Char; Expanded: boolean;
Y, M, D: PtrUInt): PUtf8Char; overload;
/// convert a date into 'YYYY-MM-DD' date format
// - resulting text is compatible with all ISO-8601 functions
function DateToIso8601Text(Date: TDateTime): RawUtf8;
/// Write a Date/Time to P^ Ansi buffer
function DateToIso8601PChar(Date: TDateTime; P: PUtf8Char;
Expanded: boolean): PUtf8Char; overload;
/// Write a TDateTime value, expanded as Iso-8601 encoded text into P^ Ansi buffer
// - if DT=0, returns ''
// - if DT contains only a date, returns the date encoded as 'YYYY-MM-DD'
// - if DT contains only a time, returns the time encoded as 'Thh:mm:ss'
// - otherwise, returns the ISO-8601 date and time encoded as 'YYYY-MM-DDThh:mm:ss'
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
function DateTimeToIso8601ExpandedPChar(const Value: TDateTime; Dest: PUtf8Char;
FirstChar: AnsiChar = 'T'; WithMS: boolean = false): PUtf8Char;
/// write a TDateTime into strict ISO-8601 date and/or time text
// - if DT=0, returns ''
// - if DT contains only a date, returns the date encoded as 'YYYY-MM-DD'
// - if DT contains only a time, returns the time encoded as 'Thh:mm:ss'
// - otherwise, returns the ISO-8601 date and time encoded as 'YYYY-MM-DDThh:mm:ss'
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
// - used e.g. by TPropInfo.GetValue() and TPropInfo.NormalizeValue() methods
function DateTimeToIso8601Text(DT: TDateTime; FirstChar: AnsiChar = 'T';
WithMS: boolean = false): RawUtf8;
/// write a TDateTime into strict ISO-8601 date and/or time text
// - if DT=0, returns ''
// - if DT contains only a date, returns the date encoded as 'YYYY-MM-DD'
// - if DT contains only a time, returns the time encoded as 'Thh:mm:ss'
// - otherwise, returns the ISO-8601 date and time encoded as 'YYYY-MM-DDThh:mm:ss'
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
// - used e.g. by TPropInfo.GetValue() and TPropInfo.NormalizeValue() methods
procedure DateTimeToIso8601TextVar(DT: TDateTime; FirstChar: AnsiChar;
var result: RawUtf8; WithMS: boolean = false);
/// write a TDateTime into strict ISO-8601 date and/or time text
// - if DT=0, returns ''
// - if DT contains only a date, returns the date encoded as 'YYYY-MM-DD'
// - if DT contains only a time, returns the time encoded as 'Thh:mm:ss'
// - otherwise, returns the ISO-8601 date and time encoded as 'YYYY-MM-DDThh:mm:ss'
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
// - used e.g. by TPropInfo.GetValue() and TPropInfo.NormalizeValue() methods
procedure DateTimeToIso8601StringVar(DT: TDateTime; FirstChar: AnsiChar;
var result: string; WithMS: boolean = false);
/// Write a Time to P^ Ansi buffer
// - if Expanded is false, 'Thhmmss' time format is used
// - if Expanded is true, 'Thh:mm:ss' time format is used
// - you can custom the first char in from of the resulting text time
// - if WithMS is TRUE, will append MS as '.sss' for milliseconds resolution
function TimeToIso8601PChar(P: PUtf8Char; Expanded: boolean; H, M, S, MS: PtrUInt;
FirstChar: AnsiChar = 'T'; WithMS: boolean = false): PUtf8Char; overload;
/// Write a Time to P^ Ansi buffer
// - if Expanded is false, 'Thhmmss' time format is used
// - if Expanded is true, 'Thh:mm:ss' time format is used
// - you can custom the first char in from of the resulting text time
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
function TimeToIso8601PChar(Time: TDateTime; P: PUtf8Char; Expanded: boolean;
FirstChar: AnsiChar = 'T'; WithMS: boolean = false): PUtf8Char; overload;
/// convert any date/time Variant into a TDateTime value
// - would handle varDate kind of variant, or use a string conversion and
// ISO-8601 parsing if possible
function VariantToDateTime(const V: Variant; var Value: TDateTime): boolean;
/// decode most used TimeZone text values (CEST, GMT, +0200, -0800...)
// - on match, returns true and the time zone minutes offset in respect to UTC
// - if P is not a time zone, returns false and leave Zone to its supplied value
// - will recognize only the most used text values using a fixed table (RFC 822
// with some extensions like -0000 as current system timezone) - using
// numerical zones is the preferred way in recent RFC anyway
function ParseTimeZone(var P: PUtf8Char; var Zone: integer): boolean; overload;
/// decode most used TimeZone text values (CEST, GMT, +0200, -0800...)
// - just a wrapper around overloaded ParseTimeZone(PUtf8Char)
function ParseTimeZone(const s: RawUtf8; var Zone: integer): boolean; overload;
/// decode a month from its RFC 822 text value (Jan, Feb...)
function ParseMonth(var P: PUtF8Char; var Month: word): boolean; overload;
/// decode a month from its RFC 822 text value (Jan, Feb...)
function ParseMonth(const s: RawUtf8; var Month: word): boolean; overload;
const
/// Rotate local log file if reached this size (1MB by default)
// - .log file will be save as .log.bak file
// - a new .log file is created
// - used by AppendToTextFile() and LogToTextFile() functions (not TSynLog)
MAXLOGSIZE = 1024*1024;
/// log a message with the current timestamp to a local text file
// - the text file is located in the executable directory, and its name is
// simply the executable file name with the '.log' extension instead of '.exe'
// - format contains the current date and time, then the Msg on one line
// - date and time format used is 'YYYYMMDD hh:mm:ss (i.e. ISO-8601)'
procedure LogToTextFile(Msg: RawUtf8);
/// log a message with the current timestamp to a local text file
// - this version expects the filename to be specified
// - format contains the current date and time, then the Msg on one line
// - date and time format used is 'YYYYMMDD hh:mm:ss'
function AppendToTextFile(const aLine: RawUtf8; const aFileName: TFileName;
aMaxSize: Int64 = MAXLOGSIZE; aUtcTimeStamp: boolean = false): boolean;
var
/// custom TTimeLog date to ready to be displayed text function
// - you can override this pointer in order to display the text according
// to your expected i18n settings
// - this callback will therefore be set by the mORMoti18n.pas unit
// - used e.g. by TTimeLogBits.i18nText and by TOrmTable.ExpandAsString()
// methods, i.e. TOrmTableToGrid.DrawCell()
i18nDateText: function(const Iso: TTimeLog): string = nil;
/// custom date to ready to be displayed text function
// - you can override this pointer in order to display the text according
// to your expected i18n settings
// - this callback will therefore be set by the mORMoti18n.pas unit
// - used e.g. by TOrmTable.ExpandAsString() method,
// i.e. TOrmTableToGrid.DrawCell()
i18nDateTimeText: function(const DateTime: TDateTime): string = nil;
{ ************ TSynDate / TSynDateTime / TSynSystemTime High-Level objects }
type
{$A-}
/// a simple way to store a date as Year/Month/Day
// - with no intermediate computation needed as with TDate/TUnixTime values
// - consider using TSynSystemTime if you need to handle both Date and Time
// - match the first 4 fields of TSynSystemTime - so PSynDate(@aSynSystemTime)^
// is safe to be used
// - some Delphi revisions have trouble with "object" as own method parameters
// (e.g. IsEqual or Compare) so we force to use "record" type if possible
{$ifdef USERECORDWITHMETHODS}
TSynDate = record
{$else}
TSynDate = object
{$endif USERECORDWITHMETHODS}
public
/// the Year value of this Date
Year: word;
/// the Month value of this Date (1..12)
Month: word;
/// which day of week this Date happened
// - sunday is DayOfWeek 1, saturday is 7
// - DayOfWeek field is not handled by its methods by default, but could be
// filled on demand via ComputeDayOfWeek - making this record 64-bit long
DayOfWeek: word;
/// the Day value of this Date (1..31)
Day: word;
/// set all fields to 0
procedure Clear;
{$ifdef HASINLINE}inline;{$endif}
/// set internal date to 9999-12-31
procedure SetMax;
{$ifdef HASINLINE}inline;{$endif}
/// returns true if all fields are zero
function IsZero: boolean;
{$ifdef HASINLINE}inline;{$endif}
/// try to parse a YYYY-MM-DD or YYYYMMDD ISO-8601 date from the supplied buffer
// - on success, move P^ just after the date, and return TRUE
function ParseFromText(var P: PUtf8Char): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// fill fields with the current UTC/local date, using a 8-16ms thread-safe cache
procedure FromNow(localtime: boolean = false);
/// fill fields with the supplied date
procedure FromDate(date: TDate);
/// returns true if all fields do match - ignoring DayOfWeek field value
function IsEqual(const another: TSynDate): boolean;
/// compare the stored value to a supplied value
// - returns <0 if the stored value is smaller than the supplied value,
// 0 if both are equals, and >0 if the stored value is bigger
// - DayOfWeek field value is not compared
function Compare(const another: TSynDate): integer;
{$ifdef HASINLINE}inline;{$endif}
/// fill the DayOfWeek field from the stored Year/Month/Day
// - by default, most methods will just store 0 in the DayOfWeek field
// - sunday is DayOfWeek 1, saturday is 7
procedure ComputeDayOfWeek;
/// convert the stored date into a TDate floating-point value
function ToDate: TDate;
{$ifdef HASINLINE}inline;{$endif}
/// encode the stored date as ISO-8601 text
// - returns '' if the stored date is 0 (i.e. after Clear)
function ToText(Expanded: boolean = true): RawUtf8;
end;
/// store several dates as Year/Month/Day
TSynDateDynArray = array of TSynDate;
/// a pointer to a TSynDate instance
PSynDate = ^TSynDate;
/// a cross-platform and cross-compiler TSystemTime 128-bit structure
// - FPC's TSystemTime in datih.inc does NOT match Windows TSystemTime fields!
// - also used to store a Date/Time in TSynTimeZone internal structures, or
// for fast conversion from TDateTime to its ready-to-display members
// - DayOfWeek field is not handled by most methods by default, but could be
// filled on demand via ComputeDayOfWeek
// - some Delphi revisions have trouble with "object" as own method parameters
// (e.g. IsEqual) so we force to use "record" type if possible
{$ifdef USERECORDWITHMETHODS}
TSynSystemTime = record
{$else}
TSynSystemTime = object
{$endif USERECORDWITHMETHODS}
public
/// the Year value of this timestamp
Year: word;
/// the Month value of this timstamp, in range 1..12
Month: word;
/// which day of week this Date happened
// - Sunday is DayOfWeek 1, Saturday is 7
// - DayOfWeek field is not handled by its methods by default, but could be
// filled on demand via ComputeDayOfWeek
DayOfWeek: word;
/// the Day value of this timestamp, in range 1..31
Day: word;
/// the Hour value of this timestamp, in range 0..59
Hour: word;
/// the Minute value of this timestamp, in range 0..59
Minute: word;
/// the Second value of this timestamp, in range 0..59
Second: word;
/// the MilliSecond value of this timestamp, in range 0..999
MilliSecond: word;
/// set all fields to 0
procedure Clear;
{$ifdef HASINLINE}inline;{$endif}
/// returns true if all fields are zero
function IsZero: boolean;
{$ifdef HASINLINE}inline;{$endif}
/// returns true if all fields do match
function IsEqual(const another: TSynSystemTime): boolean;
/// returns true if date fields do match (ignoring DayOfWeek and time fields)
function IsDateEqual(const date: TSynDate): boolean;
/// internal method used by TSynTimeZone
function EncodeForTimeChange(const aYear: word): TDateTime;
/// fill fields with the current UTC time, using a 8-16ms thread-safe cache
procedure FromNowUtc;
{$ifdef HASINLINE}inline;{$endif}
/// fill fields with the current Local time, using a 8-16ms thread-safe cache
procedure FromNowLocal;
{$ifdef HASINLINE}inline;{$endif}
/// fill fields with the current UTC or local time, using a 8-16ms thread-safe cache
procedure FromNow(localtime: boolean);
{$ifdef HASINLINE}inline;{$endif}
/// fill fields from the given value - but not DayOfWeek
procedure FromDateTime(const dt: TDateTime);
/// fill Year/Month/Day fields from the given value - but not DayOfWeek
// - faster than the RTL DecodeDate() function
procedure FromDate(const dt: TDateTime);
/// fill fields from the given value - but not DayOfWeek
procedure FromUnixTime(ut: TUnixTime);
/// fill fields from the given value - but not DayOfWeek
procedure FromUnixMsTime(ut: TUnixMsTime);
/// fill Hour/Minute/Second/Millisecond fields from the given number of milliseconds
// - faster than the RTL DecodeTime() function
procedure FromMS(ms: PtrUInt);
/// fill Hour/Minute/Second/Millisecond fields from the given number of seconds
// - faster than the RTL DecodeTime() function
procedure FromSec(s: PtrUInt);
/// fill Hour/Minute/Second/Millisecond fields from the given TDateTime value
// - faster than the RTL DecodeTime() function
procedure FromTime(const dt: TDateTime);
/// fill Year/Month/Day and Hour/Minute/Second fields from the given ISO-8601 text
// - returns true on success
function FromText(const iso: RawUtf8): boolean;
/// fill Year/Month/Day and Hour/Minute/Second fields from HTTP-date format
// - defined e.g. by https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1
// $ Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate
// $ Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format
// $ Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
function FromHttpDate(const httpdate: RawUtf8;
tolocaltime: boolean = false): boolean;
/// fill Year/Month/Day and Hour/Minute/Second fields from HTTP-date PUtf8Char
function FromHttpDateBuffer(P: PUtf8Char; tolocaltime: boolean): boolean;
/// encode the stored date/time as ISO-8601 text with Milliseconds
function ToText(Expanded: boolean = true; FirstTimeChar: AnsiChar = 'T';
const TZD: RawUtf8 = ''): RawUtf8;
/// append a value, expanded as Iso-8601 encoded date text
// - use 'YYYY-MM-DD' format
procedure AddIsoDate(WR: TTextWriter);
/// append a value, expanded as Iso-8601 encoded text
// - use 'YYYY-MM-DDThh:mm:ss' format with '.sss' optional milliseconds
procedure AddIsoDateTime(WR: TTextWriter; WithMS: boolean;
FirstTimeChar: AnsiChar = 'T'; const TZD: RawUtf8 = '');
/// append the stored date and time, in a log-friendly format
// - e.g. append '20110325 19241502' - with no trailing space nor tab
// - as called by TJsonWriter.AddCurrentLogTime()
procedure AddLogTime(WR: TTextWriter);
/// append the stored date and time, in apache-like format, to a TJsonWriter
// - e.g. append '19/Feb/2019:06:18:55 ' - including a trailing space
procedure AddNcsaText(WR: TTextWriter; const TZD: RawUtf8 = '');
/// append the stored date and time, in HTTP-like format, to a TJsonWriter
// - e.g. append '19/Feb/2019:06:18:55 ' - including a trailing space
procedure AddHttpDate(WR: TTextWriter; const TZD: RawUtf8 = 'GMT');
/// append the stored date and time, in apache-like format, to a memory buffer
// - e.g. "Tue, 15 Nov 1994 12:45:26 GMT" to be used as a value of
// - e.g. append '19/Feb/2019:06:18:55 ' - including a trailing space
// - returns the number of chars added to P, i.e. always 21
function ToNcsaText(P: PUtf8Char): PtrInt;
/// convert the stored date and time to its text in apache-like format
procedure ToNcsaShort(var text: shortstring; const tz: RawUtf8 = 'GMT');
/// convert the stored date and time to its text in HTTP-like format
// - i.e. "Tue, 15 Nov 1994 12:45:26 GMT" to be used as a value of
// "Date", "Expires" or "Last-Modified" HTTP header
// - handle UTC/GMT time zone by default, and allow a 'Date: ' prefix
procedure ToHttpDate(out text: RawUtf8; const tz: RawUtf8 = 'GMT';
const prefix: RawUtf8 = '');
/// convert the stored date and time to its text in HTTP-like format
procedure ToHttpDateShort(var text: shortstring; const tz: RawUtf8 = 'GMT';
const prefix: RawUtf8 = '');
/// convert the stored date and time into its Iso-8601 text, with no Milliseconds
procedure ToIsoDateTimeShort(var text: shortstring; FirstTimeChar: AnsiChar = 'T');
/// convert the stored date and time into its Iso-8601 text, with no Milliseconds
procedure ToIsoDateTime(out text: RawUtf8; FirstTimeChar: AnsiChar = 'T');
/// convert the stored date into its Iso-8601 text with no time part
procedure ToIsoDate(out text: RawUtf8);
/// convert the stored time into its Iso-8601 text with no date part nor Milliseconds
procedure ToIsoTime(out text: RawUtf8; FirstTimeChar: RawUtf8 = 'T');
/// convert the stored time into a TDateTime
function ToDateTime: TDateTime;
/// convert the stored time into a TUnixTime in seconds since UNIX Epoch
function ToUnixTime: TUnixTime;
/// copy Year/Month/DayOfWeek/Day fields to a TSynDate
procedure ToSynDate(out date: TSynDate);
{$ifdef HASINLINE}inline;{$endif}
/// convert the stored time into a timestamped local file name
// - use 'YYMMDDHHMMSS' format so year is truncated to last 2 digits,
// expecting a date > 1999 (a current date would be fine)
procedure ToFileShort(out result: TShort16);
/// fill the DayOfWeek field from the stored Year/Month/Day
// - by default, most methods will just store 0 in the DayOfWeek field
// - sunday is DayOfWeek 1, saturday is 7
procedure ComputeDayOfWeek;
{$ifdef HASINLINE}inline;{$endif}
/// compute how many days there are in the current month
function DaysInMonth: cardinal;
/// add some 1..999 milliseconds to the stored time
// - not to be used for computation, but e.g. for fast AddLogTime generation
procedure IncrementMS(ms: integer);
/// compute all fields so that they are in their natural range
// - set e.g. Second := 60 to force the next minute, or Hour := 24 so that
// it will be normalized to the next day
procedure Normalize;
/// change the system date/time with the value stored in this instance
// - i.e. call SetSystemTime/fpsettimeofday API with the stored date/time
// - will also flush the FromNowLocal/FromNowUtc cached timestamps
function ChangeOperatingSystemTime: boolean;
end;
/// pointer to our cross-platform and cross-compiler TSystemTime 128-bit structure
PSynSystemTime = ^TSynSystemTime;
{$A+}
/// internal low-level function to retrieve the cached current decoded date/time
procedure FromGlobalTime(out NewTime: TSynSystemTime; LocalTime: boolean;
tix64: Int64 = 0);
/// our own faster version of the corresponding RTL function
function TryEncodeDate(Year, Month, Day: cardinal; out Date: TDateTime): boolean;
/// our own faster version of the corresponding RTL function
function IsLeapYear(Year: cardinal): boolean;
/// compute how many days there are in a given month
function DaysInMonth(Year, Month: cardinal): cardinal; overload;
/// compute how many days there are in the month of a given date
function DaysInMonth(Date: TDateTime): cardinal; overload;
/// retrieve the current local Date, in the ISO 8601 layout, but expanded and
// ready to be displayed
function NowToString(Expanded: boolean = true; FirstTimeChar: AnsiChar = ' ';
UtcDate: boolean = false): RawUtf8;
/// retrieve the current UTC Date, in the ISO 8601 layout, but expanded and
// ready to be displayed
function NowUtcToString(Expanded: boolean = true; FirstTimeChar: AnsiChar = ' '): RawUtf8;
{$ifdef HASINLINE} inline; {$endif}
/// convert some date/time to the ISO 8601 text layout, including milliseconds
// - i.e. 'YYYY-MM-DD hh:mm:ss.sssZ' or 'YYYYMMDD hhmmss.sssZ' format
// - TZD is the ending time zone designator ('', 'Z' or '+hh:mm' or '-hh:mm')
// - see also TJsonWriter.AddDateTimeMS method
function DateTimeMSToString(DateTime: TDateTime; Expanded: boolean = true;
FirstTimeChar: AnsiChar = ' '; const TZD: RawUtf8 = 'Z'): RawUtf8; overload;
/// convert some date/time to the ISO 8601 text layout, including milliseconds
// - i.e. 'YYYY-MM-DD hh:mm:ss.sssZ' or 'YYYYMMDD hhmmss.sssZ' format
// - TZD is the ending time zone designator ('', 'Z' or '+hh:mm' or '-hh:mm')
// - see also TJsonWriter.AddDateTimeMS method
function DateTimeMSToString(HH, MM, SS, MS, Y, M, D: cardinal; Expanded: boolean;
FirstTimeChar: AnsiChar = ' '; const TZD: RawUtf8 = 'Z'): RawUtf8; overload;
/// convert some date/time to the "HTTP-date" format as defined by RFC 7231
// - i.e. "Tue, 15 Nov 1994 12:45:26 GMT" to be used as a value of
// "Date", "Expires" or "Last-Modified" HTTP header
// - if you care about timezones, dt value must be converted to UTC first
// using TSynTimeZone.LocalToUtc, or tz should be properly set
function DateTimeToHttpDate(dt: TDateTime; const tz: RawUtf8 = 'GMT'): RawUtf8; overload;
/// convert some "HTTP-date" format as defined by RFC 7231 into date/time
// - wrapper around TSynSystemTime.FromHttpDate() conversion algorithm
function HttpDateToDateTime(const httpdate: RawUtf8; var datetime: TDateTime;
tolocaltime: boolean = false): boolean; overload;
/// convert some "HTTP-date" format as defined by RFC 7231 into date/time
function HttpDateToDateTime(const httpdate: RawUtf8;
tolocaltime: boolean = false): TDateTime; overload;
/// convert some "HTTP-date" format as defined by RFC 7231 into date/time
// - wrapper around TSynSystemTime.FromHttpDate() conversion algorithm
function HttpDateToDateTimeBuffer(httpdate: PUtf8Char; var datetime: TDateTime;
tolocaltime: boolean = false): boolean;
/// convert some "HTTP-date" format as defined by RFC 7231 into UTC date/time
function HttpDateToUnixTime(const httpdate: RawUtf8): TUnixTime;
/// convert some "HTTP-date" format as defined by RFC 7231 into UTC date/time
function HttpDateToUnixTimeBuffer(httpdate: PUtf8Char): TUnixTime;
type
THttpDateNowUtc = string[39];
/// returns the current UTC timestamp as the full 'Date' HTTP header line
// - e.g. as 'Date: Tue, 15 Nov 1994 12:45:26 GMT'#13#10
// - returns as a 40-bytes shortstring to avoid a memory allocation by caller
// - use an internal cache for every second refresh
function HttpDateNowUtc: THttpDateNowUtc;
/// returns the a specified UTC timestamp in HTTP-like format
// - e.g. as 'Tue, 15 Nov 1994 12:45:26 GMT'
function UnixMSTimeUtcToHttpDate(UnixMSTime: TUnixMSTime): TShort31;
/// convert some TDateTime to a small text layout, perfect e.g. for naming a local file
// - use 'YYMMDDHHMMSS' format so year is truncated to last 2 digits, expecting
// a date > 1999 (a current date would be fine)
function DateTimeToFileShort(const DateTime: TDateTime): TShort16; overload;
{$ifdef FPC_OR_UNICODE} inline;{$endif} // Delphi 2007 is buggy as hell
/// convert some TDateTime to a small text layout, perfect e.g. for naming a local file
// - use 'YYMMDDHHMMSS' format so year is truncated to last 2 digits, expecting
// a date > 1999 (a current date would be fine)
procedure DateTimeToFileShort(const DateTime: TDateTime; out result: TShort16); overload;
/// get the current time a small text layout, perfect e.g. for naming a file
// - use 'YYMMDDHHMMSS' format so year is truncated to last 2 digits
function NowToFileShort(localtime: boolean = false): TShort16;
/// retrieve the current Time (whithout Date), in the ISO 8601 layout
// - useful for direct on screen logging e.g.
function TimeToString: RawUtf8;
const
/// used e.g. by DateTimeMSToString and TJsonWriter.AddDateTimeMS
DTMS_FMT: array[boolean] of RawUtf8 = (
'%%%%%%%%%',
'%-%-%%%:%:%.%%');
{ ************ TUnixTime / TUnixMSTime POSIX Epoch Compatible 64-bit date/time }
const
/// a contemporary, but elapsed, TUnixTime second-based value
// - corresponds to Thu, 08 Dec 2016 08:50:20 GMT
// - may be used to check for a valid just-generated Unix timestamp value
// - or used to store a timestamp without any 32-bit "Year 2038" overflow
UNIXTIME_MINIMAL = 1481187020;
/// returns UnixTimeUtc - UNIXTIME_MINIMAL so has no "Year 2038" overflow issue
function UnixTimeMinimalUtc: cardinal;
{$ifdef HASINLINE}inline;{$endif}
/// convert a second-based c-encoded time as TDateTime
// - i.e. number of seconds elapsed since Unix epoch 1/1/1970 into TDateTime
function UnixTimeToDateTime(const UnixTime: TUnixTime): TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// convert a TDateTime into a second-based c-encoded time
// - i.e. TDateTime into number of seconds elapsed since Unix epoch 1/1/1970
function DateTimeToUnixTime(const AValue: TDateTime): TUnixTime;
{$ifdef HASINLINE}inline;{$endif}
/// convert some second-based c-encoded time (from Unix epoch 1/1/1970) to
// the ISO 8601 text layout
// - use 'YYYYMMDDThhmmss' format if not Expanded
// - use 'YYYY-MM-DDThh:mm:ss' format if Expanded
function UnixTimeToString(const UnixTime: TUnixTime; Expanded: boolean = true;
FirstTimeChar: AnsiChar = 'T'): RawUtf8;
/// convert some second-based c-encoded time (from Unix epoch 1/1/1970) to
// a small text layout, perfect e.g. for naming a local file
// - use 'YYMMDDHHMMSS' format so year is truncated to last 2 digits, expecting
// a date > 1999 (a current date would be fine)
procedure UnixTimeToFileShort(const UnixTime: TUnixTime; out result: TShort16); overload;
/// convert some second-based c-encoded time (from Unix epoch 1/1/1970) to
// a small text layout, perfect e.g. for naming a local file
// - use 'YYMMDDHHMMSS' format so year is truncated to last 2 digits, expecting
// a date > 1999 (a current date would be fine)
function UnixTimeToFileShort(const UnixTime: TUnixTime): TShort16; overload;
{$ifdef FPC_OR_UNICODE} inline;{$endif} // Delphi 2007 is buggy as hell
/// convert some second-based c-encoded time to the ISO 8601 text layout, either
// as time or date elapsed period
// - this function won't add the Unix epoch 1/1/1970 offset to the timestamp
// - returns 'Thh:mm:ss' or 'YYYY-MM-DD' format, depending on the supplied value
function UnixTimePeriodToString(const UnixTime: TUnixTime;
FirstTimeChar: AnsiChar = 'T'): RawUtf8;
/// convert a millisecond-based c-encoded time (from Unix epoch 1/1/1970) as TDateTime
function UnixMSTimeToDateTime(const UnixMSTime: TUnixMSTime): TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// convert a TDateTime into a millisecond-based c-encoded time (from Unix epoch 1/1/1970)
// - if AValue is 0, will return 0 (since is likely to be an error constant)
function DateTimeToUnixMSTime(const AValue: TDateTime): TUnixMSTime;
{$ifdef HASINLINE}inline;{$endif}
/// convert some millisecond-based c-encoded time (from Unix epoch 1/1/1970) to
// the ISO 8601 text layout, including milliseconds
// - i.e. 'YYYY-MM-DDThh:mm:ss.sssZ' or 'YYYYMMDDThhmmss.sssZ' format
// - TZD is the ending time zone designator ('', 'Z' or '+hh:mm' or '-hh:mm')
function UnixMSTimeToString(const UnixMSTime: TUnixMSTime; Expanded: boolean = true;
FirstTimeChar: AnsiChar = 'T'; const TZD: RawUtf8 = ''): RawUtf8;
/// convert some milllisecond-based c-encoded time (from Unix epoch 1/1/1970) to
// a small text layout, trimming to the second resolution, perfect e.g. for
// naming a local file
// - use 'YYMMDDHHMMSS' format so year is truncated to last 2 digits, expecting
// a date > 1999 (a current date would be fine)
function UnixMSTimeToFileShort(const UnixMSTime: TUnixMSTime): TShort16;
{$ifdef FPC_OR_UNICODE} inline;{$endif} // Delphi 2007 is buggy as hell
/// convert some millisecond-based c-encoded time to the ISO 8601 text layout,
// as time or date elapsed period
// - this function won't add the Unix epoch 1/1/1970 offset to the timestamp
// - returns 'Thh:mm:ss' or 'YYYY-MM-DD' format, depending on the supplied value
function UnixMSTimePeriodToString(const UnixMSTime: TUnixMSTime;
FirstTimeChar: AnsiChar = 'T'): RawUtf8;
{ ************ TTimeLog efficient 64-bit custom date/time encoding }
type
/// pointer to a memory structure for direct access to a TTimeLog type value
PTimeLogBits = ^TTimeLogBits;
/// internal memory structure for direct access to a TTimeLog type value
// - most of the time, you should not use this object, but higher level
// TimeLogFromDateTime/TimeLogToDateTime/TimeLogNow/Iso8601ToTimeLog functions
// - since TTimeLogBits.Value is bit-oriented, you can't just add or substract
// two TTimeLog values when doing date/time computation: use a TDateTime
// temporary conversion in such case
// - TTimeLogBits.Value needs up to 40-bit precision, so features exact
// representation as JavaScript numbers (stored in a 52-bit mantissa)
{$ifdef USERECORDWITHMETHODS}
TTimeLogBits = record
{$else}
TTimeLogBits = object
{$endif USERECORDWITHMETHODS}
public
/// the bit-encoded value itself, which follows an abstract "year" of 16
// months of 32 days of 32 hours of 64 minutes of 64 seconds
// - bits 0..5 = Seconds (0..59)
// - bits 6..11 = Minutes (0..59)
// - bits 12..16 = Hours (0..23)
// - bits 17..21 = Day-1 (0..31)
// - bits 22..25 = Month-1 (0..11)
// - bits 26..40 = Year (0..9999)
Value: Int64;
/// extract the date and time content in Value into individual values
procedure Expand(out Date: TSynSystemTime);
/// convert to Iso-8601 encoded text, truncated to date/time only if needed
function Text(Expanded: boolean; FirstTimeChar: AnsiChar = 'T'): RawUtf8; overload;
/// convert to Iso-8601 encoded text, truncated to date/time only if needed
function Text(Dest: PUtf8Char; Expanded: boolean;
FirstTimeChar: AnsiChar = 'T'; QuoteChar: AnsiChar = #0): PUtf8Char; overload;
/// convert to Iso-8601 encoded text with date and time part
// - never truncate to date/time nor return '' as Text() does
function FullText(Expanded: boolean; FirstTimeChar: AnsiChar = 'T';
QuotedChar: AnsiChar = #0): RawUtf8; overload;
{$ifdef FPC}inline;{$endif} // URW1111 on Delphi 2010 and URW1136 on XE
/// convert to Iso-8601 encoded text with date and time part
// - never truncate to date/time or return '' as Text() does
function FullText(Dest: PUtf8Char; Expanded: boolean;
FirstTimeChar: AnsiChar = 'T'; QuotedChar: AnsiChar = #0): PUtf8Char; overload;
/// convert to ready-to-be displayed text
// - using i18nDateText global event, if set (e.g. by mORMoti18n.pas)
function i18nText: string;
/// extract the Time value of this date/time as floating-point TTime
function ToTime: TTime;
/// extract the Date value of this date/time as floating-point TDate
// - will return 0 if the stored value is not a valid date
function ToDate: TDate;
/// convert to a floating-point TDateTime
// - will return 0 if the stored value is not a valid date
function ToDateTime: TDateTime;
/// convert to a second-based c-encoded time (from Unix epoch 1/1/1970)
function ToUnixTime: TUnixTime;
/// convert to a millisecond-based c-encoded time (from Unix epoch 1/1/1970)
// - of course, milliseconds will be 0 due to TTimeLog second resolution
function ToUnixMSTime: TUnixMSTime;
/// fill Value from specified Date and Time
procedure From(Y, M, D, HH, MM, SS: cardinal); overload;
/// fill Value from specified TDateTime
procedure From(DateTime: TDateTime; DateOnly: boolean = false); overload;
/// fill Value from specified low-level system-specific FileAge() integer
// - i.e. 32-bit Windows bitmask local time, or 64-bit Unix UTC time
procedure FromFileDate(const FileDate: TFileAge);
/// fill Value from Iso-8601 encoded text
procedure From(P: PUtf8Char; L: integer); overload;
/// fill Value from Iso-8601 encoded text
procedure From(const S: RawUtf8); overload;
/// fill Value from specified Date/Time individual fields
procedure From(Time: PSynSystemTime); overload;
/// fill Value from second-based c-encoded time (from Unix epoch 1/1/1970)
procedure FromUnixTime(const UnixTime: TUnixTime);
/// fill Value from millisecond-based c-encoded time (from Unix epoch 1/1/1970)
// - of course, millisecond resolution will be lost during conversion
procedure FromUnixMSTime(const UnixMSTime: TUnixMSTime);
/// fill Value from current local system Date and Time
procedure FromNow;
/// fill Value from current UTC system Date and Time
// - FromNow uses local time: this function retrieves the system time
// expressed in Coordinated Universal Time (UTC)
procedure FromUtcTime;
/// get the year (e.g. 2015) of the TTimeLog value
function Year: integer;
{$ifdef HASINLINE}inline;{$endif}
/// get the month (1..12) of the TTimeLog value
function Month: integer;
{$ifdef HASINLINE}inline;{$endif}
/// get the day (1..31) of the TTimeLog value
function Day: integer;
{$ifdef HASINLINE}inline;{$endif}
/// get the hour (0..23) of the TTimeLog value
function Hour: integer;
{$ifdef HASINLINE}inline;{$endif}
/// get the minute (0..59) of the TTimeLog value
function Minute: integer;
{$ifdef HASINLINE}inline;{$endif}
/// get the second (0..59) of the TTimeLog value
function Second: integer;
{$ifdef HASINLINE}inline;{$endif}
end;
/// get TTimeLog value from current local system date and time
// - handle TTimeLog bit-encoded Int64 format
function TimeLogNow: TTimeLog;
{$ifdef HASINLINE}inline;{$endif}
/// get TTimeLog value from current UTC system Date and Time
// - handle TTimeLog bit-encoded Int64 format
function TimeLogNowUtc: TTimeLog;
{$ifdef HASINLINE}inline;{$endif}
/// get TTimeLog value from a file date and time
// - handle TTimeLog bit-encoded Int64 format
function TimeLogFromFile(const FileName: TFileName): TTimeLog;
/// get TTimeLog value from a given floating-point TDateTime
// - handle TTimeLog bit-encoded Int64 format
// - just a wrapper around PTimeLogBits(@aTime)^.From()
// - we defined such a function since TTimeLogBits(aTimeLog).From() won't change
// the aTimeLog variable content
function TimeLogFromDateTime(const DateTime: TDateTime): TTimeLog;
{$ifdef HASINLINE}inline;{$endif}
/// get TTimeLog value from a given Unix seconds since epoch timestamp
// - handle TTimeLog bit-encoded Int64 format
// - just a wrapper around PTimeLogBits(@aTime)^.FromUnixTime()
function TimeLogFromUnixTime(const UnixTime: TUnixTime): TTimeLog;
{$ifdef HASINLINE}inline;{$endif}
/// Date/Time conversion from a TTimeLog value
// - handle TTimeLog bit-encoded Int64 format
// - just a wrapper around PTimeLogBits(@Timestamp)^.ToDateTime
// - we defined such a function since TTimeLogBits(aTimeLog).ToDateTime gives an
// internall compiler error on some Delphi IDE versions (e.g. Delphi 6)
function TimeLogToDateTime(const Timestamp: TTimeLog): TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// Unix seconds since epoch timestamp conversion from a TTimeLog value
// - handle TTimeLog bit-encoded Int64 format
// - just a wrapper around PTimeLogBits(@Timestamp)^.ToUnixTime
function TimeLogToUnixTime(const Timestamp: TTimeLog): TUnixTime;
{$ifdef HASINLINE}inline;{$endif}
/// convert a Iso8601 encoded string into a TTimeLog value
// - handle TTimeLog bit-encoded Int64 format
// - use this function only for fast comparison between two Iso8601 date/time
// - conversion is faster than Iso8601ToDateTime: use only binary integer math
// - ContainsNoTime optional pointer can be set to a boolean, which will be
// set according to the layout in P (e.g. TRUE for '2012-05-26')
// - returns 0 in case of invalid input string
function Iso8601ToTimeLogPUtf8Char(P: PUtf8Char; L: integer;
ContainsNoTime: PBoolean = nil): TTimeLog;
/// convert a Iso8601 encoded string into a TTimeLog value
// - handle TTimeLog bit-encoded Int64 format
// - use this function only for fast comparison between two Iso8601 date/time
// - conversion is faster than Iso8601ToDateTime: use only binary integer math
function Iso8601ToTimeLog(const S: RawByteString): TTimeLog;
{$ifdef HASINLINE}inline;{$endif}
{ ******************* TTextDateWriter supporting date/time ISO-8601 serialization }
type
/// enhanced TTextWriter inherited class
// - in addition to TTextWriter, will handle date/time ISO-8601 serialization
TTextDateWriter = class(TTextWriter)
public
/// append a TTimeLog value, expanded as Iso-8601 encoded text
procedure AddTimeLog(Value: PInt64; QuoteChar: AnsiChar = #0);
/// append a TUnixTime value, expanded as Iso-8601 encoded text
procedure AddUnixTime(Value: PInt64; QuoteChar: AnsiChar = #0);
/// append a TUnixMSTime value, expanded as Iso-8601 encoded text
procedure AddUnixMSTime(Value: PInt64; WithMS: boolean = false;
QuoteChar: AnsiChar = #0);
/// append a TDateTime value, expanded as Iso-8601 encoded text
// - use 'YYYY-MM-DDThh:mm:ss' format (with FirstChar='T')
// - if twoDateTimeWithZ CustomOption is set, will append an ending 'Z'
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
// - if QuoteChar is not #0, it will be written before and after the date
procedure AddDateTime(Value: PDateTime; FirstChar: AnsiChar = 'T';
QuoteChar: AnsiChar = #0; WithMS: boolean = false;
AlwaysDateAndTime: boolean = false); overload;
/// append a TDateTime value, expanded as Iso-8601 encoded text
// - use 'YYYY-MM-DDThh:mm:ss' format
// - if twoDateTimeWithZ CustomOption is set, will append an ending 'Z'
// - append nothing if Value=0
// - if WithMS is TRUE, will append '.sss' for milliseconds resolution
procedure AddDateTime(const Value: TDateTime; WithMS: boolean = false); overload;
{$ifdef HASINLINE} inline; {$endif}
/// append a TDateTime value, expanded as Iso-8601 text with milliseconds
// and a specified Time Zone designator
// - i.e. 'YYYY-MM-DDThh:mm:ss.sssZ' format
// - twoDateTimeWithZ CustomOption is ignored in favor of TZD parameter
// - TZD is the ending time zone designator ('', 'Z' or '+hh:mm' or '-hh:mm')
procedure AddDateTimeMS(const Value: TDateTime; Expanded: boolean = true;
FirstTimeChar: AnsiChar = 'T'; const TZD: RawUtf8 = 'Z');
/// append the current UTC date and time, expanded as Iso-8601 encoded text
// - use 'YYYY-MM-DDThh:mm:ss' format with '.sss' optional milliseconds
// - you may set LocalTime=TRUE to write the local date and time instead
// - this method will add the supplied TZD and ignore twoDateTimeWithZ flag
procedure AddCurrentIsoDateTime(LocalTime, WithMS: boolean;
FirstTimeChar: AnsiChar = 'T'; const TZD: RawUtf8 = '');
/// append the current UTC date and time, in apache-like format
// - e.g. append '19/Feb/2019:06:18:55 +0000' - with a space before the TZD
// - you may set LocalTime=TRUE to write the local date and time instead
procedure AddCurrentNcsaLogTime(LocalTime: boolean; const TZD: RawUtf8 = '+0000');
/// append the current UTC date and time, in our HTTP format
// - e.g. append '19/Feb/2019:06:18:55 GMT' - with a space before the TZD
// - you may set LocalTime=TRUE to write the local date and time instead
procedure AddCurrentHttpTime(LocalTime: boolean; const TZD: RawUtf8 = 'GMT');
/// append the current UTC date and time, in our TSynLog human-friendly format
// - e.g. append '20110325 19241502' - with no trailing space nor TZD
// - you may set LocalTime=TRUE to write the local date and time instead
procedure AddCurrentLogTime(LocalTime: boolean);
/// append a time period as "seconds.milliseconds" content
procedure AddSeconds(MilliSeconds: QWord; Quote: AnsiChar = #0);
end;
{ ******************* TValuePUtf8Char text value wrapper record }
type
/// points to one value of raw UTF-8 content, decoded from a JSON buffer
// - used e.g. by JsonDecode() overloaded function to returns names/values
{$ifdef USERECORDWITHMETHODS}
TValuePUtf8Char = record
{$else}
TValuePUtf8Char = object
{$endif USERECORDWITHMETHODS}
public
/// a pointer to the actual UTF-8 text
Text: PUtf8Char;
/// how many UTF-8 bytes are stored in Value
Len: PtrInt;
/// convert the value into a UTF-8 string
procedure ToUtf8(var Value: RawUtf8); overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert the value into a UTF-8 string
function ToUtf8: RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert the value into a RTL string
function ToString: string;
{$ifdef HASINLINE}inline;{$endif}
/// convert the value into a signed integer
function ToInteger: PtrInt;
{$ifdef HASINLINE}inline;{$endif}
/// convert the value into an unsigned integer
function ToCardinal: PtrUInt; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert the value into an unsigned integer
function ToCardinal(Def: PtrUInt): PtrUInt; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert the value into a 64-bit signed integer
function ToInt64: Int64;
{$ifdef HASINLINE}inline;{$endif}
/// returns true if Value is either '1' or 'true'
function ToBoolean: boolean;
/// convert the value into a floating point number
function ToDouble: double;
{$ifdef HASINLINE}inline;{$endif}
/// convert the ISO-8601 text value as TDateTime
// - could have been written e.g. by DateTimeToIso8601Text()
function Iso8601ToDateTime: TDateTime;
{$ifdef HASINLINE}inline;{$endif}
/// will call IdemPropNameU() over the stored text Value
function Idem(const Value: RawUtf8): boolean;
{$ifdef HASSAFEINLINE}inline;{$endif}
end;
PValuePUtf8Char = ^TValuePUtf8Char;
/// used e.g. by JsonDecode() overloaded function to returns values
TValuePUtf8CharArray =
array[0 .. maxInt div SizeOf(TValuePUtf8Char) - 1] of TValuePUtf8Char;
PValuePUtf8CharArray = ^TValuePUtf8CharArray;
TValuePUtf8CharDynArray = array of TValuePUtf8Char;
implementation
{ ************ ISO-8601 Compatible Date/Time Text Encoding }
function Iso8601ToDateTimePUtf8Char(P: PUtf8Char; L: integer): TDateTime;
var
tmp: TDateTime; // circumvent FPC limitation
begin
Iso8601ToDateTimePUtf8CharVar(P, L, tmp);
result := tmp;
end;
function Iso8601ToDateTime(const S: RawByteString): TDateTime;
var
tmp: TDateTime; // circumvent FPC limitation
begin
Iso8601ToDateTimePUtf8CharVar(pointer(S), length(S), tmp);
result := tmp;
end;
procedure Iso8601ToDateTimePUtf8CharVar(P: PUtf8Char; L: integer;
var result: TDateTime);
var
B: cardinal;
Y, M, D, H, MI, SS, MS: cardinal;
d100: TDiv100Rec;
{$ifdef CPUX86NOTPIC}
tab: TNormTableByte absolute ConvertHexToBin;
{$else}
tab: PByteArray; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
// expect 'YYYYMMDDThhmmss[.sss]' format but handle also 'YYYY-MM-DDThh:mm:ss[.sss]'
begin
unaligned(result) := 0;
if P = nil then
exit;
if L = 0 then
L := StrLen(P);
if L < 4 then
exit; // we need 'YYYY' at least
if (P[0] = '''') and
(P[L - 1] = '''') then
begin
// in-place unquote of input - typical from SQL values
inc(P);
dec(L, 2);
if L < 4 then
exit;
end;
if P[0] = 'T' then
begin
dec(P, 8);
inc(L, 8);
end
else
begin
{$ifndef CPUX86NOTPIC}
tab := @ConvertHexToBin;
{$endif CPUX86NOTPIC}
B := tab[ord(P[0])]; // first digit
if B > 9 then
exit
else
Y := B; // fast check '0'..'9'
B := tab[ord(P[1])];
if B > 9 then
exit
else
Y := Y * 10 + B;
B := tab[ord(P[2])];
if B > 9 then
exit
else
Y := Y * 10 + B;
B := tab[ord(P[3])];
if B > 9 then
exit
else
Y := Y * 10 + B;
if P[4] in ['-', '/'] then
begin
inc(P);
dec(L);
end; // allow YYYY-MM-DD
D := 1;
if L >= 6 then
begin
// YYYYMM
M := ord(P[4]) * 10 + ord(P[5]) - (48 + 480);
if (M = 0) or
(M > 12) then
exit;
if P[6] in ['-', '/'] then
begin
inc(P);
dec(L);
end; // allow YYYY-MM-DD
if L >= 8 then
begin
// YYYYMMDD
if (L > 8) and
not (P[8] in [#0, ' ', 'T']) then
exit; // invalid date format
D := ord(P[6]) * 10 + ord(P[7]) - (48 + 480);
if (D = 0) or
(D > MonthDays[true][M]) then
exit; // worse day number to allow is for leapyear=true
end;
end
else
M := 1;
if M > 2 then // inlined EncodeDate(Y,M,D)
dec(M, 3)
else if M > 0 then
begin
inc(M, 9);
dec(Y);
end;
if Y > 9999 then
exit; // avoid integer overflow e.g. if '0000' is an invalid date
Div100(Y, d100{%H-});
unaligned(result) := (146097 * d100.d) shr 2 + (1461 * d100.m) shr 2 +
(153 * M + 2) div 5 + D;
unaligned(result) := unaligned(result) - 693900; // avoid sign issue
if L < 15 then
exit; // not enough space to retrieve the time
end;
H := ord(P[9]) * 10 + ord(P[10]) - (48 + 480);
if P[11] = ':' then
begin
inc(P);
dec(L);
end; // allow hh:mm:ss
MI := ord(P[11]) * 10 + ord(P[12]) - (48 + 480);
if P[13] = ':' then
begin
inc(P);
dec(L);
end; // allow hh:mm:ss
SS := ord(P[13]) * 10 + ord(P[14]) - (48 + 480);
if (L > 16) and
(P[15] = '.') then
begin
// one or more digits representing a decimal fraction of a second
MS := ord(P[16]) * 100 - 4800;
if L > 17 then
MS := MS {%H-}+ byte(P[17]) * 10 - 480;
if L > 18 then
MS := MS + byte(P[18]) - 48;
if MS > 1000 then
MS := 0;
end
else
MS := 0;
if (H < 24) and
(MI < 60) and
(SS < 60) then // inlined EncodeTime()
result := result + (H * (MinsPerHour * SecsPerMin * MSecsPerSec) +
MI * (SecsPerMin * MSecsPerSec) + SS * MSecsPerSec + MS) / MSecsPerDay;
end;
function Iso8601CheckAndDecode(P: PUtf8Char; L: integer;
var Value: TDateTime): boolean;
// handle 'YYYY-MM-DDThh:mm:ss[.sss]' or 'YYYY-MM-DD' or 'Thh:mm:ss[.sss]'
begin
if P = nil then
result := false
else if (((L = 9) or (L = 13)) and
(P[0] = 'T') and (P[3] = ':')) or // 'Thh:mm:ss[.sss]'
((L = 10) and
(P[4] = '-') and (P[7] = '-')) or // 'YYYY-MM-DD'
(((L = 19) or (L = 23)) and
(P[4] = '-') and (P[10] = 'T')) then
begin
Iso8601ToDateTimePUtf8CharVar(P, L, Value);
result := PInt64(@Value)^ <> 0;
end
else
result := false;
end;
function IsIso8601(P: PUtf8Char; L: integer): boolean;
begin
result := Iso8601ToTimeLogPUtf8Char(P, L) <> 0;
end;
function Iso8601ToTimePUtf8Char(P: PUtf8Char; L: integer): TDateTime;
begin
Iso8601ToTimePUtf8CharVar(P, L, result);
end;
procedure Iso8601ToTimePUtf8CharVar(P: PUtf8Char; L: integer;
var result: TDateTime);
var
H, MI, SS, MS: cardinal;
begin
if Iso8601ToTimePUtf8Char(P, L, H, MI, SS, MS) then
result := (H * (MinsPerHour * SecsPerMin * MSecsPerSec) +
MI * (SecsPerMin * MSecsPerSec) + SS * MSecsPerSec + MS) / MSecsPerDay
else
result := 0;
end;
function Iso8601ToTimePUtf8Char(P: PUtf8Char; L: integer;
var H, M, S, MS: cardinal): boolean;
begin
result := false; // error
if P = nil then
exit;
if L = 0 then
L := StrLen(P);
if L < 6 then
exit; // we need 'hhmmss' at least
H := ord(P[0]) * 10 + ord(P[1]) - (48 + 480);
if P[2] = ':' then
begin
inc(P);
dec(L);
end; // allow hh:mm:ss
M := ord(P[2]) * 10 + ord(P[3]) - (48 + 480);
if P[4] = ':' then
begin
inc(P);
dec(L);
end; // allow hh:mm:ss
S := ord(P[4]) * 10 + ord(P[5]) - (48 + 480);
if (L > 6) and
(P[6] = '.') then
begin
// one or more digits representing a decimal fraction of a second
MS := ord(P[7]) * 100 - 4800;
if L > 7 then
MS := MS {%H-}+ ord(P[8]) * 10 - 480;
if L > 8 then
MS := MS + ord(P[9]) - 48;
end
else
MS := 0;
if (H < 24) and
(M < 60) and
(S < 60) and
(MS < 1000) then
result := true;
end;
function Iso8601ToDatePUtf8Char(P: PUtf8Char; L: integer;
var Y, M, D: cardinal): boolean;
begin
result := false; // error
if P = nil then
exit;
if L = 0 then
L := StrLen(P);
if (L < 8) or
not (P[0] in ['0'..'9']) or
not (P[1] in ['0'..'9']) or
not (P[2] in ['0'..'9']) or
not (P[3] in ['0'..'9']) then
exit; // we need 'YYYYMMDD' at least
Y := ord(P[0]) * 1000 + ord(P[1]) * 100 + ord(P[2]) * 10 +
ord(P[3]) - (48 + 480 + 4800 + 48000);
if (Y < 1000) or
(Y > 2999) then
exit;
if P[4] in ['-', '/'] then
inc(P); // allow YYYY-MM-DD
M := ord(P[4]) * 10 + ord(P[5]) - (48 + 480);
if (M = 0) or
(M > 12) then
exit;
if P[6] in ['-', '/'] then
inc(P);
D := ord(P[6]) * 10 + ord(P[7]) - (48 + 480);
if (D <> 0) and
(D <= MonthDays[true][M]) then
// worse day number to allow is for leapyear=true
result := true;
end;
function IntervalTextToDateTime(Text: PUtf8Char): TDateTime;
begin
IntervalTextToDateTimeVar(Text, result);
end;
procedure IntervalTextToDateTimeVar(Text: PUtf8Char;
var result: TDateTime);
var
negative: boolean;
Time: TDateTime;
begin
// e.g. IntervalTextToDateTime('+0 06:03:20')
result := 0;
if Text = nil then
exit;
if Text^ in ['+', '-'] then
begin
negative := (Text^ = '-');
result := GetNextItemDouble(Text, ' ');
end
else
negative := false;
Iso8601ToTimePUtf8CharVar(Text, 0, Time);
if negative then
result := result - Time
else
result := result + Time;
end;
{$ifndef CPUX86NOTPIC}
procedure YearToPChar2(tab: PWordArray; Y: PtrUInt; P: PUtf8Char); inline;
var
d100: TDiv100Rec;
begin
Div100(Y, d100{%H-});
PWordArray(P)[0] := tab[d100.D];
PWordArray(P)[1] := tab[d100.M];
end;
{$endif CPUX86NOTPIC}
function DateToIso8601PChar(P: PUtf8Char; Expanded: boolean;
Y, M, D: PtrUInt): PUtf8Char;
// use 'YYYYMMDD' format if not Expanded, 'YYYY-MM-DD' format if Expanded
var
{$ifdef CPUX86NOTPIC}
tab: TWordArray absolute TwoDigitLookupW;
{$else}
tab: PWordArray;
{$endif CPUX86NOTPIC}
begin
{$ifdef CPUX86NOTPIC}
YearToPChar(Y, P);
{$else}
tab := @TwoDigitLookupW;
YearToPChar2(tab, Y, P);
{$endif CPUX86NOTPIC}
inc(P, 4);
if Expanded then
begin
P^ := '-';
inc(P);
end;
PWord(P)^ := tab[M];
inc(P, 2);
if Expanded then
begin
P^ := '-';
inc(P);
end;
PWord(P)^ := tab[D];
result := P + 2;
end;
function TimeToIso8601PChar(P: PUtf8Char; Expanded: boolean;
H, M, S, MS: PtrUInt; FirstChar: AnsiChar; WithMS: boolean): PUtf8Char;
var
{$ifdef CPUX86NOTPIC}
tab: TWordArray absolute TwoDigitLookupW;
{$else}
tab: PWordArray;
{$endif CPUX86NOTPIC}
begin
// use Thhmmss[.sss] format
if FirstChar <> #0 then
begin
P^ := FirstChar;
inc(P);
end;
{$ifndef CPUX86NOTPIC}
tab := @TwoDigitLookupW;
{$endif CPUX86NOTPIC}
PWord(P)^ := tab[H];
inc(P, 2);
if Expanded then
begin
P^ := ':';
inc(P);
end;
PWord(P)^ := tab[M];
inc(P, 2);
if Expanded then
begin
P^ := ':';
inc(P);
end;
PWord(P)^ := tab[S];
inc(P, 2);
if WithMS then
begin
{$ifdef CPUX86NOTPIC}
YearToPChar(MS, P);
{$else}
YearToPChar2(tab, MS, P);
{$endif CPUX86NOTPIC}
P^ := '.'; // override first digit
inc(P, 4);
end;
result := P;
end;
function DateToIso8601PChar(Date: TDateTime; P: PUtf8Char;
Expanded: boolean): PUtf8Char;
var
T: TSynSystemTime;
begin
// use YYYYMMDD / YYYY-MM-DD date format
T.FromDate(Date);
result := DateToIso8601PChar(P, Expanded, T.Year, T.Month, T.Day);
end;
function DateToIso8601Text(Date: TDateTime): RawUtf8;
begin
// into 'YYYY-MM-DD' date format
if Date = 0 then
result := ''
else
begin
FastSetString(result, 10);
DateToIso8601PChar(Date, pointer(result), True);
end;
end;
function TimeToIso8601PChar(Time: TDateTime; P: PUtf8Char; Expanded: boolean;
FirstChar: AnsiChar; WithMS: boolean): PUtf8Char;
var
T: TSynSystemTime;
begin
T.FromTime(Time);
result := TimeToIso8601PChar(P, Expanded, T.Hour, T.Minute, T.Second,
T.MilliSecond, FirstChar, WithMS);
end;
function DateTimeToIso8601(P: PUtf8Char; D: TDateTime; Expanded: boolean;
FirstChar: AnsiChar; WithMS: boolean; QuotedChar: AnsiChar): integer;
var
S: PUtf8Char;
begin
S := P;
if QuotedChar <> #0 then
begin
P^ := QuotedChar;
inc(P);
end;
P := DateToIso8601PChar(D, P, Expanded);
P := TimeToIso8601PChar(D, P, Expanded, FirstChar, WithMS);
if QuotedChar <> #0 then
begin
P^ := QuotedChar;
inc(P);
end;
result := P - S;
end;
function DateTimeToIso8601(D: TDateTime; Expanded: boolean;
FirstChar: AnsiChar; WithMS: boolean; QuotedChar: AnsiChar): RawUtf8;
begin
DateTimeToIso8601Var(D, Expanded, WithMS, FirstChar, QuotedChar, result);
end;
procedure DateTimeToIso8601Var(D: TDateTime; Expanded, WithMS: boolean;
FirstChar, QuotedChar: AnsiChar; var Result: RawUtf8);
var
tmp: array[0 .. 31] of AnsiChar;
begin
// D=0 is handled in DateTimeToIso8601Text()
FastSetString(result, @tmp,
DateTimeToIso8601(@tmp, D, Expanded, FirstChar, WithMS, QuotedChar));
end;
function DateTimeToIso8601Short(D: TDateTime; Expanded, WithMS: boolean;
FirstChar, QuotedChar: AnsiChar): TShort31;
begin
if D = 0 then
result[0] := #0
else
result[0] := AnsiChar(DateTimeToIso8601(
@result[1], D, Expanded, FirstChar, WithMS, QuotedChar));
end;
function DateToIso8601(Date: TDateTime; Expanded: boolean): RawUtf8;
// use YYYYMMDD / YYYY-MM-DD date format
begin
FastSetString(result, 8 + 2 * integer(Expanded));
DateToIso8601PChar(Date, pointer(result), Expanded);
end;
function DateToIso8601(Y, M, D: cardinal; Expanded: boolean): RawUtf8;
// use 'YYYYMMDD' format if not Expanded, 'YYYY-MM-DD' format if Expanded
begin
FastSetString(result, 8 + 2 * integer(Expanded));
DateToIso8601PChar(pointer(result), Expanded, Y, M, D);
end;
function TimeToIso8601(Time: TDateTime; Expanded: boolean;
FirstChar: AnsiChar; WithMS: boolean): RawUtf8;
// use Thhmmss[.sss] / Thh:mm:ss[.sss] format
begin
FastSetString(result, 7 + 2 * integer(Expanded) + 4 * integer(WithMS));
TimeToIso8601PChar(Time, pointer(result), Expanded, FirstChar, WithMS);
end;
function DaysToIso8601(Days: cardinal; Expanded: boolean): RawUtf8;
var
Y, M: cardinal;
begin
Y := 0;
while Days > 365 do
begin
dec(Days, 366);
inc(Y);
end;
M := 0;
if Days > 31 then
begin
inc(M); // years as increment, not absolute: always 365 days with no leap
while Days > MonthDays[false][M] do
begin
dec(Days, MonthDays[false][M]);
inc(M);
end;
end;
result := DateToIso8601(Y, M, Days, Expanded);
end;
function DateTimeToIso8601Text(DT: TDateTime; FirstChar: AnsiChar;
WithMS: boolean): RawUtf8;
begin
DateTimeToIso8601TextVar(DT, FirstChar, result, WithMS);
end;
procedure DateTimeToIso8601TextVar(DT: TDateTime; FirstChar: AnsiChar;
var result: RawUtf8; WithMS: boolean);
begin
if DT = 0 then
result := ''
else if frac(DT) = 0 then
result := DateToIso8601(DT, true)
else if trunc(DT) = 0 then
result := TimeToIso8601(DT, true, FirstChar, WithMS)
else
result := DateTimeToIso8601(DT, true, FirstChar, WithMS);
end;
procedure DateTimeToIso8601StringVar(DT: TDateTime; FirstChar: AnsiChar;
var result: string; WithMS: boolean);
var
tmp: RawUtf8;
begin
DateTimeToIso8601TextVar(DT, FirstChar, tmp, WithMS);
Ansi7ToString(Pointer(tmp), length(tmp), result);
end;
function DateTimeToIso8601ExpandedPChar(const Value: TDateTime; Dest: PUtf8Char;
FirstChar: AnsiChar; WithMS: boolean): PUtf8Char;
begin
if Value <> 0 then
begin
if trunc(Value) <> 0 then
Dest := DateToIso8601PChar(Value, Dest, true);
if frac(Value) <> 0 then
Dest := TimeToIso8601PChar(Value, Dest, true, FirstChar, WithMS);
end;
Dest^ := #0;
result := Dest;
end;
function VariantToDateTime2(const V: Variant; var Value: TDateTime): boolean;
var
tmp: RawUtf8; // sub-procedure to void hidden try..finally
begin
VariantToUtf8(V, tmp);
Iso8601ToDateTimePUtf8CharVar(pointer(tmp), length(tmp), Value);
result := Value <> 0;
end;
function VariantToDateTime(const V: Variant; var Value: TDateTime): boolean;
var
vd: TVarData;
vt: cardinal;
begin
vt := TVarData(V).VType;
if vt = varVariantByRef then
result := VariantToDateTime(PVariant(TVarData(V).VPointer)^, Value)
else
begin
result := true;
case vt of
varEmpty,
varNull:
Value := 0;
varDouble,
varDate:
Value := TVarData(V).VDouble;
varSingle:
Value := TVarData(V).VSingle;
varCurrency:
Value := TVarData(V).VCurrency;
{$ifdef OSWINDOWS}
varOleFileTime:
Value := FileTimeToDateTime(PFileTime(@TVarData(V).VInt64)^);
{$endif OSWINDOWS}
varString:
with TVarData(V) do
begin
Iso8601ToDateTimePUtf8CharVar(VString, length(RawUtf8(VString)), Value);
result := Value <> 0;
end;
else
if SetVariantUnRefSimpleValue(V, vd{%H-}) then
result := VariantToDateTime(variant(vd), Value)
else
result := VariantToDateTime2(V, Value);
end;
end;
end;
const
_TZs: PAnsiChar = // fast brute force search in L1 cache
#3'GMT'#4'NZDT'#1'M'#4'IDLE'#4'NZST'#3'NZT'#4'EADT'#3'GST'#3'JST'#3'CCT' +
#4'WADT'#4'WAST'#3'ZP6'#3'ZP5'#3'ZP4'#2'BT'#3'EET'#4'MEST'#4'MESZ'#3'SST' +
#3'FST'#4'CEST'#3'CET'#3'FWT'#3'MET'#4'MEWT'#3'SWT'#2'UT'#3'UTC'#1'Z' +
#3'WET'#1'A'#3'WAT'#3'BST'#2'AT'#3'ADT'#3'AST'#3'EDT'#3'EST' +
#3'CDT'#3'CST'#3'MDT'#3'MST'#3'PDT'#3'PST'#3'YDT'#3'YST'#3'HDT' +
#4'AHST'#3'CAT'#3'HST'#4'EAST'#2'NT'#4'IDLW'#1'Y';
_TZv: array[0..54] of ShortInt = (
0, 13, 12, 12, 12, 12, 11, 10, 9, 8,
8, 7, 6, 5, 4, 3, 2, 2, 2, 2,
2, 2, 1, 1, 1, 1, 1, 0, 0, 0,
0, -1, -1, -1, -2, -3, -4, -4, -5,
-5, -6, -6, -7, -7, -8, -8, -9, -9,
-10, -10, -10, -10, -11, -12, -12);
HTML_WEEK_DAYS: array[1..7] of string[3] = (
'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
HTML_MONTH_NAMES: array[1..12] of string[3] = (
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
HTML_MONTH_NAMES_32: array[0..11] of array[0..3] of AnsiChar = (
'JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN',
'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC');
function ParseTimeZone(var P: PUtf8Char; var Zone: integer): boolean;
var
z: integer;
S: PUtf8Char;
begin
result := false;
if P = nil then
exit;
P := GotoNextNotSpace(P);
S := P;
if PCardinal(S)^ and $ffffff = // most common case (always for HTTP dates)
ord('G') + ord('M') shl 8 + ord('T') shl 16 then
begin
P := GotoNextNotSpace(S + 3);
Zone := 0;
result := true;
end
else if (S^ = '+') or
(S^ = '-') then
begin
if not (S[1] in ['0'..'9']) or
not (S[2] in ['0'..'9']) or
not (S[3] in ['0'..'9']) or
not (S[4] in ['0'..'9']) then
exit;
if (S^ = '-') and
(PCardinal(S + 1)^ = $30303030) then // '-0000' for current local
Zone := TimeZoneLocalBias
else
begin
Zone := (ord(S[1]) * 10 + ord(S[2]) - (48 + 480)) * 60 +
(ord(S[3]) * 10 + ord(S[4]) - (48 + 480));
if P^ = '-' then
Zone := -Zone;
end;
P := GotoNextNotSpace(S + 5);
result := true;
end
else
begin
// TODO: enhance TSynTimeZone from mormot.core.search to parse timezones?
while (S^ in ['a'..'z', 'A'..'Z']) do
inc(S);
z := S - P;
if (z >= 1) and
(z <= 4) then
begin
z := FindShortStringListExact(@_TZs[0], high(_TZv), P, z);
if z >= 0 then
begin
Zone := integer(_TZv[z]) * 60;
P := GotoNextNotSpace(S);
result := true
end;
end;
end;
end;
function ParseTimeZone(const s: RawUtf8; var Zone: integer): boolean;
var
P: PUtf8Char;
begin
P := pointer(s);
result := ParseTimeZone(P, Zone) and
(GotoNextNotSpace(P)^ = #0);
end;
function ParseMonth(var P: PUtf8Char; var Month: word): boolean;
var
m: integer;
begin
result := false;
if P = nil then
exit;
P := GotoNextNotSpace(P);
m := PCardinal(P)^ and $dfdfdf;
if m and $00404040 <> $00404040 then // quick alphabetical guess
exit;
m := IntegerScanIndex(@HTML_MONTH_NAMES_32, 12, m);
if m < 0 then
exit;
Month := m + 1;
inc(P, 3);
if P^ = '-' then
inc(P) // e.g. '06-Nov-94'
else
P := GotoNextNotSpace(P);
result := true;
end;
function ParseMonth(const s: RawUtf8; var Month: word): boolean;
var
P: PUtf8Char;
begin
P := pointer(s);
result := ParseMonth(P, Month) and
(GotoNextNotSpace(P)^ = #0);
end;
var
AppendToTextFileSafe: TLightLock; // to make AppendToTextFile() thread-safe
function AppendToTextFile(const aLine: RawUtf8; const aFileName: TFileName;
aMaxSize: Int64; aUtcTimeStamp: boolean): boolean;
var
line: RawUtf8;
begin
result := false;
if (aFileName = '') or
(aLine = '') then
exit;
FormatUtf8(CRLF + '% %',
[NowToString(true, ' ', aUtcTimeStamp), TrimControlChars(aLine)], line);
AppendToTextFileSafe.Lock;
try
AppendToFile(line, aFileName, aMaxSize);
finally
AppendToTextFileSafe.UnLock;
end;
end;
var
LogToTextFileName: TFileName;
procedure LogToTextFile(Msg: RawUtf8);
begin
if Msg = '' then
begin
Msg := GetErrorText(GetLastError);
if Msg = '' then
exit;
end;
if LogToTextFileName = '' then
begin
AppendToTextFileSafe.Lock;
try
LogToTextFileName := ChangeFileExt(Executable.ProgramFileName, '.log');
if not IsDirectoryWritable(Executable.ProgramFilePath, [idwExcludeWinSys]) then
LogToTextFileName := GetSystemPath(spLog) + ExtractFileName(LogToTextFileName);
finally
AppendToTextFileSafe.UnLock;
end;
end;
AppendToTextFile(Msg, LogToTextFileName);
end;
{ ************ TSynDate / TSynDateTime / TSynSystemTime High-Level objects }
var
// GlobalTime[LocalTime] thread-safe cache
GlobalTime: array[boolean] of packed record
safe: TLightLock; // better than RCU
time: TSystemTime;
clock: cardinal; // avoid slower API call with 8-16ms loss of precision
_pad: array[1 .. 64 - SizeOf(TLightLock) - SizeOf(TSystemTime) - 4] of byte;
end;
procedure FromGlobalTime(out NewTime: TSynSystemTime; LocalTime: boolean;
tix64: Int64);
var
tix: cardinal;
newtimesys: TSystemTime absolute NewTime;
begin
if tix64 = 0 then
tix64 := GetTickCount64;
tix := tix64 shr 4;
with GlobalTime[LocalTime] do
if clock <> tix then // recompute every 16 ms
begin
clock := tix;
NewTime.Clear;
if LocalTime then
GetLocalTime(newtimesys)
else
GetSystemTime(newtimesys);
{$ifdef OSPOSIX}
// two TSystemTime fields are inverted in FPC datih.inc :(
tix := newtimesys.DayOfWeek;
NewTime.Day := newtimesys.Day;
NewTime.DayOfWeek := tix;
{$endif OSPOSIX}
safe.Lock;
time := newtimesys;
safe.UnLock;
end
else
begin
safe.Lock;
newtimesys := time;
safe.UnLock;
end;
end;
{ TSynDate }
procedure TSynDate.Clear;
begin
PInt64(@self)^ := 0;
end;
procedure TSynDate.SetMax;
begin
PInt64(@self)^ := $001F0000000C270F; // 9999 + 12 shl 16 + 31 shl 48
end;
function TSynDate.IsZero: boolean;
begin
result := PInt64(@self)^ = 0;
end;
function TSynDate.ParseFromText(var P: PUtf8Char): boolean;
var
L: PtrInt;
Y, M, D: cardinal;
begin
result := false;
if P = nil then
exit;
while P^ in [#9, ' '] do
inc(P);
L := 0;
while P[L] in ['0'..'9', '-', '/'] do
inc(L);
if not Iso8601ToDatePUtf8Char(P, L, Y, M, D) then
exit;
Year := Y;
Month := M;
DayOfWeek := 0;
Day := D;
inc(P, L); // move P^ just after the date
result := true;
end;
procedure TSynDate.FromNow(localtime: boolean);
var
dt: TSynSystemTime;
begin
FromGlobalTime(dt, localtime);
self := PSynDate(@dt)^; // 4 first fields of TSynSystemTime do match
end;
procedure TSynDate.FromDate(date: TDate);
var
dt: TSynSystemTime;
begin
dt.FromDate(date); // faster than RTL DecodeDate()
self := PSynDate(@dt)^;
end;
function TSynDate.IsEqual(const another: TSynDate): boolean;
begin
result := (PCardinal(@Year)^ = PCardinal(@TSynDate(another).Year)^) and
(Day = TSynDate(another).Day);
end;
function TSynDate.Compare(const another: TSynDate): integer;
begin
result := Year - TSynDate(another).Year;
if result = 0 then
begin
result := Month - TSynDate(another).Month;
if result = 0 then
result := Day - TSynDate(another).Day;
end;
end;
procedure TSynDate.ComputeDayOfWeek;
var
d: TDateTime;
i: PtrInt;
begin
if not mormot.core.datetime.TryEncodeDate(Year, Month, Day, d) then
begin
DayOfWeek := 0;
exit;
end;
i := ((trunc(d) - 1) mod 7) + 1; // sunday is day 1
if i <= 0 then
DayOfWeek := i + 7
else
DayOfWeek := i;
end;
function TSynDate.ToDate: TDate;
begin
if not mormot.core.datetime.TryEncodeDate(Year, Month, Day, PDateTime(@result)^) then
result := 0;
end;
function TSynDate.ToText(Expanded: boolean): RawUtf8;
begin
if PInt64(@self)^ = 0 then
result := ''
else
result := DateToIso8601(Year, Month, Day, Expanded);
end;
function IsLeapYear(Year: cardinal): boolean;
var
d100: TDiv100Rec;
begin
if Year and 3 = 0 then
begin
Div100(Year, d100{%H-});
result := ((d100.M <> 0) or // (Year mod 100 > 0)
(Year - ((d100.D shr 2) * 400) = 0)); // (Year mod 400 = 0))
end
else
result := false;
end;
function DaysInMonth(Year, Month: cardinal): cardinal;
begin
result := MonthDays[IsLeapYear(Year)][Month];
end;
function DaysInMonth(Date: TDateTime): cardinal;
var
dt: TSynSystemTime;
begin
dt.FromDate(Date); // faster than RTL DecodeDate()
result := dt.DaysInMonth;
end;
{ TSynSystemTime }
function TSynSystemTime.DaysInMonth: cardinal;
begin
result := MonthDays[IsLeapYear(Year)][Month];
end;
function TryEncodeDayOfWeekInMonth(
AYear, AMonth, ANthDayOfWeek, ADayOfWeek: integer;
out AValue: TDateTime): boolean;
var
LStartOfMonth, LDay: integer;
begin
// adapted from DateUtils
result := mormot.core.datetime.TryEncodeDate(AYear, AMonth, 1, AValue);
if not result then
exit;
LStartOfMonth := (DateTimeToTimestamp(AValue).date - 1) mod 7 + 1;
if LStartOfMonth <= ADayOfWeek then
dec(ANthDayOfWeek);
LDay := (ADayOfWeek - LStartOfMonth + 1) + 7 * ANthDayOfWeek;
result := mormot.core.datetime.TryEncodeDate(AYear, AMonth, LDay, AValue);
end;
function TSynSystemTime.EncodeForTimeChange(const aYear: word): TDateTime;
var
dow, d: word;
begin
if DayOfWeek = 0 then
dow := 7 // Delphi/FPC Sunday = 7
else
dow := DayOfWeek;
// Encoding the day of change
d := Day;
while not TryEncodeDayOfWeekInMonth(aYear, Month, d, dow, result) do
begin
// if Day = 5 then try it and if needed decrement to find the last
// Occurrence of the day in this month
if d = 0 then
begin
TryEncodeDayOfWeekInMonth(aYear, Month, 1, 7, result);
break;
end;
dec(d);
end;
// finally add the time when change is due
result := result + EncodeTime(Hour, Minute, Second, MilliSecond);
end;
procedure TSynSystemTime.Clear;
begin
PInt64Array(@self)[0] := 0;
PInt64Array(@self)[1] := 0;
end;
function TSynSystemTime.IsZero: boolean;
begin
result := (PInt64Array(@self)[0] = 0) and
(PInt64Array(@self)[1] = 0);
end;
function TSynSystemTime.IsEqual(const another: TSynSystemTime): boolean;
begin
result := (PInt64Array(@self)[0] = PInt64Array(@another)[0]) and
(PInt64Array(@self)[1] = PInt64Array(@another)[1]);
end;
function TSynSystemTime.IsDateEqual(const date: TSynDate): boolean;
begin
result := (PCardinal(@Year)^ = PCardinal(@TSynDate(date).Year)^) and // +Month
(Day = TSynDate(date).Day);
end;
procedure TSynSystemTime.FromNowUtc;
begin
FromGlobalTime(self, {local=}false);
end;
procedure TSynSystemTime.FromNowLocal;
begin
FromGlobalTime(self, {local=}true);
end;
procedure TSynSystemTime.FromNow(localtime: boolean);
begin
FromGlobalTime(self, localtime);
end;
procedure TSynSystemTime.FromDateTime(const dt: TDateTime);
begin
FromDate(dt);
FromTime(dt);
end;
procedure TSynSystemTime.FromUnixTime(ut: TUnixTime);
begin
FromDateTime(ut / SecsPerDay + UnixDateDelta); // via a temp TDateTime
end;
procedure TSynSystemTime.FromUnixMsTime(ut: TUnixMsTime);
begin
FromDateTime(ut / MSecsPerDay + UnixDateDelta); // via a temp TDateTime
end;
procedure TSynSystemTime.FromDate(const dt: TDateTime);
var
t, t2, t3: PtrUInt;
begin
t := Trunc(dt);
t := (t + 693900) * 4 - 1;
if PtrInt(t) >= 0 then
begin
t3 := t div 146097;
t2 := (t - t3 * 146097) and not 3;
t := PtrUInt(t2 + 3) div 1461; // PtrUInt() needed for FPC i386
Year := t3 * 100 + t;
t2 := ((t2 + 7 - t * 1461) shr 2) * 5;
t3 := PtrUInt(t2 - 3) div 153;
Day := PtrUInt(t2 + 2 - t3 * 153) div 5;
if t3 < 10 then
inc(t3, 3)
else
begin
dec(t3, 9);
inc(Year);
end;
Month := t3;
DayOfWeek := 0; // not set by default
end
else
PInt64(@Year)^ := 0;
end;
procedure TSynSystemTime.FromTime(const dt: TDateTime);
begin
FromMS(QWord(round(abs(dt) * MSecsPerDay)) mod MSecsPerDay);
end;
procedure TSynSystemTime.FromMS(ms: PtrUInt);
var
t: PtrUInt;
begin
t := ms div 3600000;
Hour := t;
dec(ms, t * 3600000);
t := ms div 60000;
Minute := t;
dec(ms, t * 60000);
t := ms div 1000;
Second := t;
dec(ms, t * 1000);
MilliSecond := ms;
end;
procedure TSynSystemTime.FromSec(s: PtrUInt);
var
t: PtrUInt;
begin
t := s div 3600;
Hour := t;
dec(s, t * 3600);
t := s div 60;
Minute := t;
dec(s, t * 60);
Second := s;
MilliSecond := 0;
end;
function TSynSystemTime.FromText(const iso: RawUtf8): boolean;
var
t: TTimeLogBits;
begin
t.From(iso);
if t.Value = 0 then
result := false
else
begin
t.Expand(self); // TTimeLogBits is faster than FromDateTime()
result := true;
end;
end;
function TSynSystemTime.FromHttpDateBuffer(
P: PUtf8Char; tolocaltime: boolean): boolean;
var
pnt: byte;
hasday: boolean;
S: PUtf8Char;
zone: integer;
v, H, MI, SS, MS: cardinal;
dt, t: TDateTime;
begin
// Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
// Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
// Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() Format
Clear;
hasday := false;
zone := maxInt; // invalid
result := false;
if P = nil then
exit;
repeat
P := GotoNextNotSpace(P);
case P^ of
'A'..'Z',
'a'..'z':
if (not hasday) or
(not ParseMonth(P, Month)) then
begin
hasday := true; // first alphabetic word is always the week day text
P := GotoNextSpace(P); // also ignore trailing '-'
end;
'0'..'9':
begin
// e.g. '1994' '08:49:37 GMT' or '6'
pnt := 0;
S := P;
repeat
inc(P);
case P^ of
'0'..'9':
;
':':
begin
inc(pnt);
if pnt = 0 then
exit;
end;
'.':
if pnt < 2 then
break;
else
break;
end;
until false;
case pnt of
0:
// e.g. '6', '94' or '2014'
begin
v := GetCardinal(S);
if v <> 0 then
begin
if (v < 32) and
(Day = 0) then
Day := v
else if (Year = 0) and
(v <= 9999) and
((v > 12) or
(Month > 0)) then
begin
if v < 32 then
inc(v, 2000)
else if v < 1000 then
inc(v, 1900);
Year := v;
end;
end;
end;
2:
// e.g. '08:49:37 GMT'
if Iso8601ToTimePUtf8Char(S, P - S, H, MI, SS, MS) then
begin
Hour := H;
Minute := MI;
Second := SS;
MilliSecond := MS;
zone := 0; // GMT by default
ParseTimeZone(P, zone);
end;
end;
if P^ = '-' then
inc(P); // e.g. '06-Nov-94'
end;
else
P := GotoNextSpace(P);
end;
until P^ in [#0, #10, #13]; // end of string or end of line (e.g. HTTP header)
if (Year = 0) or
(zone = maxInt) or
(Month = 0) then
exit;
if Day = 0 then
Day := 1 // assume first of the month if none supplied
else
begin
v := DaysInMonth;
if Day > v then
Day := v; // assume last of the month if too big supplied
end;
if tolocaltime or
(zone <> 0) then
begin
// need to apply some time zone shift
if tolocaltime then
dec(zone, TimeZoneLocalBias);
dt := ToDateTime - zone div MinsPerDay;
v := abs(zone mod MinsPerDay);
t := EncodeTime(v div 60, v mod 60, 0, 0);
if zone < 0 then
dt := dt + t
else
dt := dt - t;
FromDateTime(dt); // local TDateTime to compute time shift
end;
result := true;
end;
function TSynSystemTime.FromHttpDate(const httpdate: RawUtf8;
tolocaltime: boolean): boolean;
begin
result := (length(httpdate) >= 12) and
FromHttpDateBuffer(pointer(httpdate), tolocaltime);
end;
function TSynSystemTime.ToText(Expanded: boolean; FirstTimeChar: AnsiChar;
const TZD: RawUtf8): RawUtf8;
begin
result := DateTimeMSToString(Hour, Minute, Second, MilliSecond,
Year, Month, Day, Expanded, FirstTimeChar, TZD);
end;
procedure TSynSystemTime.AddIsoDate(WR: TTextWriter);
var
p: PUtf8Char;
begin
if WR.BEnd - WR.B <= 24 then
WR.FlushToStream;
p := WR.B + 1;
inc(WR.B, DateToIso8601PChar(p, {expanded=}true, Year, Month, Day) - p);
end;
procedure TSynSystemTime.AddIsoDateTime(WR: TTextWriter;
WithMS: boolean; FirstTimeChar: AnsiChar; const TZD: RawUtf8);
var
p: PUtf8Char;
begin
if WR.BEnd - WR.B <= 24 then
WR.FlushToStream;
p := WR.B + 1;
inc(WR.B, TimeToIso8601PChar(DateToIso8601PChar(p, true, Year, Month, Day),
true, Hour, Minute, Second, MilliSecond, FirstTimeChar, WithMS) - p);
if TZD <> '' then
WR.AddString(TZD);
end;
procedure TSynSystemTime.AddLogTime(WR: TTextWriter);
var
d100: TDiv100Rec;
p: PUtf8Char;
{$ifdef CPUX86NOTPIC}
tab: TWordArray absolute TwoDigitLookupW;
{$else}
tab: PWordArray;
{$endif CPUX86NOTPIC}
begin
if WR.BEnd - WR.B <= 18 then
WR.FlushToStream;
p := WR.B + 1;
{$ifndef CPUX86NOTPIC}
tab := @TwoDigitLookupW;
{$endif CPUX86NOTPIC}
Div100(Year, d100{%H-});
PWord(p)^ := tab[d100.D];
PWord(p + 2)^ := tab[d100.M];
PWord(p + 4)^ := tab[PtrUInt(Month)];
PWord(p + 6)^ := tab[PtrUInt(Day)];
p[8] := ' ';
PWord(p + 9)^ := tab[PtrUInt(Hour)];
PWord(p + 11)^ := tab[PtrUInt(Minute)];
PWord(p + 13)^ := tab[PtrUInt(Second)];
PWord(p + 15)^ := tab[PtrUInt(Millisecond) shr 4];
inc(WR.B, 17);
end;
procedure TSynSystemTime.AddNcsaText(WR: TTextWriter; const TZD: RawUtf8);
begin
if WR.BEnd - WR.B <= 21 then
WR.FlushToStream;
inc(WR.B, ToNcsaText(WR.B + 1));
if TZD <> '' then
WR.AddString(TZD);
end;
procedure TSynSystemTime.AddHttpDate(WR: TTextWriter; const TZD: RawUtf8);
var
tmp: shortstring;
begin
ToHttpDateShort(tmp, TZD);
WR.AddShort(tmp);
end;
function TSynSystemTime.ToNcsaText(P: PUtf8Char): PtrInt;
var
y, d100: PtrUInt;
{$ifdef CPUX86NOTPIC}
tab: TWordArray absolute TwoDigitLookupW;
{$else}
tab: PWordArray;
{$endif CPUX86NOTPIC}
begin
{$ifndef CPUX86NOTPIC}
tab := @TwoDigitLookupW;
{$endif CPUX86NOTPIC}
PWord(P)^ := tab[Day];
PCardinal(P + 2)^ := PCardinal(@HTML_MONTH_NAMES[Month])^;
P[2] := '/'; // overwrite HTML_MONTH_NAMES[][0]
P[6] := '/';
y := Year;
d100 := y div 100;
PWord(P + 7)^ := tab[d100];
PWord(P + 9)^ := tab[y - (d100 * 100)];
P[11] := ':';
PWord(P + 12)^ := tab[Hour];
P[14] := ':';
PWord(P + 15)^ := tab[Minute];
P[17] := ':';
PWord(P + 18)^ := tab[Second];
P[20] := ' ';
result := 21;
end;
procedure TSynSystemTime.ToNcsaShort(var text: shortstring; const tz: RawUtf8);
begin
text[0] := AnsiChar(ToNcsaText(@text[1]));
AppendShortAnsi7String(tz, text);
end;
procedure TSynSystemTime.ToHttpDate(out text: RawUtf8; const tz, prefix: RawUtf8);
var
tmp: shortstring;
begin
ToHttpDateShort(tmp, tz, prefix);
FastSetString(text, @tmp[1], ord(tmp[0]));
end;
procedure TSynSystemTime.ToHttpDateShort(
var text: shortstring; const tz, prefix: RawUtf8);
begin
if DayOfWeek = 0 then
PSynDate(@self)^.ComputeDayOfWeek; // first 4 fields do match
FormatShort('%%, % % % %:%:% %', [
prefix,
HTML_WEEK_DAYS[DayOfWeek],
UInt2DigitsToShortFast(Day),
HTML_MONTH_NAMES[Month],
UInt4DigitsToShort(Year),
UInt2DigitsToShortFast(Hour),
UInt2DigitsToShortFast(Minute),
UInt2DigitsToShortFast(Second),
tz], text);
end;
procedure TSynSystemTime.ToIsoDateTime(out text: RawUtf8; FirstTimeChar: AnsiChar);
var
tmp: shortstring;
begin
ToIsoDateTimeShort(tmp, FirstTimeChar);
ShortStringToAnsi7String(tmp, text);
end;
procedure TSynSystemTime.ToIsoDateTimeShort(var text: shortstring;
FirstTimeChar: AnsiChar);
begin
FormatShort('%-%-%%%:%:%', [
UInt4DigitsToShort(Year),
UInt2DigitsToShortFast(Month),
UInt2DigitsToShortFast(Day),
FirstTimeChar,
UInt2DigitsToShortFast(Hour),
UInt2DigitsToShortFast(Minute),
UInt2DigitsToShortFast(Second)], text);
end;
procedure TSynSystemTime.ToIsoDate(out text: RawUtf8);
begin
FormatUtf8('%-%-%', [
UInt4DigitsToShort(Year),
UInt2DigitsToShortFast(Month),
UInt2DigitsToShortFast(Day)], text);
end;
procedure TSynSystemTime.ToIsoTime(out text: RawUtf8; FirstTimeChar: RawUtf8);
begin
FormatUtf8('%%:%:%', [
FirstTimeChar,
UInt2DigitsToShortFast(Hour),
UInt2DigitsToShortFast(Minute),
UInt2DigitsToShortFast(Second)], text);
end;
function TSynSystemTime.ToDateTime: TDateTime;
var
time: TDateTime;
begin
if mormot.core.datetime.TryEncodeDate(Year, Month, Day, result) then
if TryEncodeTime(Hour, Minute, Second, MilliSecond, time) then
result := result + time
else
result := 0
else
result := 0;
end;
function TSynSystemTime.ToUnixTime: TUnixTime;
var
dt: TDateTime;
begin
dt := ToDateTime;
if dt = 0 then
result := 0
else
result := DateTimeToUnixTime(dt);
end;
procedure TSynSystemTime.ToSynDate(out date: TSynDate);
begin
date := PSynDate(@self)^; // first 4 fields do match
end;
procedure TSynSystemTime.ToFileShort(out result: TShort16);
var
{$ifdef CPUX86NOTPIC}
tab: TWordArray absolute TwoDigitLookupW;
{$else}
tab: PWordArray;
{$endif CPUX86NOTPIC}
begin
if IsZero then
begin
PWord(@result[0])^ := 1 + ord('0') shl 8;
exit;
end;
if Year > 1999 then
if Year < 2100 then
dec(Year, 2000)
else
Year := 99
else
Year := 0;
{$ifndef CPUX86NOTPIC}
tab := @TwoDigitLookupW;
{$endif CPUX86NOTPIC}
result[0] := #12;
PWord(@result[1])^ := tab[Year];
PWord(@result[3])^ := tab[Month];
PWord(@result[5])^ := tab[Day];
PWord(@result[7])^ := tab[Hour];
PWord(@result[9])^ := tab[Minute];
PWord(@result[11])^ := tab[Second];
end;
procedure TSynSystemTime.ComputeDayOfWeek;
begin
PSynDate(@self)^.ComputeDayOfWeek; // first 4 fields do match
end;
procedure TSynSystemTime.IncrementMS(ms: integer);
begin
inc(MilliSecond, ms);
Normalize;
end;
procedure TSynSystemTime.Normalize;
procedure NormalizeMonth;
var
thismonth: cardinal;
begin
repeat
thismonth := DaysInMonth;
if Day <= thismonth then
break;
dec(Day, thismonth);
inc(Month);
if Month > 12 then
begin
dec(Month, 12);
inc(Year);
end;
until false;
end;
begin
DayOfWeek := 0;
while MilliSecond >= 1000 do
begin
dec(MilliSecond, 1000);
inc(Second);
end;
while Second >= 60 do
begin
dec(Second, 60);
inc(Minute);
end;
while Minute >= 60 do
begin
dec(Minute, 60);
inc(Hour);
end;
while Hour >= 24 do
begin
dec(Hour, 24);
inc(Day);
end;
NormalizeMonth;
while Month > 12 do
begin
dec(Month, 12);
inc(Year);
NormalizeMonth;
end;
end;
function TSynSystemTime.ChangeOperatingSystemTime: boolean;
begin
{$ifdef OSPOSIX}
result := SetSystemTime(ToUnixTime); // fpsettimeofday
{$else}
result := SetSystemTime(PSystemTime(@self)^); // set privilege + API + notify
{$endif OSPOSIX}
FillCharFast(GlobalTime, SizeOf(GlobalTime), 0); // reset cache
end;
function TryEncodeDate(Year, Month, Day: cardinal;
out Date: TDateTime): boolean;
var
d100: TDiv100Rec;
begin
result := false;
if (Month = 0) or
(Month > 12) or
(Day = 0) or
(Year = 0) or
(Year > 10000) or
(Day > MonthDays[IsLeapYear(Year)][Month]) then
exit;
if Month > 2 then
dec(Month, 3)
else if Month > 0 then
begin
inc(Month, 9);
dec(Year);
end;
Div100(Year, d100{%H-});
Date := (146097 * d100.D) shr 2 + (1461 * d100.M) shr 2 +
(153 * Month + 2) div 5 + Day;
Date := Date - 693900; // separated to avoid sign issue
result := true;
end;
function NowToString(Expanded: boolean; FirstTimeChar: AnsiChar;
UtcDate: boolean): RawUtf8;
var
I: TTimeLogBits;
begin
if UtcDate then
I.FromUtcTime
else
I.FromNow;
result := I.Text(Expanded, FirstTimeChar);
end;
function NowUtcToString(Expanded: boolean; FirstTimeChar: AnsiChar): RawUtf8;
begin
result := NowToString(Expanded, FirstTimeChar, {UTC=}true);
end;
function DateTimeMSToString(DateTime: TDateTime; Expanded: boolean;
FirstTimeChar: AnsiChar; const TZD: RawUtf8): RawUtf8;
var
T: TSynSystemTime;
begin
// 'YYYY-MM-DD hh:mm:ss.sssZ' or 'YYYYMMDD hhmmss.sssZ' format
if DateTime = 0 then
result := ''
else
begin
T.FromDateTime(DateTime);
result := DateTimeMSToString(T.Hour, T.Minute, T.Second, T.MilliSecond,
T.Year, T.Month, T.Day, Expanded, FirstTimeChar, TZD);
end;
end;
function DateTimeMSToString(HH, MM, SS, MS, Y, M, D: cardinal; Expanded: boolean;
FirstTimeChar: AnsiChar; const TZD: RawUtf8): RawUtf8;
begin
// 'YYYY-MM-DD hh:mm:ss.sssZ' or 'YYYYMMDD hhmmss.sssZ' format
FormatUtf8(DTMS_FMT[Expanded], [
UInt4DigitsToShort(Y),
UInt2DigitsToShortFast(M),
UInt2DigitsToShortFast(D),
FirstTimeChar,
UInt2DigitsToShortFast(HH),
UInt2DigitsToShortFast(MM),
UInt2DigitsToShortFast(SS),
UInt3DigitsToShort(MS),
TZD], result);
end;
function DateTimeToHttpDate(dt: TDateTime; const tz: RawUtf8): RawUtf8;
var
T: TSynSystemTime;
begin
if dt = 0 then
result := ''
else
begin
T.FromDateTime(dt);
T.ToHttpDate(result, tz);
end;
end;
function HttpDateToDateTime(const httpdate: RawUtf8; var datetime: TDateTime;
tolocaltime: boolean): boolean; overload;
var
T: TSynSystemTime;
begin
PInt64(@datetime)^ := 0;
result := (httpdate <> '') and
T.FromHttpDate(httpdate, tolocaltime);
if result then
datetime := T.ToDateTime;
end;
function HttpDateToDateTime(const httpdate: RawUtf8;
tolocaltime: boolean): TDateTime;
begin
if not HttpDateToDateTime(httpdate, result, tolocaltime) then
result := 0;
end;
function HttpDateToDateTimeBuffer(httpdate: PUtf8Char; var datetime: TDateTime;
tolocaltime: boolean): boolean;
var
T: TSynSystemTime;
begin
PInt64(@datetime)^ := 0;
result := (httpdate <> '') and
T.FromHttpDateBuffer(httpdate, tolocaltime);
if result then
datetime := T.ToDateTime;
end;
function HttpDateToUnixTime(const httpdate: RawUtf8): TUnixTime;
var
dt: TDateTime;
begin
result := 0;
if HttpDateToDateTime(httpdate, dt, {tolocaltime=}false) then
result := DateTimeToUnixTime(dt);
end;
function HttpDateToUnixTimeBuffer(httpdate: PUtf8Char): TUnixTime;
var
dt: TDateTime;
begin
result := 0;
if HttpDateToDateTimeBuffer(httpdate, dt, {tolocaltime=}false) then
result := DateTimeToUnixTime(dt);
end;
var
_HttpDateNowUtc: record
Safe: TLightLock;
Tix: cardinal; // = GetTickCount64 div 1024 (every second)
Value: THttpDateNowUtc;
end;
function HttpDateNowUtc: THttpDateNowUtc;
var
c: cardinal;
T: TSynSystemTime;
now: shortstring; // use a temp variable for _HttpDateNowUtc atomic set
begin
c := GetTickCount64 shr 10;
with _HttpDateNowUtc do
begin
Safe.Lock;
if c <> Tix then
begin
Tix := c; // let this single thread update the Value
Safe.UnLock;
T.FromNowUtc;
T.ToHttpDateShort(now, 'GMT'#13#10, 'Date: ');
Safe.Lock;
Value := now;
end;
MoveFast(Value[0], result[0], ord(Value[0]) + 1);
Safe.UnLock;
end;
end;
function UnixMSTimeUtcToHttpDate(UnixMSTime: TUnixMSTime): TShort31;
var
T: TSynSystemTime;
begin
if UnixMSTime <= 0 then
result[0] := #0
else
begin
T.FromUnixMsTime(UnixMSTime);
T.ToHttpDateShort(result);
end;
end;
function TimeToString: RawUtf8;
var
I: TTimeLogBits;
begin
I.FromNow;
I.Value := I.Value and (1 shl (6 + 6 + 5) - 1); // keep only time
result := I.Text(true, ' ');
end;
function DateTimeToFileShort(const DateTime: TDateTime): TShort16;
begin
DateTimeToFileShort(DateTime, result);
end;
procedure DateTimeToFileShort(const DateTime: TDateTime; out result: TShort16);
var
T: TSynSystemTime;
begin
// use 'YYMMDDHHMMSS' format
if DateTime <= 0 then
PWord(@result[0])^ := 1 + ord('0') shl 8
else
begin
T.FromDate(DateTime);
T.FromTime(DateTime);
T.ToFileShort(result);
end;
end;
function NowToFileShort(localtime: boolean): TShort16;
var
T: TSynSystemTime;
begin
T.FromNow(localtime);
T.ToFileShort(result);
end;
{ ************ TUnixTime / TUnixMSTime POSIX Epoch Compatible 64-bit date/time }
function UnixTimeMinimalUtc: cardinal;
begin
result := UnixTimeUtc - UNIXTIME_MINIMAL;
end;
function UnixTimeToDateTime(const UnixTime: TUnixTime): TDateTime;
begin
result := UnixTime / SecsPerDay + UnixDateDelta;
end;
function DateTimeToUnixTime(const AValue: TDateTime): TUnixTime;
begin
result := Round((AValue - UnixDateDelta) * SecsPerDay);
end;
function UnixTimeToString(const UnixTime: TUnixTime; Expanded: boolean;
FirstTimeChar: AnsiChar): RawUtf8;
begin
// inlined UnixTimeToDateTime() + DateTimeToIso8601()
DateTimeToIso8601Var(UnixTime / SecsPerDay + UnixDateDelta,
Expanded, false, FirstTimeChar, #0, result);
end;
procedure UnixTimeToFileShort(const UnixTime: TUnixTime; out result: TShort16);
begin
// use 'YYMMDDHHMMSS' format
if UnixTime <= 0 then
PWord(@result[0])^ := 1 + ord('0') shl 8
else
DateTimeToFileShort(UnixTime / SecsPerDay + UnixDateDelta, result);
end;
function UnixTimeToFileShort(const UnixTime: TUnixTime): TShort16;
begin
UnixTimeToFileShort(UnixTime, result);
end;
function UnixMSTimeToFileShort(const UnixMSTime: TUnixMSTime): TShort16;
begin
UnixTimeToFileShort(UnixMSTime div MSecsPerSec, result);
end;
function UnixTimePeriodToString(const UnixTime: TUnixTime;
FirstTimeChar: AnsiChar): RawUtf8;
begin
if UnixTime < SecsPerDay then
result := TimeToIso8601(UnixTime / SecsPerDay, true, FirstTimeChar)
else
result := DaysToIso8601(UnixTime div SecsPerDay, true);
end;
function UnixMSTimeToDateTime(const UnixMSTime: TUnixMSTime): TDateTime;
begin
result := UnixMSTime / MSecsPerDay + UnixDateDelta;
end;
function UnixMSTimePeriodToString(const UnixMSTime: TUnixMSTime;
FirstTimeChar: AnsiChar): RawUtf8;
begin
if UnixMSTime < MSecsPerDay then
result := TimeToIso8601(UnixMSTime / MSecsPerDay, true,
FirstTimeChar, UnixMSTime < 1000)
else
result := DaysToIso8601(UnixMSTime div MSecsPerDay, true);
end;
function DateTimeToUnixMSTime(const AValue: TDateTime): TUnixMSTime;
begin
if AValue = 0 then
result := 0
else
result := Round((AValue - UnixDateDelta) * MSecsPerDay);
end;
function UnixMSTimeToString(const UnixMSTime: TUnixMSTime; Expanded: boolean;
FirstTimeChar: AnsiChar; const TZD: RawUtf8): RawUtf8;
begin
// inlined UnixMSTimeToDateTime()
if UnixMSTime <= 0 then
result := ''
else
result := DateTimeMSToString(UnixMSTime / MSecsPerDay + UnixDateDelta,
Expanded, FirstTimeChar, TZD);
end;
{ ************ TTimeLog efficient 64-bit custom date/time encoding }
// bits: S=0..5 M=6..11 H=12..16 D=17..21 M=22..25 Y=26..40
// size: S=6 M=6 H=5 D=5 M=4 Y=12
// i.e. S<64 M<64 H<32 D<32 M<16 Y<=9999: power of 2 -> use fast shl/shr
{ TTimeLogBits }
procedure TTimeLogBits.From(Y, M, D, HH, MM, SS: cardinal);
begin
inc(HH, D shl 5 + M shl 10 + Y shl 14 - (1 shl 5 + 1 shl 10));
Value := SS + MM shl 6 + Int64(HH) shl 12;
end;
procedure TTimeLogBits.From(P: PUtf8Char; L: integer);
begin
Value := Iso8601ToTimeLogPUtf8Char(P, L);
end;
procedure TTimeLogBits.Expand(out Date: TSynSystemTime);
var
V: PtrUInt;
begin
V := PPtrUint(@Value)^;
{$ifdef CPU32}
Date.Year := Value shr (6 + 6 + 5 + 5 + 4);
{$else}
Date.Year := V shr (6 + 6 + 5 + 5 + 4);
{$endif CPU32}
Date.Month := 1 + (V shr (6 + 6 + 5 + 5)) and 15;
Date.DayOfWeek := 0;
Date.Day := 1 + (V shr (6 + 6 + 5)) and 31;
Date.Hour := (V shr (6 + 6)) and 31;
Date.Minute := (V shr 6) and 63;
Date.Second := V and 63;
Date.MilliSecond := 0;
end;
procedure TTimeLogBits.From(const S: RawUtf8);
begin
Value := Iso8601ToTimeLogPUtf8Char(pointer(S), length(S));
end;
procedure TTimeLogBits.FromFileDate(const FileDate: TFileAge);
begin
{$ifdef OSWINDOWS} // already local time
with PLongRec(@FileDate)^ do
From(Hi shr 9 + 1980, Hi shr 5 and 15, Hi and 31, Lo shr 11,
Lo shr 5 and 63, Lo and 31 shl 1);
{$else}
From(mormot.core.os.FileDateToDateTime(FileDate)); // convert UTC to local
{$endif OSWINDOWS}
end;
procedure TTimeLogBits.From(DateTime: TDateTime; DateOnly: boolean);
var
T: TSynSystemTime;
V: PtrInt;
begin
T.FromDate(DateTime);
if DateOnly then
T.Hour := 0
else
T.FromTime(DateTime);
V := T.Day shl 5 + T.Month shl 10 + T.Year shl 14 - (1 shl 5 + 1 shl 10);
Value := V; // circumvent C1093 error on oldest Delphi
Value := Value shl 12;
if not DateOnly then
begin
V := T.Second + T.Minute shl 6 + T.Hour shl 12;
Value := Value + V;
end;
end;
procedure TTimeLogBits.FromUnixTime(const UnixTime: TUnixTime);
begin
From(UnixTimeToDateTime(UnixTime));
end;
procedure TTimeLogBits.FromUnixMSTime(const UnixMSTime: TUnixMSTime);
begin
From(UnixMSTimeToDateTime(UnixMSTime));
end;
procedure TTimeLogBits.From(Time: PSynSystemTime);
var
V: PtrInt;
begin
V := Time^.Hour + Time^.Day shl 5 + Time^.Month shl 10 +
Time^.Year shl 14 - (1 shl 5 + 1 shl 10);
Value := V; // circumvent C1093 error on Delphi 5
V := Time^.Second + Time^.Minute shl 6;
Value := (Value shl 12) + V;
end;
procedure TTimeLogBits.FromUtcTime;
var
now: TSynSystemTime;
begin
FromGlobalTime(now, {local=}false);
From(@now);
end;
procedure TTimeLogBits.FromNow;
var
now: TSynSystemTime;
begin
FromGlobalTime(now, {local=}true);
From(@now);
end;
function TTimeLogBits.ToTime: TTime;
var
lo: PtrUInt;
begin
{$ifdef CPU64}
lo := Value;
{$else}
lo := PCardinal(@Value)^;
{$endif CPU64}
if lo and (1 shl (6 + 6 + 5) - 1) = 0 then
result := 0
else
result := EncodeTime((lo shr (6 + 6)) and 31,
(lo shr 6) and 63,
lo and 63, 0);
end;
function TTimeLogBits.ToDate: TDate;
var
Y, lo: PtrUInt;
begin
{$ifdef CPU64}
lo := Value;
Y := lo shr (6 + 6 + 5 + 5 + 4);
{$else}
Y := Value shr (6 + 6 + 5 + 5 + 4);
lo := PCardinal(@Value)^;
{$endif CPU64}
if (Y = 0) or
not TryEncodeDate(Y,
1 + (lo shr (6 + 6 + 5 + 5)) and 15,
1 + (lo shr (6 + 6 + 5)) and 31,
TDateTime(result)) then
result := 0;
end;
function TTimeLogBits.ToDateTime: TDateTime;
var
Y, lo: PtrUInt;
Time: TDateTime;
begin
{$ifdef CPU64}
lo := Value;
Y := lo shr (6 + 6 + 5 + 5 + 4);
{$else}
Y := Value shr (6 + 6 + 5 + 5 + 4);
lo := PCardinal(@Value)^;
{$endif CPU64}
if (Y = 0) or
not TryEncodeDate(Y,
1 + (lo shr (6 + 6 + 5 + 5)) and 15,
1 + (lo shr (6 + 6 + 5)) and 31,
result) then
result := 0;
if (lo and (1 shl (6 + 6 + 5) - 1) <> 0) and
TryEncodeTime((lo shr (6 + 6)) and 31,
(lo shr 6) and 63,
lo and 63,
0,
Time) then
result := result + Time;
end;
function TTimeLogBits.Year: integer;
begin
result := Value shr (6 + 6 + 5 + 5 + 4);
end;
function TTimeLogBits.Month: integer;
begin
result := 1 + (PCardinal(@Value)^ shr (6 + 6 + 5 + 5)) and 15;
end;
function TTimeLogBits.Day: integer;
begin
result := 1 + (PCardinal(@Value)^ shr (6 + 6 + 5)) and 31;
end;
function TTimeLogBits.Hour: integer;
begin
result := (PCardinal(@Value)^ shr (6 + 6)) and 31;
end;
function TTimeLogBits.Minute: integer;
begin
result := (PCardinal(@Value)^ shr 6) and 63;
end;
function TTimeLogBits.Second: integer;
begin
result := PCardinal(@Value)^ and 63;
end;
function TTimeLogBits.ToUnixTime: TUnixTime;
var
dt: TDateTime;
begin
dt := ToDateTime;
if dt = 0 then
result := 0
else
result := DateTimeToUnixTime(dt);
end;
function TTimeLogBits.ToUnixMSTime: TUnixMSTime;
begin
result := ToUnixTime * MSecsPerSec;
end;
function TTimeLogBits.Text(Dest: PUtf8Char; Expanded: boolean;
FirstTimeChar, QuoteChar: AnsiChar): PUtf8Char;
var
lo: PtrUInt;
begin
if QuoteChar <> #0 then
begin
Dest^ := QuoteChar;
inc(Dest);
end;
if Value <> 0 then
begin
{$ifdef CPU64}
lo := Value;
{$else}
lo := PCardinal(@Value)^;
{$endif CPU64}
if lo and (1 shl (6 + 6 + 5) - 1) = 0 then
// no Time: just convert date
Dest := DateToIso8601PChar(Dest, Expanded,
{$ifdef CPU64} lo {$else} Value {$endif} shr (6 + 6 + 5 + 5 + 4),
1 + (lo shr (6 + 6 + 5 + 5)) and 15,
1 + (lo shr (6 + 6 + 5)) and 31)
else
{$ifdef CPU64}
if lo shr (6 + 6 + 5) = 0 then
{$else}
if Value shr (6 + 6 + 5) = 0 then
{$endif CPU64}
// no Date: just convert time
Dest := TimeToIso8601PChar(Dest, Expanded,
(lo shr (6 + 6)) and 31,
(lo shr 6) and 63,
lo and 63, 0, FirstTimeChar)
else
begin
// convert time and date
Dest := DateToIso8601PChar(Dest, Expanded,
{$ifdef CPU64} lo {$else} Value {$endif} shr (6+6+5+5+4),
1 + (lo shr (6 + 6 + 5 + 5)) and 15,
1 + (lo shr (6 + 6 + 5)) and 31);
Dest := TimeToIso8601PChar(Dest, Expanded,
(lo shr (6 + 6)) and 31,
(lo shr 6) and 63,
lo and 63, 0, FirstTimeChar);
end;
end;
if QuoteChar <> #0 then
begin
Dest^ := QuoteChar;
inc(Dest);
end;
result := Dest;
end;
function TTimeLogBits.Text(Expanded: boolean; FirstTimeChar: AnsiChar): RawUtf8;
var
tmp: array[0..31] of AnsiChar;
begin
if Value = 0 then
result := ''
else
FastSetString(result, @tmp,
Text(@tmp, Expanded, FirstTimeChar) - PUtf8Char(@tmp));
end;
function TTimeLogBits.FullText(Dest: PUtf8Char; Expanded: boolean;
FirstTimeChar, QuotedChar: AnsiChar): PUtf8Char;
var
lo: PtrUInt;
begin
// convert full time and date
if QuotedChar <> #0 then
begin
Dest^ := QuotedChar;
inc(Dest);
end;
lo := {$ifdef CPU64}Value{$else}PCardinal(@Value)^{$endif};
Dest := DateToIso8601PChar(Dest, Expanded,
{$ifdef CPU64}lo{$else}Value{$endif} shr (6 + 6 + 5 + 5 + 4),
1 + (lo shr (6 + 6 + 5 + 5)) and 15,
1 + (lo shr (6 + 6 + 5)) and 31);
Dest := TimeToIso8601PChar(Dest, Expanded,
(lo shr (6 + 6)) and 31,
(lo shr 6) and 63,
lo and 63, 0, FirstTimeChar);
if QuotedChar <> #0 then
begin
Dest^ := QuotedChar;
inc(Dest);
end;
result := Dest;
end;
function TTimeLogBits.FullText(Expanded: boolean;
FirstTimeChar, QuotedChar: AnsiChar): RawUtf8;
var
tmp: array[0..31] of AnsiChar;
begin
FastSetString(result, @tmp,
FullText(tmp{%H-}, Expanded, FirstTimeChar, QuotedChar) - PUtf8Char(@tmp));
end;
function TTimeLogBits.i18nText: string;
begin
if Assigned(i18nDateText) then
result := i18nDateText(Value)
else
result := {$ifdef UNICODE}Ansi7ToString{$endif}(Text(true, ' '));
end;
function TimeLogNow: TTimeLog;
begin
PTimeLogBits(@result)^.FromNow;
end;
function TimeLogNowUtc: TTimeLog;
begin
PTimeLogBits(@result)^.FromUtcTime;
end;
function TimeLogFromFile(const FileName: TFileName): TTimeLog;
var
Date: TDateTime;
begin
Date := FileAgeToDateTime(FileName);
if Date = 0 then
result := 0
else
PTimeLogBits(@result)^.From(Date);
end;
function TimeLogFromDateTime(const DateTime: TDateTime): TTimeLog;
begin
PTimeLogBits(@result)^.From(DateTime);
end;
function TimeLogFromUnixTime(const UnixTime: TUnixTime): TTimeLog;
begin
PTimeLogBits(@result)^.FromUnixTime(UnixTime);
end;
function TimeLogToDateTime(const Timestamp: TTimeLog): TDateTime;
begin
result := PTimeLogBits(@Timestamp)^.ToDateTime;
end;
function TimeLogToUnixTime(const Timestamp: TTimeLog): TUnixTime;
begin
result := PTimeLogBits(@Timestamp)^.ToUnixTime;
end;
function Iso8601ToTimeLogPUtf8Char(P: PUtf8Char; L: integer;
ContainsNoTime: PBoolean): TTimeLog;
// bits: S=0..5 M=6..11 H=12..16 D=17..21 M=22..25 Y=26..40
// i.e. S<64 M<64 H<32 D<32 M<16 Y<9999: power of 2 -> use fast shl/shr
var
V, B: PtrUInt;
{$ifdef CPUX86NOTPIC}
tab: TNormTableByte absolute ConvertHexToBin;
{$else}
tab: PByteArray; // faster on PIC/x86_64/ARM
{$endif CPUX86NOTPIC}
begin
result := 0;
if P = nil then
exit;
if L = 0 then
L := StrLen(P);
if L < 4 then
exit; // we need 'YYYY' at least
if P[0] = 'T' then
dec(P, 8)
else
begin
// 'YYYY' -> year decode
{$ifndef CPUX86NOTPIC}
tab := @ConvertHexToBin;
{$endif CPUX86NOTPIC}
V := tab[ord(P[0])];
if V > 9 then
exit;
B := tab[ord(P[1])];
if B > 9 then
exit
else
V := V * 10 + B;
B := tab[ord(P[2])];
if B > 9 then
exit
else
V := V * 10 + B;
B := tab[ord(P[3])];
if B > 9 then
exit
else
V := V * 10 + B;
result := Int64(V) shl 26; // store YYYY
if P[4] in ['-', '/'] then
begin
inc(P);
dec(L);
end; // allow YYYY-MM-DD
if L >= 6 then
begin
// YYYYMM
V := ord(P[4]) * 10 + ord(P[5]) - (48 + 480 + 1); // Month 1..12 -> 0..11
if V <= 11 then
inc(result, V shl 22)
else
begin
result := 0;
exit;
end;
if P[6] in ['-', '/'] then
begin
inc(P);
dec(L);
end; // allow YYYY-MM-DD
if L >= 8 then
begin
// YYYYMMDD
V := ord(P[6]) * 10 + ord(P[7]) - (48 + 480 + 1); // Day 1..31 -> 0..30
if (V <= 30) and
((L = 8) or
(L = 14) or
(P[8] in [#0, ' ', 'T'])) then
inc(result, V shl 17)
else
begin
result := 0;
exit;
end;
end;
end;
if L = 14 then
dec(P) // no 'T' or ' ' separator for YYYYMMDDhhmmss
else if L < 14 then
begin
// not enough place to retrieve a time
if ContainsNoTime <> nil then
ContainsNoTime^ := true;
exit;
end;
end;
if ContainsNoTime <> nil then
ContainsNoTime^ := false;
B := ord(P[9]) * 10 + ord(P[10]) - (48 + 480);
if B <= 23 then
V := B shl 12
else
exit;
if P[11] = ':' then
inc(P); // allow hh:mm:ss
B := ord(P[11]) * 10 + ord(P[12]) - (48 + 480);
if B <= 59 then
inc(V, B shl 6)
else
exit;
if P[13] = ':' then
inc(P); // allow hh:mm:ss
B := ord(P[13]) * 10 + ord(P[14]) - (48 + 480);
if B <= 59 then
inc(result, PtrUInt(V + B));
end;
function Iso8601ToTimeLog(const S: RawByteString): TTimeLog;
begin
result := Iso8601ToTimeLogPUtf8Char(pointer(S), length(S));
end;
{ ******************* TTextDateWriter supporting date/time ISO-8601 serialization }
{ TTextDateWriter }
procedure TTextDateWriter.AddTimeLog(Value: PInt64; QuoteChar: AnsiChar);
begin
if BEnd - B <= 31 then
FlushToStream;
B := PTimeLogBits(Value)^.Text(B + 1, true, 'T', QuoteChar) - 1;
end;
procedure TTextDateWriter.AddUnixTime(Value: PInt64; QuoteChar: AnsiChar);
var
DT: TDateTime;
begin
// inlined UnixTimeToDateTime()
DT := Value^ / SecsPerDay + UnixDateDelta;
AddDateTime(@DT, 'T', QuoteChar, {withms=}false, {dateandtime=}true);
end;
procedure TTextDateWriter.AddUnixMSTime(Value: PInt64; WithMS: boolean;
QuoteChar: AnsiChar);
var
DT: TDateTime;
begin
// inlined UnixMSTimeToDateTime()
DT := Value^ / MSecsPerDay + UnixDateDelta;
AddDateTime(@DT, 'T', QuoteChar, WithMS, {dateandtime=}true);
end;
procedure TTextDateWriter.AddDateTime(Value: PDateTime; FirstChar: AnsiChar;
QuoteChar: AnsiChar; WithMS: boolean; AlwaysDateAndTime: boolean);
var
T: TSynSystemTime;
begin
if (PInt64(Value)^ = 0) and
(QuoteChar = #0) then
exit;
if BEnd - B <= 26 then
FlushToStream;
inc(B);
if QuoteChar <> #0 then
B^ := QuoteChar
else
dec(B);
if PInt64(Value)^ <> 0 then
begin
inc(B);
if AlwaysDateAndTime or
(trunc(Value^) <> 0) then
begin
T.FromDate(Value^);
B := DateToIso8601PChar(B, true, T.Year, T.Month, T.Day);
end;
if AlwaysDateAndTime or
(frac(Value^) <> 0) then
begin
T.FromTime(Value^);
B := TimeToIso8601PChar(B, true, T.Hour, T.Minute, T.Second,
T.MilliSecond, FirstChar, WithMS);
end;
dec(B);
end;
if twoDateTimeWithZ in fCustomOptions then
begin
inc(B);
B^ := 'Z';
end;
if QuoteChar <> #0 then
begin
inc(B);
B^ := QuoteChar;
end;
end;
procedure TTextDateWriter.AddDateTime(const Value: TDateTime; WithMS: boolean);
begin
AddDateTime(@Value, 'T', {quotechar=}#0, WithMS, {always=}false);
end;
procedure TTextDateWriter.AddDateTimeMS(const Value: TDateTime; Expanded: boolean;
FirstTimeChar: AnsiChar; const TZD: RawUtf8);
var
T: TSynSystemTime;
begin
if Value = 0 then
exit;
T.FromDateTime(Value);
Add(DTMS_FMT[Expanded], [UInt4DigitsToShort(T.Year),
UInt2DigitsToShortFast(T.Month), UInt2DigitsToShortFast(T.Day),
FirstTimeChar, UInt2DigitsToShortFast(T.Hour),
UInt2DigitsToShortFast(T.Minute), UInt2DigitsToShortFast(T.Second),
UInt3DigitsToShort(T.MilliSecond), TZD]);
end;
procedure TTextDateWriter.AddCurrentIsoDateTime(
LocalTime, WithMS: boolean; FirstTimeChar: AnsiChar; const TZD: RawUtf8);
var
time: TSynSystemTime;
begin
time.FromNow(LocalTime);
time.AddIsoDateTime(self, WithMS, FirstTimeChar, TZD);
end;
procedure TTextDateWriter.AddCurrentLogTime(LocalTime: boolean);
var
time: TSynSystemTime;
begin
time.FromNow(LocalTime);
time.AddLogTime(self);
end;
procedure TTextDateWriter.AddCurrentNcsaLogTime(
LocalTime: boolean; const TZD: RawUtf8);
var
time: TSynSystemTime;
begin
time.FromNow(LocalTime);
time.AddNcsaText(self, TZD);
end;
procedure TTextDateWriter.AddCurrentHttpTime(LocalTime: boolean;
const TZD: RawUtf8);
var
time: TSynSystemTime;
begin
time.FromNow(LocalTime);
time.AddHttpDate(self, TZD);
end;
procedure TTextDateWriter.AddSeconds(MilliSeconds: QWord; Quote: AnsiChar);
begin
if Quote <> #0 then
Add(Quote);
MilliSeconds := MilliSeconds * 10; // convert a.bcd to a.bcd0 currency/Curr64
AddCurr64(@MilliSeconds); // fast output
if Quote <> #0 then
Add(Quote);
end;
{ ******************* TValuePUtf8Char text value wrapper record }
{ TValuePUtf8Char }
procedure TValuePUtf8Char.ToUtf8(var Value: RawUtf8);
begin
FastSetString(Value, Text, Len);
end;
function TValuePUtf8Char.ToUtf8: RawUtf8;
begin
FastSetString(result, Text, Len);
end;
function TValuePUtf8Char.ToString: string;
begin
Utf8DecodeToString(Text, Len, result);
end;
function TValuePUtf8Char.ToInteger: PtrInt;
begin
result := GetInteger(Text);
end;
function TValuePUtf8Char.ToCardinal: PtrUInt;
begin
result := GetCardinal(Text);
end;
function TValuePUtf8Char.ToCardinal(Def: PtrUInt): PtrUInt;
begin
result := GetCardinalDef(Text, Def);
end;
function TValuePUtf8Char.ToInt64: Int64;
begin
SetInt64(Text, result{%H-});
end;
function TValuePUtf8Char.ToDouble: double;
begin
result := GetExtended(Text);
end;
function TValuePUtf8Char.Iso8601ToDateTime: TDateTime;
begin
result := Iso8601ToDateTimePUtf8Char(Text, Len);
end;
function TValuePUtf8Char.Idem(const Value: RawUtf8): boolean;
begin
result := (length(Value) = Len) and
((Len = 0) or
IdemPropNameUSameLenNotNull(pointer(Value), Text, Len));
end;
function TValuePUtf8Char.ToBoolean: boolean;
begin
result := (Text <> nil) and
((PWord(Text)^ = ord('1')) or
(GetTrue(Text) = 1));
end;
procedure InitializeUnit;
begin
// as expected by ParseMonth() to call FindShortStringListExact()
assert(PtrUInt(@HTML_MONTH_NAMES[3]) - PtrUInt(@HTML_MONTH_NAMES[1]) = 8);
assert(SizeOf(GlobalTime) = 128);
// some mormot.core.text wrappers are implemented by this unit
_VariantToUtf8DateTimeToIso8601 := DateTimeToIso8601TextVar;
_Iso8601ToDateTime := Iso8601ToDateTime;
end;
procedure FinalizeUnit;
begin
end;
initialization
InitializeUnit;
finalization
FinalizeUnit;
end.