Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 8d7aefc

Browse files
committed
Add JsScope.PreserveUtc. When true overrides standard behavior of converting all DateTime into DateTimeKind.Local.
1 parent 4507e0b commit 8d7aefc

File tree

5 files changed

+114
-17
lines changed

5 files changed

+114
-17
lines changed

src/ServiceStack.Text/Common/DateTimeSerializer.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static class DateTimeSerializer
4646
public static Func<string, Exception, DateTime> OnParseErrorFn { get; set; }
4747

4848
/// <summary>
49-
/// If AlwaysUseUtc is set to true then convert all DateTime to UTC.
49+
/// If AlwaysUseUtc is set to true then convert all DateTime to UTC. If PreserveUtc is set to true then UTC dates will not convert to local
5050
/// </summary>
5151
/// <param name="dateTime"></param>
5252
/// <returns></returns>
@@ -56,6 +56,10 @@ public static DateTime Prepare(this DateTime dateTime, bool parsedAsUtc=false)
5656
{
5757
return dateTime.Kind != DateTimeKind.Utc ? dateTime.ToStableUniversalTime() : dateTime;
5858
}
59+
if (JsConfig.PreserveUtc && dateTime.Kind == DateTimeKind.Utc)
60+
{
61+
return dateTime;
62+
}
5963
return parsedAsUtc ? dateTime.ToLocalTime() : dateTime;
6064
}
6165

