215

I am trying to serialize a .NET TimeSpan object to XML and it is not working. A quick google has suggested that while TimeSpan is serializable, the XmlCustomFormatter does not provide methods to convert TimeSpan objects to and from XML.

One suggested approach was to ignore the TimeSpan for serialization, and instead serialize the result of TimeSpan.Ticks (and use new TimeSpan(ticks) for deserialization). An example of this follows:

[Serializable]
public class MyClass
{
    // Local Variable
    private TimeSpan m_TimeSinceLastEvent;

    // Public Property - XmlIgnore as it doesn't serialize anyway
    [XmlIgnore]
    public TimeSpan TimeSinceLastEvent
    {
        get { return m_TimeSinceLastEvent; }
        set { m_TimeSinceLastEvent = value; }
    }

    // Pretend property for serialization
    [XmlElement("TimeSinceLastEvent")]
    public long TimeSinceLastEventTicks
    {
        get { return m_TimeSinceLastEvent.Ticks; }
        set { m_TimeSinceLastEvent = new TimeSpan(value); }
    }
}

While this appears to work in my brief testing - is this the best way to achieve this?

Is there a better way to serialize a TimeSpan to and from XML?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
joeldixon66
  • 2,153
  • 2
  • 13
  • 5
  • 4
    Rory MacLeod's answer below is actually the way Microsoft recommends doing this. – Jeff Jun 08 '12 at 17:23
  • 2
    I would not use long ticks for TimeSpand because XML's duration type is the exact match. The issue was raised to Microsoft in year 2008 but never resolved. There is a workaround documented back then: http://kennethxu.blogspot.com/2008/09/xmlserializer-doesn-serialize-timespan.html – Kenneth Xu Sep 03 '13 at 01:52

13 Answers13

114

This is only a slight modification on the approach suggested in the question, but this Microsoft Connect issue recommends using a property for serialization like this:

[XmlIgnore]
public TimeSpan TimeSinceLastEvent
{
    get { return m_TimeSinceLastEvent; }
    set { m_TimeSinceLastEvent = value; }
}

// XmlSerializer does not support TimeSpan, so use this property for 
// serialization instead.
[Browsable(false)]
[XmlElement(DataType="duration", ElementName="TimeSinceLastEvent")]
public string TimeSinceLastEventString
{
    get 
    { 
        return XmlConvert.ToString(TimeSinceLastEvent); 
    }
    set 
    { 
        TimeSinceLastEvent = string.IsNullOrEmpty(value) ?
            TimeSpan.Zero : XmlConvert.ToTimeSpan(value); 
    }
}

This would serialize a TimeSpan of 0:02:45 as:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Alternatively, the DataContractSerializer supports TimeSpan.

Rory MacLeod
  • 11,012
  • 7
  • 41
  • 43
  • 17
    +1 for XmlConvert.ToTimeSpan(). It handles ISO standard duration syntax for timespan like PT2H15M, see http://en.wikipedia.org/wiki/ISO_8601#Durations – yzorg Mar 08 '12 at 23:28
  • 3
    Correct me if I'm wrong, but the serlized TimeSpan "PT2M45S" is 00:02:45, not 2:45:00. – Tom Pažourek Jul 04 '13 at 07:48
  • The connect link is now broken, maybe it can be replaced with this one:https://connect.microsoft.com/VisualStudio/feedback/details/684819/xml-serialization-of-timespan-doesnt-work ? The technique also looks a little different... – TJB May 11 '15 at 23:54
72

The way you've already posted is probably the cleanest. If you don't like the extra property, you could implement IXmlSerializable, but then you have to do everything, which largely defeats the point. I'd happily use the approach you've posted; it is (for example) efficient (no complex parsing etc), culture independent, unambiguous, and timestamp-type numbers are easily and commonly understood.

As an aside, I often add:

[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]

This just hides it in the UI and in referencing dlls, to avoid confusion.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 5
    Doing __everything__ isn't so bad if you implement the interface on a struct that wraps System.TimeSpan, rather than implementing it on MyClass. Then you only have to change the type on your MyClass.TimeSinceLastEvent property – phoog Dec 08 '10 at 20:52
31

Something that can work in some cases is to give your public property a backing field, which is a TimeSpan, but the public property is exposed as a string.

eg:

protected TimeSpan myTimeout;
public string MyTimeout 
{ 
    get { return myTimeout.ToString(); } 
    set { myTimeout = TimeSpan.Parse(value); }
}

