JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.84k stars 3.26k forks source link

IsoDateTimeConverter converts to UTC when it shouldn't #1804

Open ghost opened 6 years ago

ghost commented 6 years ago

Source/destination types

public class MyClass
{
    public string Key { get; set; }
    public DateTime InsertTsUtc { get; set; }
}

Source/destination JSON

{
  "Key": "Test",
  "InsertTsUtc": "2018-08-21T17:31:15.083Z"
}

Expected behavior

When using IsoDateTimeConverter with DateTimeStyles = DateTimeStyles.AssumeUtc, it is expected that the DateTime will not be converted to UTC time when serializing to a JSON string

Actual behavior

InsertTsUtc was converted to UTC before formatting and serializing to a JSON string

Notes

In my case, InsertTsUtc comes from MSSQL Database and the column type is DateTime2. It is populated by a default constraint of GETUTCDATE().

When it is retrieved from the database, InsertTsUtc.Kind is DateTimeKind.Unspecified and the value is the correct UTC time that was generated by the default constraint.

I believe the issue may be in the IsoDateTimeConverter.WriteJson method

The conditional statement on line 85 (and 96)

(_dateTimeStyles & DateTimeStyles.AssumeUniversal) == DateTimeStyles.AssumeUniversal)

Should probably be

(_dateTimeStyles & DateTimeStyles.AssumeUniversal) != DateTimeStyles.AssumeUniversal)

Steps to reproduce


using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            var o = new MyClass
            {
                Key = "Test",
                InsertTsUtc = new DateTime(DateTime.UtcNow.Ticks, DateTimeKind.Unspecified)
            };

            Console.WriteLine("Inserted on: {0}", o.InsertTsUtc.ToString("g"));

            string jsonString = JsonConvert.SerializeObject(o, Formatting.Indented, new JsonSerializerSettings
            {
                Converters =
                    {
                        // Modified for clarity when testing
                        new IsoDateTimeConverter
                        {
                            Culture = CultureInfo.CurrentCulture, //CultureInfo.InvariantCulture,
                            DateTimeFormat = "g", //"yyyy-MM-ddTHH:mm:ss.fffZ",
                            DateTimeStyles = DateTimeStyles.AssumeUniversal                         
                        }
                    }
            });

            Console.WriteLine(jsonString);

            Console.ReadKey();
        }
    }

    public class MyClass
    {
        public string Key { get; set; }
        public DateTime InsertTsUtc { get; set; }
    }
}
antonberezan commented 6 years ago

Agree with @insertnamehere, according to MS docs DateTimeStyles.AssumeUniversal has the following description

If no time zone is specified in the parsed string, the string is assumed to denote a UTC.

which means, it must not convert to UTC, but just specify the UTC kind. Also IsoDateTimeConverter doesn't support DateTimeStyles.AssumeLocal

So, behavior must be following

DateTimeStyles.AdjustToUniversal: Convert DateTime to UTC.
DateTimeStyles.AssumeUniversal: Specify UTC kind.
DateTimeStyles.AssumeLocal: Specify Local kind.
freerider7777 commented 5 years ago

Any updates on this?

mywyb2 commented 2 months ago

Why is this still not fixed? It's easy to fix, and it's a serious bug that can lead to subtle data integrity issues.