Come serializzare un TimeSpan in XML


206

Sto cercando di serializzare un TimeSpanoggetto .NET su XML e non funziona. Un rapido google ha suggerito che sebbene TimeSpansia serializzabile, XmlCustomFormatternon fornisce metodi per convertire TimeSpanoggetti da e verso XML.

Un approccio suggerito era di ignorare la TimeSpanserializzazione e invece serializzare il risultato di TimeSpan.Ticks(e utilizzarlo new TimeSpan(ticks)per la deserializzazione). Ne segue un esempio:

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

Mentre questo sembra funzionare nei miei brevi test - è questo il modo migliore per raggiungere questo obiettivo?

Esiste un modo migliore per serializzare un TimeSpan da e verso XML?


4
La risposta di Rory MacLeod di seguito è in realtà il modo in cui Microsoft consiglia di farlo.
Jeff

2
Non vorrei usare segni di spunta lunghi per TimeSpand perché il tipo di durata XML è la corrispondenza esatta. Il problema è stato sollevato a Microsoft nel 2008 ma non è mai stato risolto. C'è una soluzione alternativa documentata allora: kennethxu.blogspot.com/2008/09/…
Kenneth Xu,

Risposte:


71

Il modo in cui hai già pubblicato è probabilmente il più pulito. Se non ti piace la proprietà extra, potresti implementarla IXmlSerializable, ma poi devi fare tutto , il che in gran parte sconfigge il punto. Utilizzerei felicemente l'approccio che hai pubblicato; è (per esempio) efficiente (senza analisi complesse ecc.), numeri di cultura indipendenti, inequivocabili e di tipo timestamp sono facilmente e comunemente compresi.

A parte, aggiungo spesso:

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

Questo lo nasconde solo nell'interfaccia utente e nel riferimento alle DLL, per evitare confusione.


5
Fare tutto non è poi così male se si implementa l'interfaccia su una struttura che avvolge System.TimeSpan, anziché implementarla su MyClass. Quindi devi solo cambiare il tipo sulla tua proprietà MyClass.TimeSinceLastEvent
phoog

103

Questa è solo una leggera modifica dell'approccio suggerito nella domanda, ma questo problema di Microsoft Connect consiglia di utilizzare una proprietà per la serializzazione come questa:

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

Ciò serializzerebbe un intervallo di tempo di 0:02:45 come:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

In alternativa, DataContractSerializersupporta TimeSpan.


15
+1 per XmlConvert.ToTimeSpan (). Gestisce la sintassi della durata standard ISO per periodi come PT2H15M, vedi en.wikipedia.org/wiki/ISO_8601#Durations
yzorg

2
Correggimi se sbaglio, ma TimeSpan "PT2M45S" serializzato è 00:02:45, non 2:45:00.
Tom Pažourek il

Il collegamento di connessione ora è interrotto, forse può essere sostituito con questo: connect.microsoft.com/VisualStudio/feedback/details/684819/… ? Anche la tecnica sembra un po 'diversa ...
TJB,

Una strana domanda a cui dare seguito, abbiamo un modo per deserializzare questo valore PT2M45S su Time in SQL?
Xander

28

Qualcosa che può funzionare in alcuni casi è di dare alla tua proprietà pubblica un campo di supporto, che è un TimeSpan, ma la proprietà pubblica è esposta come una stringa.

per esempio:

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

Questo va bene se il valore della proprietà viene usato principalmente con la classe contenente o ereditando le classi e viene caricato dalla configurazione xml.

Le altre soluzioni proposte sono migliori se si desidera che la proprietà pubblica sia un valore TimeSpan utilizzabile per altre classi.


Di gran lunga la soluzione più semplice. Ho escogitato esattamente la stessa cosa e funziona come un incantesimo. Facile da implementare e capire.
wpfwannabe,

1
Questa è la soluzione migliore qui. Serializza molto bene !!! Grazie per l'input amico!
Sviluppatore

25

Combinando una risposta dalla serializzazione del colore e questa soluzione originale (che è eccezionale di per sé) ho ottenuto questa soluzione:

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

dove la XmlTimeSpanclasse è così:

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

Il meglio e il modo più semplice per risolvere questo problema ... per me
Moraru Viorel

questo è assolutamente geniale - Sono molto impressionato!
Jim,

9

È possibile creare un involucro di luce attorno alla struttura 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());
        }
    }
}

Esempio di risultato serializzato:

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

qualche idea su come rendere l'output come XmlAttribute?
ala

@ala, se capisco correttamente la tua domanda, la risposta è applicare XmlAttributeAttribute alla proprietà che vuoi esprimere come attributo. Questo non è particolare per TimeSpan, ovviamente.
phoog

+1 Bello, tranne che non lo serializzerei come stringa ma il Tickspiù a lungo.
ChrisWue,

@ChrisWue Nel mio ufficio usiamo la serializzazione xml quando vogliamo output leggibili dall'uomo; serializzare un intervallo di tempo non è del tutto compatibile con tale obiettivo. Se usi la serializzazione xml per un motivo diverso, ovviamente, la serializzazione delle zecche potrebbe avere più senso.
phoog

8

Un'opzione più leggibile sarebbe quella di serializzare come stringa e utilizzare il TimeSpan.Parsemetodo per deserializzarlo. Potresti fare lo stesso del tuo esempio ma usando TimeSpan.ToString()nel getter e TimeSpan.Parse(value)nel setter.


2

Un'altra opzione sarebbe quella di serializzarlo usando la SoapFormatterclasse anziché la XmlSerializerclasse.

Il file XML risultante ha un aspetto leggermente diverso ... alcuni tag prefissati "SOAP", ecc ... ma può farlo.

Ecco cosa SoapFormatterserializzato un periodo di 20 ore e 28 minuti serializzato per:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Per utilizzare la classe SOAPFormatter, è necessario aggiungere un riferimento System.Runtime.Serialization.Formatters.Soape utilizzare lo spazio dei nomi con lo stesso nome.


Ecco come serializza in .net 4.0
Kirk Broadhurst l'

1

La mia versione della soluzione :)

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

Modifica: supponendo che sia 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;
    }
}

1

Il periodo di tempo memorizzato in xml come numero di secondi, ma spero sia facile da adottare. Timepan serializzato 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));
            }
       }
    }
}

C'è un esempio più completo: https://bitbucket.org/njkazakov/timespan-serialization

Guarda Settings.cs. E c'è un codice complicato per usare XmlElementAttribute.


1
Si prega di citare le informazioni pertinenti da quel link. Tutte le informazioni necessarie per la tua risposta dovrebbero essere su questo sito, e quindi potresti citare quel link come fonte.
SuperBiasedMan,

0

Per la serializzazione del contratto dati utilizzo quanto segue.

  • Mantenere la proprietà serializzata privata mantiene pulita l'interfaccia pubblica.
  • L'uso del nome della proprietà pubblica per la serializzazione mantiene pulito l'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

Se non si desidera risolvere il problema, utilizzare la classe DataContractSerializer da 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

Prova questo :

//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); }
        }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.