Sielu
Sielu

Reputation: 1110

Serialize DateTime to XML in specific format .net

I have a large collection of automatically generated classes that are serialized to / deserialized from XML using .NET XmlSerializer. Some of those classes contain DateTime properties.

I have a requirement to serialize all DateTime properties using specific format, for example "u". :

System.DateTime.Now.ToString("u");
//returns something like "2008-06-15 21:15:07Z"

Deserialize method seems to work with this format, but serialization does not - it just gives me hardcoded format same format as I have by default in the hosting system (maybe I can trick the serializer by changing thread setting?) (check Update #1).

Note: I've already checked few related questions and they are solved by modifying original properties and adding new ones. For me it is not acceptable due to large number of modification I'd have to make.

Below is a sample test class that I use to test this issue.

using System;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Globalization;
using System.IO;

namespace FunkyTests
{
    [TestClass]
    public class SerializationTests
    {
        [TestMethod]
        public void FunkySerializationTest()
        {
            var date = DateTime.Now;
            var dummy = new Dummy() { DummyProperty = date };
            var xml = SerializeToXml(dummy);//serializes with "o" format

            var expected = date.ToString("u", new CultureInfo("en-US"));//in this example I want "u" format
            var actual = XDocument.Parse(xml).Descendants("DummyProperty").Single().Value;
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void FunkyDeserializationTest()
        {
            var date = DateTime.Now;
            var dummy = new Dummy() { DummyProperty = date };
            var xml = SerializeToXml(dummy);//serializes with "o" format
            var deserializedDummy = DeserializeFromXml<Dummy>(xml);

            Assert.AreEqual(dummy.DummyProperty, deserializedDummy.DummyProperty);
        }

        private static T DeserializeFromXml<T>(string xml)
        {
            using (var textReader = new StringReader(xml))
            {
                using (var reader = XmlReader.Create(textReader))
                {
                    var serializer = new XmlSerializer(typeof(T));
                    var result = serializer.Deserialize(reader);
                    return (T)result;
                }
            }
        }

        private static string SerializeToXml<T>(T objectToSerialize)
        {
            var xml = new StringBuilder();
            using (var writer = XmlWriter.Create(xml, new XmlWriterSettings { OmitXmlDeclaration = true }))
            {
                var ns = new XmlSerializerNamespaces();
                ns.Add(string.Empty, string.Empty);

                var serializer = new XmlSerializer(typeof(T));
                serializer.Serialize(writer, objectToSerialize, ns);
            }
            return xml.ToString();
        }

        public class Dummy
        {
            public DateTime DummyProperty { get; set; }
        }
    }
}

Any funky ideas?

Update #1: I thought that date serialization is dependent on Thread.CurrentCulture. If I interpret XmlSerializer code correctly, format is hardcoded (thank you ILSpy).

// System.Xml.Serialization.XmlCustomFormatter
internal static string FromDateTime(DateTime value)
{
    if (XmlCustomFormatter.Mode == DateTimeSerializationSection.DateTimeSerializationMode.Local)
    {
        return XmlConvert.ToString(value, "yyyy-MM-ddTHH:mm:ss.fffffffzzzzzz");
    }
    return XmlConvert.ToString(value, XmlDateTimeSerializationMode.RoundtripKind);
}

Update #2: I have added a test method that uses proposed answer from Alexander Petrov. It fails at assertion of Ticks. According to Serialize DateTime as binary problem is in internals of DateTime and is caused by loosing "Kind" of DateTime. It was suggested to use DateTimeOffset, which leads to another problem of serialization, explained in How can I XML Serialize a DateTimeOffset Property?

Upvotes: 3

Views: 8512

Answers (1)

Alexander Petrov
Alexander Petrov

Reputation: 14261

Try to use custom xml reader and writer.

public class CustomDateTimeWriter : XmlTextWriter
{
    public CustomDateTimeWriter(TextWriter writer) : base(writer) { }
    public CustomDateTimeWriter(Stream stream, Encoding encoding) : base(stream, encoding) { }
    public CustomDateTimeWriter(string filename, Encoding encoding) : base(filename, encoding) { }

    public override void WriteRaw(string data)
    {
        DateTime dt;

        if (DateTime.TryParse(data, out dt))
            base.WriteRaw(dt.ToString("u", new CultureInfo("en-US")));
        else
            base.WriteRaw(data);
    }
}

public class CustomDateTimeReader : XmlTextReader
{
    public CustomDateTimeReader(TextReader input) : base(input) { }
    // define other required constructors

    public override string ReadElementString()
    {
        string data = base.ReadElementString();
        DateTime dt;

        if (DateTime.TryParse(data, null, DateTimeStyles.AdjustToUniversal, out dt))
            return dt.ToString("o");
        else
            return data;
    }
}

Use

var dummy = new Dummy { DummyProperty = DateTime.Now };
Console.WriteLine(dummy.DummyProperty);
var xs = new XmlSerializer(typeof(Dummy));
string xml;

using (var stringWriter = new StringWriter())
using (var xmlWriter = new CustomDateTimeWriter(stringWriter))
{
    xmlWriter.Formatting = Formatting.Indented;

    xs.Serialize(xmlWriter, dummy);
    xml = stringWriter.ToString();
}

Console.WriteLine(xml);

using (var stringReader = new StringReader(xml))
using (var xmlReader = new CustomDateTimeReader(stringReader))
{
    dummy = (Dummy)xs.Deserialize(xmlReader);
    Console.WriteLine(dummy.DummyProperty);
}

Advantages:

This approach automatically (de)serializes all values of the date-time to/from the desired format, without requiring changes to the generated classes.

Disadvantages:

This approach can accidentally change the values like date-time.

This approach might slow down the (de)serialization.

Upvotes: 2

Related Questions