Comment sérialiser un TimeSpan en XML

206

J'essaie de sérialiser un TimeSpanobjet .NET en XML et cela ne fonctionne pas. Un rapide google a suggéré que bien qu'il TimeSpansoit sérialisable, le XmlCustomFormatterne fournit pas de méthodes pour convertir des TimeSpanobjets vers et depuis XML.

Une approche suggérée consistait à ignorer le TimeSpanpour la sérialisation, et à la place sérialiser le résultat de TimeSpan.Ticks(et l'utiliser new TimeSpan(ticks)pour la désérialisation). Un exemple de ceci suit:

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

Bien que cela semble fonctionner dans mes brefs tests - est-ce la meilleure façon d'y parvenir?

Existe-t-il un meilleur moyen de sérialiser un TimeSpan vers et depuis XML?

joeldixon66
la source
4
La réponse de Rory MacLeod ci-dessous est en fait la façon dont Microsoft recommande de le faire.
Jeff
2
Je n'utiliserais pas de longs ticks pour TimeSpand car le type de durée de XML est la correspondance exacte. Le problème a été signalé à Microsoft en 2008 mais n'a jamais été résolu. Il existe une solution de contournement documentée à l'époque: kennethxu.blogspot.com/2008/09/…
Kenneth Xu

Réponses:

71

La façon dont vous avez déjà posté est probablement la plus propre. Si vous n'aimez pas la propriété supplémentaire, vous pouvez l'implémenter IXmlSerializable, mais vous devez tout faire, ce qui vainc largement le point. J'utiliserais volontiers l'approche que vous avez publiée; il est (par exemple) efficace (pas d'analyse complexe, etc.), les nombres indépendants de la culture, sans ambiguïté et de type horodatage sont facilement et communément compris.

En passant, j'ajoute souvent:

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

Cela le cache simplement dans l'interface utilisateur et dans les références aux DLL, pour éviter toute confusion.

Marc Gravell
la source
5
Tout faire n'est pas si mal si vous implémentez l'interface sur une structure qui enveloppe System.TimeSpan, plutôt que de l'implémenter sur MyClass. Ensuite, il vous suffit de changer le type sur votre propriété
MyClass.TimeSinceLastEvent
103

Il ne s'agit que d'une légère modification de l'approche suggérée dans la question, mais ce problème Microsoft Connect recommande d'utiliser une propriété de sérialisation comme celle-ci:

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

Cela sérialiserait un TimeSpan de 0:02:45 comme:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Alternativement, le DataContractSerializerprend en charge TimeSpan.

Rory MacLeod
la source
15
+1 pour XmlConvert.ToTimeSpan (). Il gère la syntaxe de durée standard ISO pour un intervalle de temps comme PT2H15M, voir en.wikipedia.org/wiki/ISO_8601#Durations
yzorg
2
Corrigez-moi si je me trompe, mais le TimeSpan serlized "PT2M45S" est 00:02:45, pas 2:45:00.
Tom Pažourek
Le lien de connexion est maintenant rompu, peut-être peut-il être remplacé par celui-ci: connect.microsoft.com/VisualStudio/feedback/details/684819/… ? La technique semble aussi un peu différente ...
TJB
Une question étrange à suivre, avons-nous un moyen de désérialiser cette valeur PT2M45S à Time en SQL?
Xander
28

Quelque chose qui peut fonctionner dans certains cas est de donner à votre propriété publique un champ de support, qui est un TimeSpan, mais la propriété publique est exposée sous forme de chaîne.

par exemple:

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

C'est correct si la valeur de la propriété est utilisée principalement avec la classe contenante ou les classes héritées et est chargée à partir de la configuration xml.

Les autres solutions proposées sont meilleures si vous souhaitez que la propriété publique soit une valeur TimeSpan utilisable pour d'autres classes.

Nous s
la source
De loin la solution la plus simple. J'ai trouvé exactement la même chose et cela fonctionne comme un charme. Facile à mettre en œuvre et à comprendre.
wpfwannabe
1
C'est la meilleure solution ici. Il sérialise très bien !!! Merci pour votre contribution ami!
Développeur
25

En combinant une réponse de la sérialisation couleur et cette solution originale (qui est excellente en soi), j'ai obtenu cette solution:

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

où la XmlTimeSpanclasse est comme ça:

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
la source
Le meilleur et le moyen le plus simple de résoudre ce problème ... pour moi
Moraru Viorel
c'est absolument ingénieux - je suis super impressionné!
Jim
9

Vous pouvez créer un wrapper léger autour de la structure 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());
        }
    }
}

Exemple de résultat sérialisé:

<Entry>
  <StartTime>2010-12-06T08:45:12.5</StartTime>
  <Duration>2.08:29:35.2500000</Duration>
</Entry>
phoog
la source
une idée comment faire la sortie en tant que XmlAttribute?
ala
@ala, Si je comprends bien votre question, la réponse est d'appliquer le XmlAttributeAttribute à la propriété que vous souhaitez exprimer en tant qu'attribut. Ce n'est pas particulier à TimeSpan, bien sûr.
phoog
+1 Bien, sauf que je ne le sérialiserais pas en tant que chaîne mais Ticksaussi longtemps.
ChrisWue
@ChrisWue Dans mon bureau, nous utilisons la sérialisation xml lorsque nous voulons une sortie lisible par l'homme; sérialiser un intervalle de temps comme un long n'est pas tout à fait compatible avec cet objectif. Si vous utilisez la sérialisation xml pour une raison différente, bien sûr, la sérialisation des ticks pourrait avoir plus de sens.
phoog
8

Une option plus lisible serait de sérialiser en tant que chaîne et d'utiliser la TimeSpan.Parseméthode pour la désérialiser. Vous pouvez faire la même chose que dans votre exemple mais en utilisant TimeSpan.ToString()dans le getter et TimeSpan.Parse(value)dans le setter.

Rune Grimstad
la source
2

Une autre option serait de le sérialiser en utilisant la SoapFormatterclasse plutôt que leXmlSerializer classe.

Le fichier XML résultant semble un peu différent ... certaines balises préfixées "SOAP", etc ... mais il peut le faire.

Voici ce qui a SoapFormattersérialisé un intervalle de temps de 20 heures et 28 minutes sérialisé à:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Pour utiliser la classe SOAPFormatter, vous devez ajouter une référence System.Runtime.Serialization.Formatters.Soapet utiliser l'espace de noms du même nom.

Liam
la source
Voici comment il sérialise en .net 4.0
Kirk Broadhurst
1

Ma version de la solution :)

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

Edit: en supposant qu'il est 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;
    }
}
Gildor
la source
1

Timespan stocké en xml en nombre de secondes, mais il est facile à adopter, j'espère. Timespan sérialisé manuellement (implémentant 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));
            }
       }
    }
}

Il existe un exemple plus complet: https://bitbucket.org/njkazakov/timespan-serialization

Regardez Settings.cs. Et il y a du code difficile à utiliser XmlElementAttribute.

askazakov
la source
1
Veuillez citer les informations pertinentes à partir de ce lien. Toutes les informations nécessaires à votre réponse devraient se trouver sur ce site, et vous pourriez alors citer ce lien comme source.
SuperBiatedMan
0

Pour la sérialisation des contrats de données, j'utilise ce qui suit.

  • Garder la propriété sérialisée privée maintient l'interface publique propre.
  • L'utilisation du nom de propriété publique pour la sérialisation maintient le XML propre.
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
la source
0

Si vous ne souhaitez aucune solution de contournement, utilisez la classe DataContractSerializer de 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
la source
-2

Essaye ça :

//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
la source