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;