Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

Cruel DateTime vs serialization

Недавно столкнулся с проблемой сериализации DateTime. protobuf-net, как и BinaryFormatter не сохраняют тип даты, перечисление DateTimeKind. В результате после чтения из архива тип даты становится Unspecified. Вот выдержка из исходного кода структуры DateTime:

// This value type represents a date and time.  Every DateTime
// object has a private field (Ticks) of type Int64 that stores the
// date and time as the number of 100 nanosecond intervals since
// 12:00 AM January 1, year 1 A.D. in the proleptic Gregorian Calendar. 
//
// Starting from V2.0, DateTime also stored some context about its time 
// zone in the form of a 3-state value representing Unspecified, Utc or 
// Local. This is stored in the two top bits of the 64-bit numeric value
// with the remainder of the bits storing the tick count. This information 
// is only used during time zone conversions and is not part of the
// identity of the DateTime. Thus, operations like Compare and Equals
// ignore this state. This is to stay compatible with earlier behavior
// and performance characteristics and to avoid forcing  people into dealing 
// with the effects of daylight savings. Note, that this has little effect
// on how the DateTime works except in a context where its specific time 
// zone is needed, such as during conversions and some parsing and formatting 
// cases.
// 
// There is also 4th state stored that is a special type of Local value that
// is used to avoid data loss when round-tripping between local and UTC time.
// See below for more information on this 4th state, although it is
// effectively hidden from most users, who just see the 3-state DateTimeKind 
// enumeration.
// 
// For compatability, DateTime does not serialize the Kind data when used in 
// binary serialization.
// 
// For a description of various calendar issues, look at
//
// Calendar Studies web site, at
// http://serendipity.nofadz.com/hermetic/cal_stud.htm. 
//
// 
[StructLayout(LayoutKind.Auto)] 
[Serializable]
public struct DateTime : IComparable, IFormattable, IConvertible, ISerializable, IComparable,IEquatable { 

Как видно из описания, дата представляет собой число типа Int64 в котором хранится количество 100нс интервалов от начала времен - 12:00 AM January 1, year 1 A.D. in the proleptic Gregorian Calendar. А вот до какой точки - здесь уже интересней. В случае DateTimeKind.Utc - до Гринвича, DateTimeKind.Local - до времени в локальной для операционной системы\программы\потока зоне. И последнее значение - DateTimeKind.Unspecified, до куда - неизвестно.

На что влияет тип даты? В первую очередь на методы ToLocalTime и ToUniversalTime, потом уже и на форматирующие методы. Самое неприятное происходит при вызове этих двух методов для дат с типом DateTimeKind.Unspecified - ToLocalTime считает, что дата имеет тип DateTimeKind.Utc, а ToUniversalTime - что тип DateTimeKind.Local. Логично, правда? В результате если сериализировать DateTime.UtcNow, вычитать его обратно и преобразовать в DateTimeKind.Utc методом ToUniversalTime - получаем сдвиг на временную зону. При этом ToLocalTime вернет правильный результат.

Обойти это недоразумение можно с помощью статического метода DateTime.SpecifyKind.

using System;

namespace CSharpLanguageInv
{
   internal class Program
   {
      private static void Main(string[] args)
      {
         var now = DateTime.UtcNow;
         var unspecified = DateTime.SpecifyKind(now, DateTimeKind.Unspecified);
         var localTime = unspecified.ToLocalTime();
         var universalTime = unspecified.ToUniversalTime();
         Console.WriteLine("Now               = " + now/*.Ticks*/);
         Console.WriteLine("unspecified       = " + unspecified/*.Ticks*/);
         Console.WriteLine("localTime         = " + localTime/*.Ticks*/);
         Console.WriteLine("universalTime     = " + universalTime/*.Ticks*/);
         Console.WriteLine("Now - unspecified = " + (now/*.Ticks*/ - unspecified/*.Ticks*/));
         Console.ReadLine();
      }
   }
}

1 коммент.:

borismod комментирует...

Интересный пост, на самом деле в Protobuf не сохраняется DateTimeKind. А используя BinaryFormatter таки сохраняется:

[TestCase(DateTimeKind.Local)]
[TestCase(DateTimeKind.Utc)]
[TestCase(DateTimeKind.Unspecified)]
public void SerializeDeserialize_DateKindSet_DateKindRestored(DateTimeKind dateTimeKind)
{
var dateTime = new DateTime(new Random().Next());
dateTime = DateTime.SpecifyKind(dateTime, dateTimeKind);

SerializationHelper.SerializeNow(dateTime);
var deSerializeNow = (DateTime) SerializationHelper.DeSerializeNow();

Assert.That(deSerializeNow.Kind, Is.EqualTo(dateTimeKind));
}

Для теста я изпользовал следующий класс с небольшой модификацией:

http://borismod.blogspot.co.il/2008/07/nunit-serialization-test.html

Отправить комментарий

Copyright 2007-2011 Chabster