protobuf-net не десериализует DateTime.Kind правильно - PullRequest
18 голосов
/ 12 июля 2011

с использованием protobuf-net.dll версии 1.0.0.280

Когда я десериализирую DateTime (завернутый в объект), дата / время в порядке, но свойство DateTime.Kind имеет значение «Не указано»

Рассмотрим этот контрольный пример для сериализации / десериализации 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);
    }
}

Утверждение не удалось, Вид == Unspecified

Добавление

В результате того, что protobuf-net не сериализует это свойство (см. Ниже), можно просто предположить, что DateTimeKind равно Utc при отображении дат на стороне клиента (только там, где вы знаете это должно быть UTC, конечно):

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;
}

Это избавляет вас от необходимости присваивать каждому DateTime свойству на принимающей стороне.

Ответы [ 7 ]

6 голосов
/ 12 июля 2011

protobuf.net должен поддерживать совместимость с двоичным форматом protobuf, который разработан для типов данных даты / времени Java. Нет Kind поле в Java -> Нет Kind поддержка в двоичном формате protobuf -> Kind не передается по сети. Или что-то в этом роде.

Как оказалось, protobuf.net кодирует поле Ticks (только), вы найдете код в BclHelpers.cs.

Но не стесняйтесь добавлять другое поле в вашем определении сообщения protobuf для этого значения.

5 голосов
/ 12 июля 2011

В качестве дополнения к ответу Бена ... строго говоря, protobuf не имеет определения времени, поэтому нет ничего, с чем можно было бы совместить совместимость. Я испытываю желание добавить поддержку для этого в v2, но к сожалению это добавило бы 2 байта на значение. Мне еще предстоит подумать о том, приемлемо ли это ... например, я мог бы, возможно, установить значение по умолчанию "не указано", чтобы значение имели только явно локальные даты или даты UTC.

2 голосов
/ 02 мая 2014

Другое решение - изменить свойство kind для DTO и всегда устанавливать его в UTC.Это может быть неприемлемо для всех приложений, но работает для менявывод, что следует использовать длинное представление для DateTime.Например, используя время UNIX.Больно переводить объект protobuf в C # DateTime на другие языки DateTime.Однако что-то такое простое, как долго, понимают все

1 голос
/ 05 октября 2015

При условии, что вам нужен только один DateTimeKind (то есть UTC или Local), есть простое (хотя и не симпатичное) решение.

Поскольку внутренняя служба protobuf-net конвертирует DateTime вПредставление Unix-Time имеет единственное значение DateTime, представляющее эпоху Unix (1970/01/01), к которому оно каждый раз добавляет соответствующую дельту.

Если вы замените это значение, используя отражение, на UTC или Local DateTime значение, все ваши DateTime s будут иметь указанное значение DateTimeKind:

typeof (BclHelpers).
    GetField("EpochOrigin", BindingFlags.NonPublic | BindingFlags.Static).
    SetValue(null, new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc));

Подробнее об этом можно узнать в моем блоге

1 голос
/ 05 августа 2012

Для protobuf может иметь больше смысла автоматически десериализовать DateTime с помощью UtcKind. Таким образом, если вы используете Utc в качестве своей базы, что, я думаю, является наилучшей практикой, у вас не возникнет никаких проблем.

1 голос
/ 27 июля 2012

Вот реализация для обхода проблемы.Дайте мне знать, если вы можете найти лучшее решение.Спасибо!

[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;
    }
}

Использование:

[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);
        }
    }

}
0 голосов
/ 25 февраля 2019

Начиная с protobuf-net 2.2 (см. commit ), есть возможность подписаться на сериализацию DateTime.Kind.Вы можете установить глобальный флаг.Соответствующий выпуск на github (все еще открыт).

А вот пример использования в связи с NServiceBus.

Отказ от ответственности: это выигралоt для старой версии protobuf-net, на которую ссылается OP, но это старый вопрос, который может быть полезен для других.

...