This is ok if the property value is used mostly w/in the containing class or inheriting classes and is loaded from xml configuration.

The other proposed solutions are better if you want the public property to be a usable TimeSpan value for other classes.

Wes
  • 1,059
  • 13
  • 18
  • By far the easiest solution. I have come up with exactly the same thing and it works like a charm. Easy to implement and understand. – wpfwannabe Sep 27 '11 at 10:06
  • 1
    This is the best solution here. It serializes very good!!! Thank you for you input friend! – NoWar Nov 28 '11 at 13:20
  • The advantage of this solution is the human-readability. "00:02:00" is much better than "PT2M45S" when manually edited in Notepad by the end-user or admin. – Kim Homann Mar 20 '23 at 15:09
30

Combining an answer from Color serialization and this original solution (which is great by itself) I got this solution:

[XmlElement(Type = typeof(XmlTimeSpan))]
public TimeSpan TimeSinceLastEvent { get; set; }

where XmlTimeSpan class is like this:

public class XmlTimeSpan
{
    private const long TICKS_PER_MS = TimeSpan.TicksPerMillisecond;

    private TimeSpan m_value = TimeSpan.Zero;

    public XmlTimeSpan() { }
    public XmlTimeSpan(TimeSpan source) { m_value = source; }

    public static implicit operator TimeSpan?(XmlTimeSpan o)
    {
        return o == null ? default(TimeSpan?) : o.m_value;
    }

    public static implicit operator XmlTimeSpan(TimeSpan? o)
    {
        return o == null ? null : new XmlTimeSpan(o.Value);
    }

    public static implicit operator TimeSpan(XmlTimeSpan o)
    {
        return o == null ? default(TimeSpan) : o.m_value;
    }

    public static implicit operator XmlTimeSpan(TimeSpan o)
    {
        return o == default(TimeSpan) ? null : new XmlTimeSpan(o);
    }

    [XmlText]
    public long Default
    {
        get { return m_value.Ticks / TICKS_PER_MS; }
        set { m_value = new TimeSpan(value * TICKS_PER_MS); }
    }
}
Community
  • 1
  • 1
Mikhail
  • 1,223
  • 14
  • 25
  • The best and the easy way to solve this problem ... for me – Moraru Viorel May 12 '17 at 09:36
  • this is absolutely ingenious - I am super impressed! – Jim May 23 '17 at 11:45
  • 1
    Wow, super simple and working perfectly! This should rise to the top. Perhaps one possible chage could be to use XmlConvert.ToTimeSpan() / ToString() from Rory MacLeod's answer for better XML readability. – Vit Kovalcik Jan 14 '21 at 14:53
  • This approach will fail with .NET6 and .NET7 with this error message: "InvalidOperationException: The type for XmlElement may not be specified for primitive types.". The solution that worked for me is to implement the interface "IXmlSerializable" as suggested by another post. – wknauf Feb 03 '23 at 21:03
10

You could create a light wrapper around the TimeSpan struct:

namespace My.XmlSerialization
{
    public struct TimeSpan : IXmlSerializable
    {
        private System.TimeSpan _value;

        public static implicit operator TimeSpan(System.TimeSpan value)
        {
            return new TimeSpan { _value = value };
        }

        public static implicit operator System.TimeSpan(TimeSpan value)
        {
            return value._value;
        }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            _value = System.TimeSpan.Parse(reader.ReadContentAsString());
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteValue(_value.ToString());
        }
    }
}

Sample serialized result:

<Entry>
  <StartTime>2010-12-06T08:45:12.5</StartTime>
  <Duration>2.08:29:35.2500000</Duration>
</Entry>
phoog
  • 42,068
  • 6
  • 79
  • 117
  • any idea how to make the output as XmlAttribute? – ala Nov 24 '11 at 01:16
  • @ala, If I understand your question correctly, the answer is to apply the XmlAttributeAttribute to the property you want to express as an attribute. That is not particular to TimeSpan, of course. – phoog Nov 24 '11 at 02:58
  • +1 Nice, except I would not serialize it as string but the `Ticks` as long. – ChrisWue Jan 17 '13 at 20:11
  • 1
    @ChrisWue In my office we use xml serialization when we want human-readable output; serializing a timespan as a long isn't quite compatible with that goal. If you use xml serialization for a different reason, of course, serializing the ticks might make more sense. – phoog Jan 18 '13 at 15:26
8

A more readable option would be to serialize as a string and use the TimeSpan.Parse method to deserialize it. You could do the same as in your example but using TimeSpan.ToString() in the getter and TimeSpan.Parse(value) in the setter.

