Why can DateTime.MinValue not be serialized in timezones ahead of UTC?

后端 未结 7 1016
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-24 02:09

I am experiencing issues with a WCF REST service. The wire object that I try to return has certain properties not set, resulting in DateTime.MinValue for properties of type

相关标签:
7条回答
  • It's possible to avoid the issue (with the WriteDateTimeInDefaultFormat method) by specifying a custom DateTimeFormat:

    DataContractJsonSerializerSettings settings = new DataContractJsonSerializerSettings();
    settings.DateTimeFormat = new DateTimeFormat("yyyy-MM-ddThh:mm:ss.fffZ");
    DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(MyDataContract), settings);
    

    Note that you should make sure that the DateTime value is really in UTC, but this is safe to use with DateTime.MinValue.

    See this for additional details about the DateTimeFormat being used.

    0 讨论(0)
  • 2020-12-24 02:20

    The main problem is DateTime.MinValue has DateTimeKind.Unspecified kind. It is defined as:

    MinValue = new DateTime(0L, DateTimeKind.Unspecified);
    

    But this is not a real problem, this definition leads to problem during serialization. JSON DateTime serialization done through:

    System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTime(DateTime value)
    

    Unfortunately it is defined as:

    ...
    
    if (value.Kind != DateTimeKind.Utc)
    {
        long num = value.Ticks - TimeZone.CurrentTimeZone.GetUtcOffset(value).Ticks;
        if ((num > DateTime.MaxValue.Ticks) || (num < DateTime.MinValue.Ticks))
        {
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(XmlObjectSerializer.CreateSerializationException(SR.GetString("JsonDateTimeOutOfRange"), new ArgumentOutOfRangeException("value")));
        }
    }
    
    ...
    

    So it doesn't take into account Unspecified and treats it as Local. To avoid this situation you can define your own constant:

    MinValueUtc = new DateTime(0L, DateTimeKind.Utc);
    

    or

    MinValueUtc = DateTime.MinValue.ToUniversalTime();
    

    It looks weird of course, but it helps.

    0 讨论(0)
  • 2020-12-24 02:21

    I believe a more elegant way is to instruct the serializer not to emit the default value for DateTime fields. This will save some byte during transfer and some processing when serializing for the fields that you don't have any value for them. Example:

    [DataContract]
    public class Document {
        [DataMember] 
        public string Title { get; set; }
        [DataMember(IsRequired = false, EmitDefaultValue = false)] 
        public DateTime Modified { get; set; } 
    }
    

    or you can use Nullables. Example:

    [DataContract]
    public class Document {
        [DataMember] 
        public string Title { get; set; }
        [DataMember] 
        public DateTime? Modified { get; set; } 
    }
    

    It all depends on the requirements and restrictions you might have in your project. Sometimes you cannot just change the data types. In that case you can still take advantage of DataMember attribute and keep the data types intact.

    In the above example if you have new Document() { Title = "Test Document" } in the server side, when serialized to JSON it will give you {"Title": "Test Document"} so it will be easier to deal with in JavaScript or any other client in the other side of the wire. In JavaScript if you JSON.Parse() it, and try to read it, you will get back undefined. In typed languages you will have the default value for that property depending on the type (which is typically the expected behavior).

    library.GetDocument(id).success(function(raw){ 
        var document = JSON.Parse(raw);
        var date = document.date; // date will be *undefined*
        ...
    }
    
    0 讨论(0)
  • 2020-12-24 02:21

    You could fix that during serialization via the OnSerializing attribute and some reflection:

    [OnSerializing]
    public void OnSerializing(StreamingContext context)
    {
      var properties = this.GetType().GetProperties();
      foreach (PropertyInfo property in properties)
      {
        if (property.PropertyType == typeof(DateTime) && property.GetValue(this).Equals(DateTime.MinValue))
        {
          property.SetValue(this, DateTime.MinValue.ToUniversalTime());
        }
      }
    }
    
    0 讨论(0)
  • 2020-12-24 02:26

    using this constructor:

    public DataContractJsonSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, IDataContractSurrogate dataContractSurrogate, bool alwaysEmitTypeInformation)
    

    example code:

    DataContractJsonSerializer serializer = new DataContractJsonSerializer(o.GetType(), null, int.MaxValue, false, new DateTimeSurrogate(), false);
    
     public class DateTimeSurrogate : IDataContractSurrogate
        {
    
            #region IDataContractSurrogate 成员
    
            public object GetCustomDataToExport(Type clrType, Type dataContractType)
            {
                return null;
            }
    
            public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
            {
                return null;
            }
    
            public Type GetDataContractType(Type type)
            {
                return type;
            }
    
            public object GetDeserializedObject(object obj, Type targetType)
            {
                       return obj;
            }
    
            public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
            {
    
            }
    
            public object GetObjectToSerialize(object obj, Type targetType)
            {
                if (obj.GetType() == typeof(DateTime))
                {
                    DateTime dt = (DateTime)obj;
                    if (dt == DateTime.MinValue)
                    {
                        dt = DateTime.MinValue.ToUniversalTime();
                        return dt;
                    }
                    return dt;
                }
                if (obj == null)
                {
                    return null;
                }
                var q = from p in obj.GetType().GetProperties()
                        where (p.PropertyType == typeof(DateTime)) && (DateTime)p.GetValue(obj, null) == DateTime.MinValue
                        select p;
                q.ToList().ForEach(p =>
                {
                    p.SetValue(obj, DateTime.MinValue.ToUniversalTime(), null);
                });
                return obj;
            }
    
            public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
            {
                return null;
            }
    
            public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
            {
                return typeDeclaration;
            }
    
            #endregion
        }
    
    0 讨论(0)
  • 2020-12-24 02:27

    Try to add this on any DateTime Member

    [DataMember(IsRequired = false, EmitDefaultValue = false)]
    

    Most of these erros happens because the default value of the datetime is DateTime.MinValue which is from year of 1 and the JSON serialization is from year 1970.

    0 讨论(0)
提交回复
热议问题