using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public class DataContractDateTimeOffsetConverter : JsonConverter
public DataContractDateTimeOffsetConverter() : this(true) { }
public DataContractDateTimeOffsetConverter(bool canWrite) => this.canWrite = canWrite;
public override bool CanWrite => canWrite;
public override bool CanConvert(Type objectType) => objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?);
[JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy))]
class DateTimeOffsetDTO<TOffset> where TOffset : struct, IComparable, IFormattable
public DateTime DateTime { get; set; }
public TOffset OffsetMinutes { get; set; }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var input = (DateTimeOffset)value;
var oldDateFormatHandling = writer.DateFormatHandling;
var oldDateTimeZoneHandling = writer.DateTimeZoneHandling;
writer.DateFormatHandling = DateFormatHandling.MicrosoftDateFormat;
writer.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
var offsetMinutes = input.Offset.TotalMinutes;
var offsetMinutesInt = checked((int)offsetMinutes);
var dateTime = input.DateTime.AddMinutes(-input.Offset.TotalMinutes);
if (offsetMinutesInt == offsetMinutes)
serializer.Serialize(writer, new DateTimeOffsetDTO<int> { DateTime = dateTime, OffsetMinutes = offsetMinutesInt });
serializer.Serialize(writer, new DateTimeOffsetDTO<double> { DateTime = dateTime, OffsetMinutes = offsetMinutes });
writer.DateFormatHandling = oldDateFormatHandling;
writer.DateTimeZoneHandling = oldDateTimeZoneHandling;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
switch (reader.MoveToContentAndAssert().TokenType)
return (DateTimeOffset)JToken.Load(reader);
case JsonToken.StartObject:
var old = reader.DateTimeZoneHandling;
reader.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
var dto = serializer.Deserialize<DateTimeOffsetDTO<double>>(reader);
var result = new DateTimeOffset(new DateTime(dto.DateTime.AddMinutes(dto.OffsetMinutes).Ticks, DateTimeKind.Unspecified),
TimeSpan.FromMinutes(dto.OffsetMinutes));
reader.DateTimeZoneHandling = old;
throw new JsonSerializationException();
public static partial class JsonExtensions
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None)
while (reader.TokenType == JsonToken.Comment)
public static JsonReader ReadAndAssert(this JsonReader reader)
throw new ArgumentNullException();
throw new JsonReaderException("Unexpected end of JSON stream.");
public class TestToSeailize
public DateTimeOffset SaveDate { get; set; }
public static void Test()
var o1 = new DateTimeOffset(2020 , 06, 05, 3 ,0, 0, TimeSpan.FromHours(0));
var o2 = new DateTimeOffset(2020 , 06, 05, 3 ,0, 0, TimeSpan.FromHours(5));
Console.WriteLine(o1.DateTime.Ticks == o2.DateTime.Ticks);
public static void Test(double hours)
var input = new DateTimeOffset(2020 , 06, 05, 3 ,0, 0, TimeSpan.FromHours(hours));
input.DebugDump("Input");
var dataContractJson = TestDataContractJsonSerialize(input);
var jsonNetJson = TestJsonNetSerialize(input);
var jsonNetFromDataContract = TestJsonNetDeserialize(dataContractJson);
var jsonNetFromJsonNet = TestJsonNetDeserialize(jsonNetJson);
var dataContractFromDataContract = TestDataContractDeserialize(dataContractJson);
var dataContractFromJsonNet = TestDataContractDeserialize(jsonNetJson);
Console.WriteLine("Data Contract: {0}", dataContractJson);
Console.WriteLine("Json.NET: {0}", jsonNetJson);
Assert.AreEqual(input, jsonNetFromDataContract, "jsonNetFromDataContract");
Assert.AreEqual(input, jsonNetFromJsonNet, "jsonNetFromJsonNet");
Assert.AreEqual(input, dataContractFromDataContract, "dataContractFromDataContract");
Assert.AreEqual(input, dataContractFromJsonNet, "dataContractFromJsonNet");
Assert.AreEqual(dataContractJson, jsonNetJson, "jsonNetJson");
static string TestDataContractJsonSerialize(DateTimeOffset test, DataContractJsonSerializerSettings settings = null)
TestToSeailize item = new TestToSeailize()
DataContractJsonSerializer serializer = new DataContractJsonSerializer(item.GetType(), settings);
using (MemoryStream ms = new MemoryStream())
serializer.WriteObject(ms, item);
var json = Encoding.UTF8.GetString(ms.ToArray());
static DateTimeOffset TestDataContractDeserialize(string json, DataContractJsonSerializerSettings settings = null)
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(TestToSeailize), settings);
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
return ((TestToSeailize)serializer.ReadObject(ms)).SaveDate;
static DateTimeOffset TestJsonNetDeserialize(string json)
var settings = new JsonSerializerSettings
Converters = { new DataContractDateTimeOffsetConverter(canWrite : true) },
var item = JsonConvert.DeserializeObject<TestToSeailize>(json, settings);
static string TestJsonNetSerialize(DateTimeOffset test)
TestToSeailize item = new TestToSeailize()
var settings = new JsonSerializerSettings
Converters = { new DataContractDateTimeOffsetConverter(canWrite : true) },
string json = JsonConvert.SerializeObject(item, settings);
public static class DateTimeOffsetExtensions
public static void DebugDump(this DateTimeOffset offset, string name = "Offset")
Console.WriteLine("{0,-7} = {1}, .Ticks = {2}, .DateTime = {3}, .Offset = {4}.", name, offset, offset.Ticks, offset.DateTime, offset.Offset);
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];