如何将TimeSpan序列化为XML


206

我正在尝试将.NET TimeSpan对象序列化为XML,但无法正常工作。一个快速的谷歌建议,虽然TimeSpan可序列化,XmlCustomFormatter但不提供将TimeSpan对象与XML 相互转换的方法。

一种建议的方法是忽略TimeSpan序列化,而序列化结果TimeSpan.Ticks(并new TimeSpan(ticks)用于反序列化)。下面是一个示例:

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

尽管这在我的简短测试中似乎可行,但这是实现此目标的最佳方法吗?

有没有更好的方法来将TimeSpan序列化为XML?


4
Rory MacLeod在下面的回答实际上是Microsoft建议这样做的方式。
杰夫

2
因为XML的持续时间类型完全匹配,所以我不会对TimeSpand使用较长的滴答声。该问题在2008年提出给Microsoft,但从未解决。当时记录了一种解决方法:kennethxu.blogspot.com/2008/09/…–
Kenneth Xu

Answers:


71

您已经发布的方式可能是最干净的。如果您不喜欢额外的属性,则可以实现IXmlSerializable,但是您必须做所有事情,这在很大程度上是不对的。我很乐意使用您发布的方法;它是(例如)高效的(无需复杂的解析等),独立于文化的,明确的和时间戳类型的数字很容易且通常被理解。

顺便说一句,我经常添加:

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

这只是将其隐藏在UI和引用dll中,以避免造成混淆。


5
这样做的一切,如果你实现对包装System.TimeSpan,而不是实现它MyClass的一个结构的接口并没有那么糟糕。然后,您只需要更改MyClass.TimeSinceLastEvent属性的类型
phoog 2010年

103

这只是对问题中建议的方法的稍作修改,但是此Microsoft Connect问题建议使用属性进行序列化,如下所示:

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

这样可以将TimeSpan的0:02:45序列化为:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

或者,DataContractSerializer支持TimeSpan。


15
XmlConvert.ToTimeSpan()+1。它处理时间跨度的ISO标准持续时间语法,例如PT2H15M,请参见en.wikipedia.org/wiki/ISO_8601#Durations
yzorg 2012年

2
如果我错了,请纠正我,但是序列化的时间跨度“ PT2M45S”是00:02:45,而不是2:45:00。
汤姆·帕索里克(TomPažourek),

现在,连接链接已断开,也许可以用以下链接代替:connect.microsoft.com/VisualStudio/feedback/details/684819/…?该技术看起来也有些不同...
TJB 2015年

后续有一个奇怪的问题,我们是否有办法在SQL中将此值PT2M45S反序列化为Time?
Xander

28

在某些情况下可以使用的方法是为您的公共财产提供一个后备字段,即TimeSpan,但是公共财产以字符串形式公开。

例如:

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

如果属性值通常在包含类或继承类中使用,并且是从xml配置加载的,则可以。

如果您希望公共属性成为其他类的可用TimeSpan值,则其他建议的解决方案会更好。


到目前为止,最简单的解决方案。我想出了完全一样的东西,它就像一个魅力。易于实施和理解。
wpfwannabe 2011年

1
这是最好的解决方案。它序列化非常好!!!谢谢您的输入朋友!
开发人员

25

结合颜色序列化的答案和这个原始解决方案(这本身就很棒),我得到了以下解决方案:

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

这里的XmlTimeSpan类是这样的:

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

解决这个问题的最佳简便方法...对我来说
Moraru Viorel

这绝对是巧妙的-我印象深刻!
吉姆(Jim)

9

您可以围绕TimeSpan结构创建一个光包装器:

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

样本序列化结果:

<Entry>
  <StartTime>2010-12-06T08:45:12.5</StartTime>
  <Duration>2.08:29:35.2500000</Duration>
</Entry>

任何想法如何使输出作为XmlAttribute?
ala

@ala,如果我正确理解了您的问题,答案是将XmlAttributeAttribute应用于要表示为属性的属性。当然,TimeSpan并非如此。
phoog

+1不错,除了我不会将其序列化为字符串,而是将Ticks其序列化。
ChrisWue 2013年

@ChrisWue在我的办公室中,当我们需要人类可读的输出时,我们使用xml序列化;将时间序列序列化很长时间与该目标不太兼容。当然,如果出于其他原因使用xml序列化,则序列号化序列可能更有意义。
phoog

8

更具可读性的选项是将序列化为字符串并使用该TimeSpan.Parse方法反序列化它。您可以执行与示例相同的操作,但可以TimeSpan.ToString()在getter和TimeSpan.Parse(value)setter中使用。


2

另一种选择是使用SoapFormatter类而不是XmlSerializer类对其进行序列化。

生成的XML文件看起来有些不同……有些带有“ SOAP”前缀的标记等,但是它可以做到。

以下是SoapFormatter将20小时28分钟的时间序列化为:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

要使用SOAPFormatter类,需要添加引用System.Runtime.Serialization.Formatters.Soap并使用相同名称的名称空间。


这就是它在.net 4.0中的序列化方式
Kirk Broadhurst,2010年

1

我的解决方案版本:)

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

编辑:假设它可以为空...

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

1

我希望,时间跨度以秒为单位存储在xml中,但是很容易采用。手动将Timespan序列化(实现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));
            }
       }
    }
}

有更全面的示例: https //bitbucket.org/njkazakov/timespan-serialization

查看Settings.cs。还有一些棘手的代码可以使用XmlElementAttribute。


1
请引用该链接中的相关信息。答案所需的所有信息都应在此站点上,然后您可以将该链接作为来源。
SuperBiasedMan 2015年

0

对于数据协定序列化,我使用以下内容。

  • 将序列化的属性设为私有可保持公共接口的清洁。
  • 使用公共属性名称进行序列化可以保持XML的整洁。
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

0

如果不需要任何解决方法,请使用System.Runtime.Serialization.dll中的DataContractSerializer类。

        using (var fs = new FileStream("file.xml", FileMode.Create))
        {
            var serializer = new DataContractSerializer(typeof(List<SomeType>));
            serializer.WriteObject(fs, _items);
        }

-2

试试这个 :

//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); }
        }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.