/// 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.