c# - protobuf-net no deserializa DateTime.Kind correctamente
.net serialization (7)
A partir de protobuf-net 2.2 (ver commit ), existe la posibilidad de optar por la serialización de DateTime.Kind. Puede establecer una bandera global. El issue correspondiente en github (todavía abierto).
Y aquí hay un ejemplo de uso en relación con NServiceBus.
Descargo de responsabilidad: Esto no ayudará para la versión antigua de protobuf-net a la que se refiere OP, pero esta es una pregunta antigua y puede ser útil para otros.
utilizando protobuf-net.dll versión 1.0.0.280
Cuando deserializo un DateTime
(envuelto en un objeto), la fecha / hora están bien, pero la propiedad DateTime.Kind
está ''Sin especificar''
Considere este caso de prueba para serializar / deserializar un DateTime.
[TestMethod]
public void TestDateTimeSerialization()
{
var obj = new DateTimeWrapper {Date = DateTime.UtcNow};
obj.Date = DateTime.SpecifyKind(obj.Date, DateTimeKind.Utc);
var serialized = obj.SerializeProto();
var deserialized = serialized.DeserializeProto<DateTimeWrapper>();
Assert.AreEqual(DateTimeKind.Utc, deserialized.Date.Kind);
}
public static byte[] SerializeProto<T>(this T item) where T : class
{
using (var ms = new MemoryStream())
{
Serializer.Serialize(ms, item);
return ms.ToArray();
}
}
public static T DeserializeProto<T>(this byte[] raw) where T : class, new()
{
using (var ms = new MemoryStream(raw))
{
return Serializer.Deserialize<T>(ms);
}
}
La afirmación falla, la clase == Unspecified
Apéndice
Como resultado de que protobuf-net no haya serializado esta propiedad (ver a continuación), una solución es simplemente asumir que DateTimeKind
es igual a Utc cuando se muestran las fechas en el lado del cliente (solo cuando se sabe que debería ser UTC, por supuesto):
public static DateTime ToDisplayTime(this DateTime utcDateTime, TimeZoneInfo timezone)
{
if (utcDateTime.Kind != DateTimeKind.Utc)//may be Unspecified due to serialization
utcDateTime = DateTime.SpecifyKind(utcDateTime, DateTimeKind.Utc);
DateTime result = TimeZoneInfo.ConvertTime(utcDateTime, timezone);
return result;
}
Esto le ahorra tener que asignar a cada propiedad DateTime
en el lado receptor.
Aquí hay una implementación para una solución. Déjame saber si puedes encontrar una mejor solución. ¡Gracias!
[ProtoContract(SkipConstructor = true)]
public class ProtoDateTime
{
[ProtoIgnore]
private DateTime? _val;
[ProtoIgnore]
private DateTime Value
{
get
{
if (_val != null)
{
return _val.Value;
}
lock (this)
{
if (_val != null)
{
return _val.Value;
}
_val = new DateTime(DateTimeWithoutKind.Ticks, Kind);
}
return _val.Value;
}
set
{
lock (this)
{
_val = value;
Kind = value.Kind;
DateTimeWithoutKind = value;
}
}
}
[ProtoMember(1)]
private DateTimeKind Kind { get; set; }
[ProtoMember(2)]
private DateTime DateTimeWithoutKind { get; set; }
public static DateTime getValue(ref ProtoDateTime wrapper)
{
if (wrapper == null)
{
wrapper = new ProtoDateTime();
}
return wrapper.Value;
}
public static DateTime? getValueNullable(ref ProtoDateTime wrapper)
{
if (wrapper == null)
{
return null;
}
return wrapper.Value;
}
public static void setValue(out ProtoDateTime wrapper, DateTime value)
{
wrapper = new ProtoDateTime { Value = value };
}
public static void setValue(out ProtoDateTime wrapper, DateTime? newVal)
{
wrapper = newVal.HasValue ? new ProtoDateTime { Value = newVal.Value } : null;
}
}
Uso:
[ProtoContract(SkipConstructor = true)]
public class MyClass
{
[ProtoMember(3)]
[XmlIgnore]
private ProtoDateTime _timestampWrapper { get; set; }
[ProtoIgnore]
public DateTime Timestamp
{
get
{
return ProtoDateTime.getValue(ref _timestampWrapper);
}
set
{
return ProtoDateTime.setValue(out _timestampWrapper, value);
}
}
[ProtoMember(4)]
[XmlIgnore]
private ProtoDateTime _nullableTimestampWrapper { get; set; }
[ProtoIgnore]
public DateTime? NullableTimestamp
{
get
{
return ProtoDateTime.getValueNullable(ref _nullableTimestampWrapper);
}
set
{
return ProtoDateTime.setValue(out _nullableTimestampWrapper, value);
}
}
}
Como extensión de la respuesta de Ben ... estrictamente hablando, protobuf no tiene una definición de tiempo, por lo que no hay nada con lo que mantener la compatibilidad. Estoy tentado a agregar soporte para esto en v2, pero lamentablemente agregaría 2 bytes por valor. Todavía tengo que pensar si esto es aceptable ... por ejemplo, tal vez podría omitir "no especificado" para que solo las fechas explícitamente locales o UTC tengan un valor.
Otra solución es cambiar la propiedad de tipo para DTO y establecerla siempre en UTC. Esto puede no ser aceptable para todas las aplicaciones, pero funciona para mí
class DateTimeWrapper
{
private DateTime _date;
public DateTime Date
{
get { return _date; }
set { _date = new DateTime(value.Ticks, DateTimeKind.Utc);}
}
}
Actualizar
Después de usar protobuf durante más de un año e integrar C #, Java, Python y Scala, llegué a la conclusión de que se debería usar una representación larga para DateTime. Por ejemplo, usando el tiempo de UNIX. Es doloroso traducir el objeto protobuf DateTime de C # a otros lenguajes DateTime. Sin embargo, algo tan simple como largo es entendido por todos.
Puede que tenga más sentido que protobuf deserialice automáticamente DateTime con UtcKind, de esa forma, si está utilizando Utc como su base, lo cual creo que es la mejor práctica, no tendrá ningún problema.
Suponiendo que solo necesita un DateTimeKind
(es decir, UTC
o Local
), hay una solución simple (aunque no bonita).
Dado que internamente protobuf-net convierte DateTime
''s en representación de Unix-Time, tiene un solo valor DateTime
que representa la época de Unix (1970/01/01) a la que agrega el delta relevante cada vez.
Si reemplaza ese valor utilizando la reflexión con un valor UTC
o Local
DateTime
, todos sus DateTime
s tendrán ese DateTimeKind
especificado:
typeof (BclHelpers).
GetField("EpochOrigin", BindingFlags.NonPublic | BindingFlags.Static).
SetValue(null, new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc));
Puedes leer más sobre en mi blog.
protobuf.net debe mantener la compatibilidad con el formato binario protobuf, que está diseñado para los tipos de datos de fecha / hora de Java. Campo No Kind
en Java -> No Kind
support en el formato binario protobuf -> Kind
no transferido a través de la red. O algo por el estilo.
Resulta que, protobuf.net codifica el campo Ticks
(solo), encontrará el código en BclHelpers.cs
.
Pero no dude en agregar otro campo en la definición de mensaje de protobuf para este valor.