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?

joeldixon66
fonte
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.

Marc Gravell
fonte
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.

Rory MacLeod
fonte
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.

Wes
fonte
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); }
    }
}
Mikhail
fonte
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>
phoog
fonte
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.

Rune Grimstad
fonte
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.

Liam
fonte
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;
    }
}
Gildor
fonte
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.

askazakov
fonte
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
JRS
fonte
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);
        }
Sasha Yakobchuk
fonte
-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); }
        }
Manvendra
fonte