From 9470f5c8e9c3a0cf1bca1ff7cf45c3cb93bf3ed5 Mon Sep 17 00:00:00 2001 From: erayhanoglu Date: Thu, 8 Dec 2016 13:57:21 +0200 Subject: [PATCH] More friendly to TimeZones Added TimeZone firendly abilities. The new TDateTimeTZ record is used to store datetime values. TDateTimeTZ stores both original time and offset values. It has some helper function to convert original time to UTCTime, LocalTime or any other offset. TDateTimeTZ values can be accesed from objects and arrays. Etc. Obj.Z['field'] returns a TDateTimeTZ value. A new configuration record "JsonDeserializationConfig" has been added. It has UseLocalTime property to control how stored TDateTimeTZ values will be cast to TDateTime valus. When UseLocalTime is True (default) TDateTimeTZ values will be cast as LocalTime. Otherwise OrgTime will be used. --- Source/JsonDataObjects.pas | 397 +++++++++++++++++++++++++++++++++---- 1 file changed, 354 insertions(+), 43 deletions(-) diff --git a/Source/JsonDataObjects.pas b/Source/JsonDataObjects.pas index 9af4356..12f82ba 100644 --- a/Source/JsonDataObjects.pas +++ b/Source/JsonDataObjects.pas @@ -245,6 +245,23 @@ TJsonStringBuilder = record jdtNone, jdtString, jdtInt, jdtLong, jdtULong, jdtFloat, jdtDateTime, jdtBool, jdtArray, jdtObject ); + TDateTimeTZ = record + public + OrgTime: TDateTime; + OffsetHours: SmallInt; + OffsetMinutes: Word; + constructor Create(Value: TDateTime); overload; + constructor Create(Value: TDateTime; OffsetHours: SmallInt; OffsetMinutes: Word = 0); overload; + class operator Implicit(const Value: TDateTimeTZ): TDateTime; overload; + class operator Implicit(const Value: TDateTime): TDateTimeTZ; overload; + class operator Equal(a, b: TDateTimeTZ): Boolean; + class operator NotEqual(a, b: TDateTimeTZ): Boolean; + + function LocalTime: TDateTime; inline; + function RemoteTime(AOffsetHours: SmallInt; AOffsetMinutes: Word = 0): TDateTime; inline; + function UTCTime: TDateTime; inline; + end; + // TJsonDataValue holds the actual value PJsonDataValue = ^TJsonDataValue; TJsonDataValue = packed record @@ -259,7 +276,7 @@ TJsonDataValueRec = record jdtLong: (L: Int64); jdtULong: (U: UInt64); jdtFloat: (F: Double); - jdtDateTime: (D: TDateTime); + jdtDateTime: (D: TDateTimeTZ); jdtBool: (B: Boolean); jdtArray: (A: Pointer); // owned by TJsonDataValue jdtObject: (O: Pointer); // owned by TJsonDataValue @@ -273,6 +290,7 @@ TJsonDataValueRec = record function GetULongValue: UInt64; function GetFloatValue: Double; function GetDateTimeValue: TDateTime; + function GetDateTimeTZValue: TDateTimeTZ; function GetBoolValue: Boolean; function GetArrayValue: TJsonArray; function GetObjectValue: TJsonObject; @@ -284,6 +302,7 @@ TJsonDataValueRec = record procedure SetULongValue(const AValue: UInt64); procedure SetFloatValue(const AValue: Double); procedure SetDateTimeValue(const AValue: TDateTime); + procedure SetDateTimeTZValue(const AValue: TDateTimeTZ); procedure SetBoolValue(const AValue: Boolean); procedure SetArrayValue(const AValue: TJsonArray); procedure SetObjectValue(const AValue: TJsonObject); @@ -304,6 +323,7 @@ TJsonDataValueRec = record property ULongValue: UInt64 read GetULongValue write SetULongValue; property FloatValue: Double read GetFloatValue write SetFloatValue; property DateTimeValue: TDateTime read GetDateTimeValue write SetDateTimeValue; + property DateTimeTZValue: TDateTimeTZ read GetDateTimeTZValue write SetDateTimeTZValue; property BoolValue: Boolean read GetBoolValue write SetBoolValue; property ArrayValue: TJsonArray read GetArrayValue write SetArrayValue; property ObjectValue: TJsonObject read GetObjectValue write SetObjectValue; @@ -320,6 +340,7 @@ TJsonDataValueHelper = record function GetULongValue: UInt64; //inline; no implicit operator due to conflict with Int64 function GetFloatValue: Double; inline; function GetDateTimeValue: TDateTime; inline; + function GetDateTimeTZValue: TDateTimeTZ; inline; function GetBoolValue: Boolean; inline; function GetArrayValue: TJsonArray; inline; function GetObjectValue: TJsonObject; inline; @@ -331,6 +352,7 @@ TJsonDataValueHelper = record procedure SetULongValue(const Value: UInt64); procedure SetFloatValue(const Value: Double); procedure SetDateTimeValue(const Value: TDateTime); + procedure SetDateTimeTZValue(const Value: TDateTimeTZ); procedure SetBoolValue(const Value: Boolean); procedure SetArrayValue(const Value: TJsonArray); procedure SetObjectValue(const Value: TJsonObject); @@ -345,6 +367,7 @@ TJsonDataValueHelper = record function GetObjectULong(const Name: string): UInt64; inline; function GetObjectFloat(const Name: string): Double; inline; function GetObjectDateTime(const Name: string): TDateTime; inline; + function GetObjectDateTimeTZ(const Name: string): TDateTimeTZ; inline; function GetObjectBool(const Name: string): Boolean; inline; function GetArray(const Name: string): TJsonArray; inline; function GetObject(const Name: string): TJsonDataValueHelper; inline; @@ -355,6 +378,7 @@ TJsonDataValueHelper = record procedure SetObjectULong(const Name: string; const Value: UInt64); inline; procedure SetObjectFloat(const Name: string; const Value: Double); inline; procedure SetObjectDateTime(const Name: string; const Value: TDateTime); inline; + procedure SetObjectDateTimeTZ(const Name: string; const Value: TDateTimeTZ); inline; procedure SetObjectBool(const Name: string; const Value: Boolean); inline; procedure SetArray(const Name: string; const Value: TJsonArray); inline; procedure SetObject(const Name: string; const Value: TJsonDataValueHelper); inline; @@ -380,7 +404,9 @@ TJsonDataValueHelper = record class operator Implicit(const Value: Extended): TJsonDataValueHelper; overload; class operator Implicit(const Value: TJsonDataValueHelper): Extended; overload; class operator Implicit(const Value: TDateTime): TJsonDataValueHelper; overload; + class operator Implicit(const Value: TDateTimeTZ): TJsonDataValueHelper; overload; class operator Implicit(const Value: TJsonDataValueHelper): TDateTime; overload; + class operator Implicit(const Value: TJsonDataValueHelper): TDateTimeTZ; overload; class operator Implicit(const Value: Boolean): TJsonDataValueHelper; overload; class operator Implicit(const Value: TJsonDataValueHelper): Boolean; overload; class operator Implicit(const Value: TJsonArray): TJsonDataValueHelper; overload; @@ -398,6 +424,7 @@ TJsonDataValueHelper = record property ULongValue: UInt64 read GetULongValue write SetULongValue; property FloatValue: Double read GetFloatValue write SetFloatValue; property DateTimeValue: TDateTime read GetDateTimeValue write SetDateTimeValue; + property DateTimeTZValue: TDateTimeTZ read GetDateTimeTZValue write SetDateTimeTZValue; property BoolValue: Boolean read GetBoolValue write SetBoolValue; property ArrayValue: TJsonArray read GetArrayValue write SetArrayValue; property ObjectValue: TJsonObject read GetObjectValue write SetObjectValue; @@ -414,6 +441,7 @@ TJsonDataValueHelper = record property U[const Name: string]: UInt64 read GetObjectULong write SetObjectULong; // returns 0 if property doesn't exist, auto type-cast except for array/object property F[const Name: string]: Double read GetObjectFloat write SetObjectFloat; // returns 0 if property doesn't exist, auto type-cast except for array/object property D[const Name: string]: TDateTime read GetObjectDateTime write SetObjectDateTime; // returns 0 if property doesn't exist, auto type-cast except for array/object + property Z[const Name: string]: TDateTimeTZ read GetObjectDateTimeTZ write SetObjectDateTimeTZ; // returns 0 if property doesn't exist, auto type-cast except for array/object property B[const Name: string]: Boolean read GetObjectBool write SetObjectBool; // returns false if property doesn't exist, auto type-cast with "<>'true'" and "<>0" except for array/object // Used to auto create arrays property A[const Name: string]: TJsonArray read GetArray write SetArray; @@ -436,7 +464,7 @@ TJsonDataValueHelper = record jdtLong: (FLongValue: Int64); jdtULong: (FULongValue: UInt64); jdtFloat: (FFloatValue: Double); - jdtDateTime: (FDateTimeValue: TDateTime); + jdtDateTime: (FDateTimeTZValue: TDateTimeTZ); jdtBool: (FBoolValue: Boolean); {$IFNDEF AUTOREFCOUNT} jdtObject: (FObj: TJsonBaseObject); // used for both Array and Object @@ -458,8 +486,8 @@ TStreamInfo = record private class procedure StrToJSONStr(const AppendMethod: TWriterAppendMethod; const S: string); static; class procedure EscapeStrToJSONStr(F, P, EndP: PChar; const AppendMethod: TWriterAppendMethod); static; - class procedure DateTimeToJSONStr(const AppendMethod: TWriterAppendMethod; - const Value: TDateTime); static; + class procedure DateTimeTZToJSONStr(const AppendMethod: TWriterAppendMethod; + const Value: TDateTimeTZ); static; class procedure InternInitAndAssignItem(Dest, Source: PJsonDataValue); static; class procedure GetStreamBytes(Stream: TStream; var Encoding: TEncoding; Utf8WithoutBOM: Boolean; var StreamInfo: TStreamInfo); static; @@ -519,7 +547,9 @@ TStreamInfo = record function ToString: string; override; class function JSONToDateTime(const Value: string): TDateTime; static; + class function JSONToDateTimeTZ(const Value: string): TDateTimeTZ; static; class function DateTimeToJSON(const Value: TDateTime; UseUtcTime: Boolean): string; static; + class function DateTimeTZToJSON(const Value: TDateTimeTZ; UseUtcTime: Boolean): string; static; end; PJsonDataValueArray = ^TJsonDataValueArray; @@ -549,6 +579,7 @@ TJsonArray = class {$IFDEF USE_FAST_NEWINSTANCE}sealed{$ENDIF}(TJsonBaseObject function GetULong(Index: Integer): UInt64; inline; function GetFloat(Index: Integer): Double; inline; function GetDateTime(Index: Integer): TDateTime; inline; + function GetDateTimeTZ(Index: Integer): TDateTimeTZ; inline; function GetBool(Index: Integer): Boolean; inline; function GetArray(Index: Integer): TJsonArray; inline; function GetObject(Index: Integer): TJsonObject; inline; @@ -560,6 +591,7 @@ TJsonArray = class {$IFDEF USE_FAST_NEWINSTANCE}sealed{$ENDIF}(TJsonBaseObject procedure SetULong(Index: Integer; const Value: UInt64); inline; procedure SetFloat(Index: Integer; const Value: Double); inline; procedure SetDateTime(Index: Integer; const Value: TDateTime); inline; + procedure SetDateTimeTZ(Index: Integer; const Value: TDateTimeTZ); inline; procedure SetBool(Index: Integer; const Value: Boolean); inline; procedure SetArray(Index: Integer; const Value: TJsonArray); inline; procedure SetObject(Index: Integer; const Value: TJsonObject); inline; @@ -631,6 +663,7 @@ TJsonArray = class {$IFDEF USE_FAST_NEWINSTANCE}sealed{$ENDIF}(TJsonBaseObject property U[Index: Integer]: UInt64 read GetULong write SetULong; property F[Index: Integer]: Double read GetFloat write SetFloat; property D[Index: Integer]: TDateTime read GetDateTime write SetDateTime; + property Z[Index: Integer]: TDateTimeTZ read GetDateTimeTZ write SetDateTimeTZ; property B[Index: Integer]: Boolean read GetBool write SetBool; property A[Index: Integer]: TJsonArray read GetArray write SetArray; property O[Index: Integer]: TJsonObject read GetObject write SetObject; @@ -683,6 +716,7 @@ TJsonObject = class {$IFDEF USE_FAST_NEWINSTANCE}sealed{$ENDIF}(TJsonBaseObjec function GetULong(const Name: string): UInt64; function GetFloat(const Name: string): Double; function GetDateTime(const Name: string): TDateTime; + function GetDateTimeTZ(const Name: string): TDateTimeTZ; function GetObject(const Name: string): TJsonObject; function GetArray(const Name: string): TJsonArray; procedure SetString(const Name, Value: string); @@ -692,6 +726,7 @@ TJsonObject = class {$IFDEF USE_FAST_NEWINSTANCE}sealed{$ENDIF}(TJsonBaseObjec procedure SetULong(const Name: string; const Value: UInt64); procedure SetFloat(const Name: string; const Value: Double); procedure SetDateTime(const Name: string; const Value: TDateTime); + procedure SetDateTimeTZ(const Name: string; const Value: TDateTimeTZ); procedure SetObject(const Name: string; const Value: TJsonObject); procedure SetArray(const Name: string; const Value: TJsonArray); @@ -763,6 +798,7 @@ TJsonObject = class {$IFDEF USE_FAST_NEWINSTANCE}sealed{$ENDIF}(TJsonBaseObjec property U[const Name: string]: UInt64 read GetULong write SetULong; // returns 0 if property doesn't exist, auto type-cast except for array/object property F[const Name: string]: Double read GetFloat write SetFloat; // returns 0 if property doesn't exist, auto type-cast except for array/object property D[const Name: string]: TDateTime read GetDateTime write SetDateTime; // returns 0 if property doesn't exist, auto type-cast except for array/object + property Z[const Name: string]: TDateTimeTZ read GetDateTimeTZ write SetDateTimeTZ; // returns 0 if property doesn't exist, auto type-cast except for array/object property B[const Name: string]: Boolean read GetBool write SetBool; // returns false if property doesn't exist, auto type-cast with "<>'true'" and "<>0" except for array/object property A[const Name: string]: TJsonArray read GetArray write SetArray; // auto creates array on first access property O[const Name: string]: TJsonObject read GetObject write SetObject; // auto creates object on first access @@ -783,6 +819,10 @@ TJsonSerializationConfig = record NullConvertsToValueTypes: Boolean; end; + TJsonDeserializationConfig = record + UseLocalTime: Boolean; + end; + // Rename classes because RTL classes have the same name TJDOJsonBaseObject = TJsonBaseObject; TJDOJsonObject = TJsonObject; @@ -796,6 +836,10 @@ TJsonSerializationConfig = record NullConvertsToValueTypes: False; // If True and an object is nil/null, a convertion to String, Int, Long, Float, DateTime, Boolean will return ''/0/False ); + JsonDeserializationConfig: TJsonDeserializationConfig = ( // not thread-safe + UseLocalTime: True; + ); + implementation uses @@ -1352,6 +1396,28 @@ class function TJsonBaseObject.DateTimeToJSON(const Value: TDateTime; UseUtcTime end; {$ENDIF MSWINDOWS} +class function TJsonBaseObject.DateTimeTZToJSON(const Value: TDateTimeTZ; UseUtcTime: Boolean): string; +var + d: TDateTime; + Year, Month, Day, Hour, Minute, Second, Milliseconds: Word; +begin + if UseUtcTime then + begin + d := Value.UTCTime; + DecodeDate(d, Year, Month, Day); + DecodeTime(d, Hour, Minute, Second, MilliSeconds); + Result := Format('%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%dZ', [Year, Month, Day, Hour, Minute, Second, Milliseconds]) + end else begin + d := Value; + DecodeDate(d, Year, Month, Day); + DecodeTime(d, Hour, Minute, Second, MilliSeconds); + Result := Format('%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%d', [Year, Month, Day, Hour, Minute, Second, Milliseconds]); + if Value.OffsetHours > 0 then + Result := Result +'+' else Result := '-'; + Result := Result + Format('%.d:%.2d', [Abs(Value.OffsetHours), Abs(Value.OffsetMinutes)]); + end; +end; + function ParseDateTimePart(P: PChar; var Value: Integer; MaxLen: Integer): PChar; var V: Integer; @@ -1392,15 +1458,15 @@ function VarTypeToJsonDataType(AVarType: TVarType): TJsonDataType; end; end; -class function TJsonBaseObject.JSONToDateTime(const Value: string): TDateTime; +class function TJsonBaseObject.JSONToDateTimeTZ(const Value: string): TDateTimeTZ; var P: PChar; MSecsSince1970: Int64; Year, Month, Day, Hour, Min, Sec, MSec: Integer; OffsetHour, OffsetMin: Integer; - Sign: Double; + Sign: SmallInt; begin - Result := 0; + FillChar(Result, SizeOf(Result), 0); if Value = '' then Exit; @@ -1421,9 +1487,7 @@ class function TJsonBaseObject.JSONToDateTime(const Value: string): TDateTime; Inc(P); end; if (P[0] = ')') and (P[1] = '/') and (P[2] = #0) then - Result := UtcDateTimeToLocalDateTime(UnixDateDelta + (MSecsSince1970 / MSecsPerDay)) - else - Result := 0; // invalid format + Result.OrgTime := UnixDateDelta + (MSecsSince1970 / MSecsPerDay) end else begin @@ -1442,7 +1506,7 @@ class function TJsonBaseObject.JSONToDateTime(const Value: string): TDateTime; Min := 0; Sec := 0; MSec := 0; - Result := EncodeDate(Year, Month, Day); + Result.OrgTime := EncodeDate(Year, Month, Day); if P^ = 'T' then begin @@ -1456,8 +1520,8 @@ class function TJsonBaseObject.JSONToDateTime(const Value: string): TDateTime; if P^ = '.' then P := ParseDateTimePart(P + 1, MSec, 3); end; - Result := Result + EncodeTime(Hour, Min, Sec, MSec); - if P^ <> 'Z' then + Result.OrgTime := Result.OrgTime + EncodeTime(Hour, Min, Sec, MSec); + if (P^ <> 'Z') then begin if (P^ = '+') or (P^ = '-') then begin @@ -1471,19 +1535,22 @@ class function TJsonBaseObject.JSONToDateTime(const Value: string): TDateTime; Inc(P); ParseDateTimePart(P, OffsetMin, 2); - Result := Result + (EncodeTime(OffsetHour, OffsetMin, 0, 0) * Sign); - end - else - begin - Result := 0; // invalid format - Exit; + Result.OffsetHours := OffsetHour * Sign; + Result.OffsetMinutes := OffsetMin * Sign; end; end; - Result := UtcDateTimeToLocalDateTime(Result); end; end; end; +class function TJsonBaseObject.JSONToDateTime(const Value: string): TDateTime; +var + dtz: TDateTimeTZ; +begin + dtz := JSONToDateTimeTZ(Value); + Result := UtcDateTimeToLocalDateTime(dtz.UTCTime); +end; + {$IFDEF NEXTGEN} function Utf8StrLen(P: PByte): Integer; begin @@ -2080,7 +2147,7 @@ procedure TJsonDataValue.Clear; jdtFloat: FValue.F := 0; jdtDateTime: - FValue.D := 0; + FValue.D := TDateTimeTZ.Create(0); jdtBool: FValue.B := False; jdtArray, @@ -2225,7 +2292,7 @@ procedure TJsonDataValue.SetVariantValue(const AValue: Variant); jdtFloat: FValue.F := AValue; jdtDateTime: - FValue.D := AValue; + FValue.D := TDateTimeTZ.Create(AValue); jdtBool: FValue.B := AValue; // else @@ -2276,7 +2343,7 @@ function TJsonDataValue.GetValue: string; jdtFloat: Result := FloatToStr(FValue.F, JSONFormatSettings); jdtDateTime: - Result := TJsonBaseObject.DateTimeToJson(FValue.F, JsonSerializationConfig.UseUtcTime); + Result := TJsonBaseObject.DateTimeTZToJSON(FValue.D, JsonSerializationConfig.UseUtcTime); jdtBool: if FValue.B then Result := sTrue @@ -2339,7 +2406,9 @@ function TJsonDataValue.GetIntValue: Integer; jdtFloat: Result := Trunc(FValue.F); jdtDateTime: - Result := Trunc(FValue.D); + if JsonDeserializationConfig.UseLocalTime then + Result := Trunc(FValue.D.LocalTime) + else Result := Trunc(FValue.D.OrgTime); jdtBool: Result := Ord(FValue.B); jdtObject: @@ -2385,7 +2454,9 @@ function TJsonDataValue.GetLongValue: Int64; jdtFloat: Result := Trunc(FValue.F); jdtDateTime: - Result := Trunc(FValue.D); + if JsonDeserializationConfig.UseLocalTime then + Result := Trunc(FValue.D.LocalTime) + else Result := Trunc(FValue.D.OrgTime); jdtBool: Result := Ord(FValue.B); jdtObject: @@ -2431,7 +2502,9 @@ function TJsonDataValue.GetULongValue: UInt64; jdtFloat: Result := Trunc(FValue.F); jdtDateTime: - Result := Trunc(FValue.D); + if JsonDeserializationConfig.UseLocalTime then + Result := Trunc(FValue.D.LocalTime) + else Result := Trunc(FValue.D.OrgTime); jdtBool: Result := Ord(FValue.B); jdtObject: @@ -2521,7 +2594,9 @@ function TJsonDataValue.GetDateTimeValue: TDateTime; jdtFloat: Result := FValue.F; jdtDateTime: - Result := FValue.D; + if JsonDeserializationConfig.UseLocalTime then + Result := FValue.D.LocalTime + else Result := FValue.D.OrgTime; jdtBool: Result := Ord(FValue.B); jdtObject: @@ -2536,7 +2611,58 @@ function TJsonDataValue.GetDateTimeValue: TDateTime; end; end; +function TJsonDataValue.GetDateTimeTZValue: TDateTimeTZ; +begin + case FTyp of + jdtNone: + Result := TDateTimeTZ.Create(0); + jdtString: + Result := TJsonBaseObject.JSONToDateTimeTZ(string(FValue.S)); + jdtInt: + Result := TDateTimeTZ.Create(FValue.I); + jdtLong: + Result := TDateTimeTZ.Create(FValue.L); + jdtULong: + Result := TDateTimeTZ.Create(FValue.U); + jdtFloat: + Result := TDateTimeTZ.Create(FValue.F); + jdtDateTime: + Result := FValue.D; + jdtBool: + Result := TDateTimeTZ.Create(Ord(FValue.B)); + jdtObject: + begin + if not JsonSerializationConfig.NullConvertsToValueTypes or (FValue.O <> nil) then + TypeCastError(jdtDateTime); + Result := TDateTimeTZ.Create(0); + end; + else + TypeCastError(jdtDateTime); + Result := TDateTimeTZ.Create(0); + end; +end; + procedure TJsonDataValue.SetDateTimeValue(const AValue: TDateTime); +var + LTyp: TJsonDataType; + old: TDateTime; +begin + LTyp := FTyp; + if JsonDeserializationConfig.UseLocalTime then + old := FValue.D.LocalTime + else old := FValue.D.OrgTime; + if (LTyp <> jdtDateTime) or (AValue <> old) then + begin + if LTyp <> jdtNone then + Clear; + FTyp := jdtDateTime; + if JsonDeserializationConfig.UseLocalTime then + FValue.D := TDateTimeTZ.Create(AValue) + else FValue.D.OrgTime := AValue; + end; +end; + +procedure TJsonDataValue.SetDateTimeTZValue(const AValue: TDateTimeTZ); var LTyp: TJsonDataType; begin @@ -2566,7 +2692,9 @@ function TJsonDataValue.GetBoolValue: Boolean; jdtFloat: Result := FValue.F <> 0; jdtDateTime: - Result := FValue.D <> 0; + if JsonDeserializationConfig.UseLocalTime then + Result := FValue.D.LocalTime <> 0 + else Result := FValue.D.OrgTime <> 0; jdtBool: Result := FValue.B; jdtObject: @@ -2724,7 +2852,7 @@ procedure TJsonDataValue.InternToJSON(var Writer: TJsonOutputWriter); jdtFloat: Writer.AppendValue(Buffer, DoubleToText(Buffer, FValue.F)); jdtDateTime: - TJsonBaseObject.DateTimeToJSONStr(Writer.AppendStrValue, FValue.D); // do the conversion in a function to prevent the compiler from creating a string intermediate in this method + TJsonBaseObject.DateTimeTZToJSONStr(Writer.AppendStrValue, FValue.D); // do the conversion in a function to prevent the compiler from creating a string intermediate in this method jdtBool: if FValue.B then Writer.AppendValue(sTrue) @@ -2803,11 +2931,11 @@ class procedure TJsonBaseObject.StrToJSONStr(const AppendMethod: TWriterAppendMe AppendMethod(nil, 0); end; -class procedure TJsonBaseObject.DateTimeToJSONStr(const AppendMethod: TWriterAppendMethod; const Value: TDateTime); +class procedure TJsonBaseObject.DateTimeTZToJSONStr(const AppendMethod: TWriterAppendMethod; const Value: TDateTimeTZ); var S: string; begin - S := TJsonBaseObject.DateTimeToJSON(Value, JsonSerializationConfig.UseUtcTime); + S := TJsonBaseObject.DateTimeTZToJSON(Value, JsonSerializationConfig.UseUtcTime); // StrToJSONStr isn't necessary because the date-time string doesn't contain any char // that must be escaped. AppendMethod(PChar(S), Length(S)); @@ -3645,6 +3773,15 @@ function TJsonArray.GetDateTime(Index: Integer): TDateTime; Result := FItems[Index].DateTimeValue; end; +function TJsonArray.GetDateTimeTZ(Index: Integer): TDateTimeTZ; +begin + {$IFDEF CHECK_ARRAY_INDEX} + if Cardinal(Index) >= Cardinal(FCount) then + RaiseListError(Index); + {$ENDIF CHECK_ARRAY_INDEX} + Result := FItems[Index].DateTimeTZValue; +end; + function TJsonArray.GetItem(Index: Integer): PJsonDataValue; begin {$IFDEF CHECK_ARRAY_INDEX} @@ -3954,6 +4091,15 @@ procedure TJsonArray.SetDateTime(Index: Integer; const Value: TDateTime); FItems[Index].DateTimeValue := Value; end; +procedure TJsonArray.SetDateTimeTZ(Index: Integer; const Value: TDateTimeTZ); +begin + {$IFDEF CHECK_ARRAY_INDEX} + if Cardinal(Index) >= Cardinal(FCount) then + RaiseListError(Index); + {$ENDIF CHECK_ARRAY_INDEX} + FItems[Index].DateTimeTZValue := Value; +end; + procedure TJsonArray.SetBool(Index: Integer; const Value: Boolean); begin {$IFDEF CHECK_ARRAY_INDEX} @@ -4345,6 +4491,16 @@ function TJsonObject.GetDateTime(const Name: string): TDateTime; Result := 0; end; +function TJsonObject.GetDateTimeTZ(const Name: string): TDateTimeTZ; +var + Item: PJsonDataValue; +begin + if FindItem(Name, Item) then + Result := Item.DateTimeTZValue + else + Result := 0; +end; + function TJsonObject.GetObject(const Name: string): TJsonObject; var Item: PJsonDataValue; @@ -4406,6 +4562,11 @@ procedure TJsonObject.SetDateTime(const Name: string; const Value: TDateTime); RequireItem(Name).DateTimeValue := Value; end; +procedure TJsonObject.SetDateTimeTZ(const Name: string; const Value: TDateTimeTZ); +begin + RequireItem(Name).DateTimeTZValue := Value; +end; + procedure TJsonObject.SetObject(const Name: string; const Value: TJsonObject); begin RequireItem(Name).ObjectValue := Value; @@ -6719,7 +6880,7 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result := FloatToStr(Value.FData.FFloatValue, JSONFormatSettings); jdtDateTime: - Result := TJsonBaseObject.DateTimeToJSON(Value.FData.FDateTimeValue, JsonSerializationConfig.UseUtcTime); + Result := TJsonBaseObject.DateTimeTZToJSON(Value.FData.FDateTimeTZValue, JsonSerializationConfig.UseUtcTime); jdtBool: if Value.FData.FBoolValue then Result := sTrue @@ -6760,7 +6921,9 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result := Trunc(Value.FData.FFloatValue); jdtDateTime: - Result := Trunc(Value.FData.FDateTimeValue); + if JsonDeserializationConfig.UseLocalTime then + Result := Trunc(Value.FData.FDateTimeTZValue.LocalTime) + else Result := Trunc(Value.FData.FDateTimeTZValue.OrgTime); jdtBool: Result := Ord(Value.FData.FBoolValue); else @@ -6798,7 +6961,9 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result := Trunc(Value.FData.FFloatValue); jdtDateTime: - Result := Trunc(Value.FData.FDateTimeValue); + if JsonDeserializationConfig.UseLocalTime then + Result := Trunc(Value.FData.FDateTimeTZValue.LocalTime) + else Result := Trunc(Value.FData.FDateTimeTZValue.OrgTime); jdtBool: Result := Ord(Value.FData.FBoolValue); else @@ -6874,7 +7039,9 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result := Value.FData.FFloatValue; jdtDateTime: - Result := Value.FData.FDateTimeValue; + if JsonDeserializationConfig.UseLocalTime then + Result := Value.FData.FDateTimeTZValue.LocalTime + else Result := Value.FData.FDateTimeTZValue.OrgTime; jdtBool: Result := Ord(Value.FData.FBoolValue); else @@ -6912,7 +7079,9 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result := Value.FData.FFloatValue; jdtDateTime: - Result := Value.FData.FDateTimeValue; + if JsonDeserializationConfig.UseLocalTime then + Result := Value.FData.FDateTimeTZValue.LocalTime + else Result := Value.FData.FDateTimeTZValue.OrgTime; jdtBool: Result := Ord(Value.FData.FBoolValue); else @@ -6930,7 +7099,20 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF Result.FData.FObj := nil; {$ENDIF AUTOREFCOUNT} Result.FData.FTyp := jdtDateTime; - Result.FData.FDateTimeValue := Value; + Result.FData.FDateTimeTZValue := TDateTimeTZ.Create(Value); +end; + +class operator TJsonDataValueHelper.Implicit(const Value: TDateTimeTZ): TJsonDataValueHelper; +begin + Result.FData.FName := ''; + Result.FData.FNameResolver := nil; + Result.FData.FIntern := nil; + {$IFDEF AUTOREFCOUNT} + if Result.FData.FObj <> nil then + Result.FData.FObj := nil; + {$ENDIF AUTOREFCOUNT} + Result.FData.FTyp := jdtDateTime; + Result.FData.FDateTimeTZValue := Value; end; class operator TJsonDataValueHelper.Implicit(const Value: TJsonDataValueHelper): TDateTime; @@ -6950,7 +7132,9 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result := Value.FData.FFloatValue; jdtDateTime: - Result := Value.FData.FDateTimeValue; + if JsonDeserializationConfig.UseLocalTime then + Result := Value.FData.FDateTimeTZValue.LocalTime + else Result := Value.FData.FDateTimeTZValue.OrgTime; jdtBool: Result := Ord(Value.FData.FBoolValue); else @@ -6958,6 +7142,31 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF end; end; +class operator TJsonDataValueHelper.Implicit(const Value: TJsonDataValueHelper): TDateTimeTZ; +begin + if Value.FData.FIntern <> nil then + Result := Value.FData.FIntern.DateTimeTZValue + else + case Value.FData.FTyp of + jdtString: + Result := TJsonBaseObject.JSONToDateTimeTZ(Value.FData.FValue); + jdtInt: + Result := TDateTimeTZ.Create(Value.FData.FIntValue); + jdtLong: + Result := TDateTimeTZ.Create(Value.FData.FLongValue); + jdtULong: + Result := TDateTimeTZ.Create(Value.FData.FULongValue); + jdtFloat: + Result := TDateTimeTZ.Create(Value.FData.FFloatValue); + jdtDateTime: + Result := TDateTimeTZ.Create(Value.FData.FDateTimeTZValue); + jdtBool: + Result := TDateTimeTZ.Create(Ord(Value.FData.FBoolValue)); + else + Result := TDateTimeTZ.Create(0); + end; +end; + class operator TJsonDataValueHelper.Implicit(const Value: Boolean): TJsonDataValueHelper; begin Result.FData.FName := ''; @@ -6988,7 +7197,9 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result := Value.FData.FFloatValue <> 0; jdtDateTime: - Result := Value.FData.FDateTimeValue <> 0; + if JsonDeserializationConfig.UseLocalTime then + Result := Value.FData.FDateTimeTZValue.LocalTime <> 0 + else Result := Value.FData.FDateTimeTZValue.OrgTime <> 0; jdtBool: Result := Value.FData.FBoolValue; else @@ -7084,7 +7295,9 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result := Value.FData.FFloatValue; jdtDateTime: - Result := Value.FData.FDateTimeValue; + if JsonDeserializationConfig.UseLocalTime then + Result := Value.FData.FDateTimeTZValue.LocalTime + else Result := Value.FData.FDateTimeTZValue.OrgTime; jdtBool: Result := Value.FData.FBoolValue; jdtArray: @@ -7127,7 +7340,7 @@ procedure TStringJsonReader.LexIdent(P: PChar{$IFDEF CPUARM}; EndP: PChar{$ENDIF jdtFloat: Result.FData.FFloatValue := Value; jdtDateTime: - Result.FData.FDateTimeValue := Value; + Result.FData.FDateTimeTZValue := TDateTimeTZ.Create(Value); jdtBool: Result.FData.FBoolValue := Value; end; @@ -7195,7 +7408,9 @@ function TJsonDataValueHelper.GetULongValue: UInt64; jdtFloat: Result := Trunc(FData.FFloatValue); jdtDateTime: - Result := Trunc(FData.FDateTimeValue); + if JsonDeserializationConfig.UseLocalTime then + Result := Trunc(FData.FDateTimeTZValue.LocalTime) + else Result := Trunc(FData.FDateTimeTZValue.OrgTime); jdtBool: Result := Ord(FData.FBoolValue); else @@ -7243,6 +7458,11 @@ function TJsonDataValueHelper.GetDateTimeValue: TDateTime; Result := Self; end; +function TJsonDataValueHelper.GetDateTimeTZValue: TDateTimeTZ; +begin + Result := Self; +end; + procedure TJsonDataValueHelper.SetDateTimeValue(const Value: TDateTime); begin ResolveName; @@ -7252,6 +7472,15 @@ procedure TJsonDataValueHelper.SetDateTimeValue(const Value: TDateTime); Self := Value; end; +procedure TJsonDataValueHelper.SetDateTimeTZValue(const Value: TDateTimeTZ); +begin + ResolveName; + if FData.FIntern <> nil then + FData.FIntern.DateTimeTZValue := Value + else + Self := Value; +end; + function TJsonDataValueHelper.GetBoolValue: Boolean; begin Result := Self; @@ -7339,7 +7568,7 @@ class procedure TJsonDataValueHelper.SetInternValue(Item: PJsonDataValue; jdtFloat: Item.FloatValue := Value.FData.FFloatValue; jdtDateTime: - Item.DateTimeValue := Value.FData.FDateTimeValue; + Item.DateTimeTZValue := Value.FData.FDateTimeTZValue; jdtBool: Item.BoolValue := Value.FData.FBoolValue; jdtArray: @@ -7402,6 +7631,11 @@ function TJsonDataValueHelper.GetObjectDateTime(const Name: string): TDateTime; Result := ObjectValue.D[Name]; end; +function TJsonDataValueHelper.GetObjectDateTimeTZ(const Name: string): TDateTimeTZ; +begin + Result := ObjectValue.Z[Name]; +end; + function TJsonDataValueHelper.GetObjectBool(const Name: string): Boolean; begin Result := ObjectValue.B[Name]; @@ -7452,6 +7686,11 @@ procedure TJsonDataValueHelper.SetObjectDateTime(const Name: string; const Value ObjectValue.D[Name] := Value; end; +procedure TJsonDataValueHelper.SetObjectDateTimeTZ(const Name: string; const Value: TDateTimeTZ); +begin + ObjectValue.Z[Name] := Value; +end; + procedure TJsonDataValueHelper.SetObjectBool(const Name: string; const Value: Boolean); begin ObjectValue.B[Name] := Value; @@ -7870,6 +8109,78 @@ function TJsonBytesStream.Realloc(var NewCapacity: Longint): Pointer; Result := Pointer(FBytes); end; +{ TDateTimeTZ } + +constructor TDateTimeTZ.Create(Value: TDateTime); +var + Offset: TDateTime; +{$IFDEF MSWINDOWS} + LocalTime, UtcTime: TSystemTime; + Hour, Min, Sec, MSec: Word; +{$ENDIF} +begin +{$IFDEF MSWINDOWS} + DateTimeToSystemTime(Value, LocalTime); + Offset := Value - SystemTimeToDateTime(UtcTime); +{$ELSE} + Offset := Value - TTimeZone.Local.ToUniversalTime(Value); +{$ENDIF} + DecodeTime(Offset, Hour, Min, Sec, MSec); + if Offset < 0 then + Create(Value, -Hour, -Min) + else Create(Value, Hour, Min); +end; + +constructor TDateTimeTZ.Create(Value: TDateTime; + OffsetHours: SmallInt; OffsetMinutes: Word = 0); +begin + Self.OrgTime := Value; + Self.OffsetHours := OffsetHours; + Self.OffsetMinutes := OffsetMinutes; +end; + +class operator TDateTimeTZ.Equal(a, b: TDateTimeTZ): Boolean; +begin + Result := CompareMem(@a, @b, SizeOf(a)); +end; + +class operator TDateTimeTZ.Implicit(const Value: TDateTime): TDateTimeTZ; +begin + Result := TDateTimeTZ.Create(Value); +end; + +function TDateTimeTZ.LocalTime: TDateTime; +var + t: TDateTimeTZ; +begin + t := TDateTimeTZ.Create(0); + Result := Self.RemoteTime(t.OffsetHours, t.OffsetMinutes); +end; + +class operator TDateTimeTZ.Implicit(const Value: TDateTimeTZ): TDateTime; +begin + Result := Value.LocalTime; +end; + +class operator TDateTimeTZ.NotEqual(a, b: TDateTimeTZ): Boolean; +begin + Result := not CompareMem(@a, @b, SizeOf(a)); +end; + +function TDateTimeTZ.RemoteTime(AOffsetHours: SmallInt; + AOffsetMinutes: Word = 0): TDateTime; +begin + Result := Self.UTCTime + (AOffsetHours * SecsPerHour * MSecsPerSec) + + (AOffsetMinutes * SecsPerMin * MSecsPerSec); +end; + +function TDateTimeTZ.UTCTime: TDateTime; +begin + Result := Self.OrgTime - ( + (OffsetHours * SecsPerHour * MSecsPerSec) + + (OffsetMinutes * SecsPerMin * MSecsPerSec) ); +end; + initialization {$IFDEF USE_NAME_STRING_LITERAL} InitializeJsonMemInfo;