Como serializar um TimeSpan para XML


206

Estou tentando serializar um TimeSpanobjeto .NET para XML e ele não está funcionando. Um google rápido sugeriu que, embora TimeSpanseja serializável, o XmlCustomFormatternão fornece métodos para converter TimeSpanobjetos de e para XML.

Uma abordagem sugerida foi ignorar a TimeSpanserialização e, em vez disso, serializar o resultado de TimeSpan.Ticks(e usar new TimeSpan(ticks)para desserialização). Um exemplo disso é o seguinte:

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

Embora isso pareça funcionar nos meus breves testes - esta é a melhor maneira de conseguir isso?

Existe uma maneira melhor de serializar um TimeSpan de e para XML?


4
A resposta de Rory MacLeod abaixo é realmente a maneira como a Microsoft recomenda fazer isso.
— 8114 Jeff

2
Eu não usaria ticks longos para o TimeSpand porque o tipo de duração do XML é a correspondência exata. O problema foi levantado para a Microsoft no ano de 2008, mas nunca foi resolvido. Há uma solução alternativa documentada na época: kennethxu.blogspot.com/2008/09/…
— Kenneth Xu

Respostas:


71

A maneira como você já postou é provavelmente a mais limpa. Se você não gosta da propriedade extra, pode implementar IXmlSerializable, mas precisa fazer tudo , o que derrota amplamente o argumento. Eu ficaria feliz em usar a abordagem que você postou; é (por exemplo) eficiente (sem análise complexa, etc.), números independentes de cultura, inequívocos e do tipo de carimbo de data e hora são fácil e comumente entendidos.

Como um aparte, costumo acrescentar:

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

Isso apenas oculta na interface do usuário e nas dlls de referência, para evitar confusão.


5
Fazer tudo não é tão ruim se você implementar a interface em uma estrutura que envolve System.TimeSpan, em vez de implementá-la no MyClass. Então você só precisa alterar o tipo na sua propriedade MyClass.TimeSinceLastEvent
— phoog

103

Essa é apenas uma pequena modificação na abordagem sugerida na pergunta, mas esse problema do Microsoft Connect recomenda o uso de uma propriedade para serialização como esta:

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

Isso serializaria um TimeSpan de 0:02:45 como:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Como alternativa, o DataContractSerializerTimeSpan suporta.


15
+1 para XmlConvert.ToTimeSpan (). Ele lida com a sintaxe de duração padrão ISO para período como PT2H15M, consulte en.wikipedia.org/wiki/ISO_8601#Durations
— yzorg 8/12

2
Corrija-me se estiver errado, mas o TimeSpan "PT2M45S" serlizado é 00:02:45, não 2:45:00.
— Tom Pažourek

O link de conexão agora está quebrado, talvez possa ser substituído por este: connect.microsoft.com/VisualStudio/feedback/details/684819/… ? A técnica também parece um pouco diferente ...
— TJB

Uma pergunta estranha para acompanhar, temos uma maneira de desserializar esse valor PT2M45S para Time no SQL?
— Xander

28

Algo que pode funcionar em alguns casos é dar à sua propriedade pública um campo de apoio, que é um TimeSpan, mas a propriedade pública é exposta como uma sequência.

por exemplo:

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

Isso está ok se o valor da propriedade for usado principalmente com a classe que contém ou herdar classes e for carregado a partir da configuração xml.

As outras soluções propostas são melhores se você deseja que a propriedade pública seja um valor TimeSpan utilizável para outras classes.


De longe a solução mais fácil. Eu inventei exatamente a mesma coisa e funciona como um encanto. Fácil de implementar e entender.
— Wpfwannabe

1
Esta é a melhor solução aqui. Serializa muito bem !!! Obrigado por você entrar amigo!
— Desenvolvedor

25

Combinando uma resposta da serialização de cores e esta solução original (que é ótima por si só), obtive esta solução:

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

onde XmlTimeSpanclasse é assim:

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

O melhor e da maneira mais fácil de resolver este problema ... para mim
— Moraru Viorel

isso é absolutamente engenhoso - estou super impressionado!
— 23717 Jim

9

Você pode criar um invólucro leve em torno da estrutura do 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());
        }
    }
}

Exemplo de resultado serializado:

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

alguma idéia de como fazer a saída como XmlAttribute?
— Ala

@ala, se eu entendi sua pergunta corretamente, a resposta é aplicar o XmlAttributeAttribute à propriedade que você deseja expressar como um atributo. Isso não é específico do TimeSpan, é claro.
— Phoog #

+1 Legal, exceto que eu não a serializaria como string, mas pelo Tickstempo.
— precisa saber é o seguinte

@ChrisWue No meu escritório, usamos serialização xml quando queremos uma saída legível por humanos; a serialização de um período de tempo não é totalmente compatível com esse objetivo. Se você usar a serialização xml por um motivo diferente, é claro, serializar os ticks pode fazer mais sentido.
— Phoog

8

Uma opção mais legível seria serializar como uma string e usar o TimeSpan.Parsemétodo para desserializá-la. Você pode fazer o mesmo que no seu exemplo, mas usando TimeSpan.ToString()no getter e TimeSpan.Parse(value)no setter.


2

Outra opção seria serializá-lo usando o SoapFormatter classe e não oXmlSerializer classe.

O arquivo XML resultante parece um pouco diferente ... algumas tags com prefixo "SOAP", etc ... mas pode fazê-lo.

Aqui está o que SoapFormatterserializou um período de 20 horas e 28 minutos para:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Para usar a classe SOAPFormatter, é necessário adicionar referência System.Runtime.Serialization.Formatters.Soape usar o espaço para nome com o mesmo nome.


Esta é a forma como ele serializa em .NET 4.0
— Kirk Broadhurst

1

Minha versão da solução :)

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

Edit: assumindo que é anulável ...

[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

Período de tempo armazenado em xml como número de segundos, mas é fácil de adotar, espero. Timespan serializado manualmente (implementando 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));
            }
       }
    }
}

Há um exemplo mais abrangente: https://bitbucket.org/njkazakov/timespan-serialization

Veja Settings.cs. E há algum código complicado para usar XmlElementAttribute.


1
Por favor, cite as informações relevantes nesse link. Todas as informações necessárias para sua resposta devem estar neste site e, em seguida, você pode citar esse link como fonte.
— SuperBiatedMan

0

Para serialização de contrato de dados, uso o seguinte.

  • Manter a propriedade serializada privada mantém a interface pública limpa.
  • O uso do nome da propriedade pública para serialização mantém o XML limpo.
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

Se você não deseja nenhuma solução alternativa, use a classe DataContractSerializer do System.Runtime.Serialization.dll.

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

-2

Tente o seguinte:

//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); }
        }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.