Liam
  • 27,717
  • 28
  • 128
  • 190
Rune Grimstad
  • 35,612
  • 10
  • 61
  • 76
3

My version of the solution.

[DataMember, XmlIgnore]
public TimeSpan MyTimeoutValue { get; set; }
[DataMember]
public string MyTimeout
{
    get { return MyTimeoutValue.ToString(); }
    set { MyTimeoutValue = TimeSpan.Parse(value); }
}

Edit: assuming it is nullable:

[DataMember, XmlIgnore]
public TimeSpan? MyTimeoutValue { get; set; }
[DataMember]
public string MyTimeout
{
    get 
    {
        if (MyTimeoutValue != null)
            return MyTimeoutValue.ToString();
        return null;
    }
    set 
    {
        TimeSpan outValue;
        if (TimeSpan.TryParse(value, out outValue))
            MyTimeoutValue = outValue;
        else
            MyTimeoutValue = null;
    }
}
Simone S.
  • 1,756
  • 17
  • 18
2

Another option would be to serialize it using the SoapFormatter class rather than the XmlSerializer class.

The resulting XML file looks a little different...some "SOAP"-prefixed tags, etc...but it can do it.

Here's what SoapFormatter serialized a timespan of 20 hours and 28 minutes serialized to:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

To use SOAPFormatter class, need to add reference to System.Runtime.Serialization.Formatters.Soap and use the namespace of the same name.

Liam
  • 27,717
  • 28
  • 128
  • 190
2

For .NET6 and .NET7, TimeSpan serialization works out of the box. The format is the format for XSD "duration" datatype. So "14:30" is serialized to PT14H30M

For .NET Framework 4.8, this behavior can be activated with this switch:

AppContext.SetSwitch("Switch.System.Xml.EnableTimeSpanSerialization", true);
wknauf
  • 160
  • 1
  • 7
1

Timespan stored in xml as number of seconds, but it is easy to adopt, I hope. Timespan serialized manually (implementing IXmlSerializable):

public class Settings : IXmlSerializable
{
    [XmlElement("IntervalInSeconds")]
    public TimeSpan Interval;

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteElementString("IntervalInSeconds", ((int)Interval.TotalSeconds).ToString());
    }

    public void ReadXml(XmlReader reader)
    {
        string element = null;
        while (reader.Read())
        {
            if (reader.NodeType == XmlNodeType.Element)
                element = reader.Name;
            else if (reader.NodeType == XmlNodeType.Text)
            {
                if (element == "IntervalInSeconds")
                    Interval = TimeSpan.FromSeconds(double.Parse(reader.Value.Replace(',', '.'), CultureInfo.InvariantCulture));
            }
       }
    }
}

There is more comprehensive example: https://bitbucket.org/njkazakov/timespan-serialization

Look at Settings.cs. And there is some tricky code to use XmlElementAttribute.

askazakov
  • 21
  • 1
  • 4
  • 1
    Please quote the relevant information from that link. All the information needed for your answer should be on this site, and then you could cite that link as a source. – SuperBiasedMan May 25 '15 at 07:03
1

If you do not want any workarounds, use the DataContractSerializer class from System.Runtime.Serialization.dll.

        using (var fs = new FileStream("file.xml", FileMode.Create))
        {
            var serializer = new DataContractSerializer(typeof(List<SomeType>));
            serializer.WriteObject(fs, _items);
        }
Sasha Yakobchuk
  • 471
  • 6
  • 12
  • Note that DataContractSerializer does not support things like XML attributes, but the answer's approach could be useful if you don't need XML attribute support. – Andrew D. Bond Sep 20 '21 at 08:18
0

For data contract serialization I use the following.

  • Keeping the serialized property private keeps the public interface clean.
  • Using the public property name for serialization keeps the XML clean.
Public Property Duration As TimeSpan

<DataMember(Name:="Duration")>
Private Property DurationString As String
    Get
        Return Duration.ToString
    End Get
    Set(value As String)
        Duration = TimeSpan.Parse(value)
    End Set
End Property
JRS
  • 1,438
  • 2
  • 16
  • 26
-2

Try this :

//Don't Serialize Time Span object.
        [XmlIgnore]
        public TimeSpan m_timeSpan;
//Instead serialize (long)Ticks and instantiate Timespan at time of deserialization.
        public long m_TimeSpanTicks
        {
            get { return m_timeSpan.Ticks; }
            set { m_timeSpan = new TimeSpan(value); }
        }