@@ -206,10 +210,10 @@ private static string RepairXsdTimeSeparator(string dateTimeStr)
206210
{
207211
if (dateTimeStr == null || dateTimeStr.Length < ShortDateTimeFormat.Length)
208212
return null;
209-
210213
if (dateTimeStr.EndsWith(XsdUtcSuffix))
211214
{
212215
dateTimeStr = dateTimeStr.Substring(0, dateTimeStr.Length - 1);
216+
dateKind = JsConfig.PreserveUtc ? DateTimeKind.Utc : dateKind;
213217
}
214218

215219
var parts = dateTimeStr.Split('T');
@@ -408,7 +412,7 @@ public static string ToShortestXsdDateTimeString(DateTime dateTime)
408412
var timeOfDay = dateTime.TimeOfDay;
409413

410414
var isStartOfDay = timeOfDay.Ticks == 0;
411-
if (isStartOfDay)
415+
if (isStartOfDay && !(JsConfig.PreserveUtc && dateTime.Kind == DateTimeKind.Utc))
412416
return dateTime.ToString(ShortDateTimeFormat);
413417

414418
var hasFractionalSecs = (timeOfDay.Milliseconds != 0)

src/ServiceStack.Text/JsConfig.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public static JsConfigScope With(
5454
Func<Type, string> typeWriter = null,
5555
Func<string, Type> typeFinder = null,
5656
bool? treatEnumAsInteger = null,
57+
bool? preserveUtc = null,
5758
bool? alwaysUseUtc = null,
5859
bool? assumeUtc = null,
5960
bool? appendUtcOffset = null,
@@ -70,8 +71,8 @@ public static JsConfigScope With(
7071
TryToParsePrimitiveTypeValues = tryToParsePrimitiveTypeValues ?? sTryToParsePrimitiveTypeValues,
7172
TryToParseNumericType = tryToParseNumericType ?? sTryToParseNumericType,
7273

73-
ParsePrimitiveFloatingPointTypes = parsePrimitiveFloatingPointTypes ?? sParsePrimitiveFloatingPointTypes,
74-
ParsePrimitiveIntegerTypes = parsePrimitiveIntegerTypes ?? sParsePrimitiveIntegerTypes,
74+
ParsePrimitiveFloatingPointTypes = parsePrimitiveFloatingPointTypes ?? sParsePrimitiveFloatingPointTypes,
75+
ParsePrimitiveIntegerTypes = parsePrimitiveIntegerTypes ?? sParsePrimitiveIntegerTypes,
7576

7677
ExcludeDefaultValues = excludeDefaultValues ?? sExcludeDefaultValues,
7778
IncludeNullValues = includeNullValues ?? sIncludeNullValues,
@@ -90,6 +91,7 @@ public static JsConfigScope With(
9091
TypeWriter = typeWriter ?? sTypeWriter,
9192
TypeFinder = typeFinder ?? sTypeFinder,
9293
TreatEnumAsInteger = treatEnumAsInteger ?? sTreatEnumAsInteger,
94+
PreserveUtc = preserveUtc ?? sPreserveUtc,
9395
AlwaysUseUtc = alwaysUseUtc ?? sAlwaysUseUtc,
9496
AssumeUtc = assumeUtc ?? sAssumeUtc,
9597
AppendUtcOffset = appendUtcOffset ?? sAppendUtcOffset,
@@ -511,6 +513,25 @@ public static bool AlwaysUseUtc
511513
}
512514
}
513515

516+
/// <summary>
517+
/// Gets or sets a value indicating if the framework should preserve <see cref="DateTime"/> <see cref="DateTimeKind"/> when input was in UTC format instead of converting to local.
518+
/// JsConfig.DateHandler = DateHandler.ISO8601 should be used when set true for consistent serialization
519+
/// </summary>
520+
private static bool? sPreserveUtc;
521+
public static bool PreserveUtc
522+
{
523+
// obeying the use of ThreadStatic, but allowing for setting JsConfig once as is the normal case
524+
get
525+
{
526+
return (JsConfigScope.Current != null ? JsConfigScope.Current.PreserveUtc : null)
527+
?? sPreserveUtc
528+
?? false;
529+
}
530+
set
531+
{
532+
if (!sPreserveUtc.HasValue) sPreserveUtc = value;
533+
}
534+
}
514535
/// <summary>
515536
/// Gets or sets a value indicating if the framework should always assume <see cref="DateTime"/> is in UTC format if Kind is Unspecified.
516537
/// </summary>

src/ServiceStack.Text/JsConfigScope.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public void Dispose()
7171
public bool? EmitCamelCaseNames { get; set; }
7272
public bool? EmitLowercaseUnderscoreNames { get; set; }
7373
public bool? ThrowOnDeserializationError { get; set; }
74+
public bool? PreserveUtc { get; set; }
7475
public bool? AlwaysUseUtc { get; set; }
7576
public bool? AssumeUtc { get; set; }
7677
public bool? AppendUtcOffset { get; set; }

tests/ServiceStack.Text.Tests/JsonTests/JsonDateTimeTests.cs

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ public void Can_serialize_json_date_iso8601_unspecified()
349349
}
350350

351351
[Test]
352-
public void Can_deserialize_json_date_iso8601_withZOffset_asUtc()
352+
public void Can_deserialize_json_date_iso8601_withZOffset_asUtc_alwaysUseUtc_true()
353353
{
354354
JsConfig.AlwaysUseUtc = true;
355355
JsConfig.DateHandler = DateHandler.ISO8601;
@@ -363,20 +363,37 @@ public void Can_deserialize_json_date_iso8601_withZOffset_asUtc()
363363
JsConfig.Reset();
364364
}
365365

366-
[Test]
367-
public void Can_deserialize_json_date_iso8601_withoutOffset_as_Unspecified()
368-
{
369-
JsConfig.DateHandler = DateHandler.ISO8601;
366+
[Test]
367+
public void Can_deserialize_json_date_iso8601_withZOffset_asUtc_preserveUtc_true()
368+
{
369+
JsConfig.PreserveUtc = true;
370+
JsConfig.DateHandler = DateHandler.ISO8601;
370371

371-
const string json = @"""1994-11-24T12:34:56""";
372-
var fromJson = JsonSerializer.DeserializeFromString<DateTime>(json);
372+
const string json = "\"1994-11-24T12:34:56.0000000Z\"";
373+
var fromJson = JsonSerializer.DeserializeFromString<DateTime>(json);
373374

374-
var dateTime = new DateTime(1994, 11, 24, 12, 34, 56, DateTimeKind.Unspecified);
375-
Assert.That(fromJson, Is.EqualTo(dateTime));
376-
Assert.That(fromJson.Kind, Is.EqualTo(dateTime.Kind));
377-
JsConfig.Reset();
378-
}
375+
var dateTime = new DateTime(1994, 11, 24, 12, 34, 56, DateTimeKind.Utc);
376+
Assert.That(fromJson, Is.EqualTo(dateTime));
377+
Assert.That(fromJson.Kind, Is.EqualTo(dateTime.Kind));
378+
var backToJson = JsonSerializer.SerializeToString(dateTime);
379+
Assert.That(json, Is.EqualTo(backToJson));
380+
JsConfig.Reset();
381+
}
382+
383+
[Test]
384+
public void Can_deserialize_json_date_iso8601_withoutOffset_as_Unspecified()
385+
{
386+
JsConfig.DateHandler = DateHandler.ISO8601;
379387

388+
const string json = @"""1994-11-24T12:34:56""";
389+
var fromJson = JsonSerializer.DeserializeFromString<DateTime>(json);
390+
391+
var dateTime = new DateTime(1994, 11, 24, 12, 34, 56, DateTimeKind.Unspecified);
392+
Assert.That(fromJson, Is.EqualTo(dateTime));
393+
Assert.That(fromJson.Kind, Is.EqualTo(dateTime.Kind));
394+
JsConfig.Reset();
395+
}
396+
380397
[Test]
381398
public void Can_deserialize_json_date_iso8601_withOffset_asLocal()
382399
{

tests/ServiceStack.Text.Tests/Utils/DateTimeSerializerTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,60 @@ public void DateTime_Is_Serialized_As_Utc_and_Deserialized_as_local()
355355
}
356356
}
357357

358+
[Test]
359+
public void DateTime_Is_Serialized_As_Utc_and_Deserialized_as_Utc_with_PreserveUtc_true()
360+
{
361+
JsConfig.PreserveUtc = true;
362+
363+
var testObject = new TestObject
364+
{
365+
Date = new DateTime(2013, 1, 1, 0, 0, 1, DateTimeKind.Utc)
366+
};
367+
368+
Assert.AreEqual(DateTimeKind.Utc, TypeSerializer.DeserializeFromString<TestObject>(TypeSerializer.SerializeToString<TestObject>(testObject)).Date.Kind);
369+
370+
using (JsConfig.With(preserveUtc: false))
371+
{
372+
Assert.AreEqual(DateTimeKind.Local, TypeSerializer.DeserializeFromString<TestObject>(TypeSerializer.SerializeToString<TestObject>(testObject)).Date.Kind);
373+
}
374+
375+
testObject = new TestObject
376+
{
377+
Date = new DateTime(2013, 1, 1, 0, 0, 0, DateTimeKind.Utc)
378+
};
379+
380+
Assert.AreEqual(DateTimeKind.Utc, TypeSerializer.DeserializeFromString<TestObject>(TypeSerializer.SerializeToString<TestObject>(testObject)).Date.Kind);
381+
382+
using (JsConfig.With(alwaysUseUtc: true))
383+
{
384+
Assert.AreEqual(DateTimeKind.Utc, TypeSerializer.DeserializeFromString<TestObject>(TypeSerializer.SerializeToString<TestObject>(testObject)).Date.Kind);
385+
}
386+
387+
//make sure it still keeps local local
388+
testObject = new TestObject
389+
{
390+
Date = new DateTime(2013, 1, 2, 0, 2, 0, DateTimeKind.Local)
391+
};
392+
393+
Assert.AreEqual(DateTimeKind.Local, TypeSerializer.DeserializeFromString<TestObject>(TypeSerializer.SerializeToString<TestObject>(testObject)).Date.Kind);
394+
395+
using (JsConfig.With(alwaysUseUtc: true))
396+
{
397+
Assert.AreEqual(DateTimeKind.Utc, TypeSerializer.DeserializeFromString<TestObject>(TypeSerializer.SerializeToString<TestObject>(testObject)).Date.Kind);
398+
}
399+
testObject = new TestObject
400+
{
401+
Date = new DateTime(2013, 1, 2, 0, 2, 0, DateTimeKind.Unspecified)
402+
};
403+
404+
Assert.AreEqual(DateTimeKind.Local, TypeSerializer.DeserializeFromString<TestObject>(TypeSerializer.SerializeToString<TestObject>(testObject)).Date.Kind);
405+
406+
using (JsConfig.With(alwaysUseUtc: true))
407+
{
408+
Assert.AreEqual(DateTimeKind.Utc, TypeSerializer.DeserializeFromString<TestObject>(TypeSerializer.SerializeToString<TestObject>(testObject)).Date.Kind);
409+
}
410+
}
411+
358412
[Test]
359413
public void Does_parse_as_UTC()
360414
{

0 commit comments

Comments
 (0)