Cómo serializar un TimeSpan a XML

206

Estoy tratando de serializar un TimeSpanobjeto .NET a XML y no funciona. Un rápido google ha sugerido que si bien TimeSpanes serializable, XmlCustomFormatterno proporciona métodos para convertirTimeSpan objetos ay desde XML.

Un enfoque sugerido era ignorar la TimeSpanserialización y, en su lugar, serializar el resultado de TimeSpan.Ticks(y usarla new TimeSpan(ticks)para la deserialización). Un ejemplo de esto sigue:

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

Si bien esto parece funcionar en mis breves pruebas, ¿es esta la mejor manera de lograrlo?

¿Hay una mejor manera de serializar un TimeSpan hacia y desde XML?

joeldixon66
fuente
44
La respuesta de Rory MacLeod a continuación es en realidad la forma en que Microsoft recomienda hacer esto.
Jeff
2
No usaría ticks largos para TimeSpand porque el tipo de duración de XML es la coincidencia exacta. El problema se planteó a Microsoft en el año 2008, pero nunca se resolvió. Hay una solución documentada en ese entonces: kennethxu.blogspot.com/2008/09/…
Kenneth Xu

Respuestas:

71

La forma en que ya ha publicado es probablemente la más limpia. Si no le gusta la propiedad adicional, puede implementarla IXmlSerializable, pero luego tiene que hacer todo , lo que en gran medida anula el punto. Me encantaría usar el enfoque que publicaste; es (por ejemplo) eficiente (sin análisis complejo, etc.), independiente de la cultura, inequívoco, y los números de tipo de marca de tiempo se entienden fácil y comúnmente.

Como comentario aparte, a menudo agrego:

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

Esto solo lo oculta en la interfaz de usuario y en referencias dlls, para evitar confusiones.

Marc Gravell
fuente
55
Hacer todo no es tan malo si implementa la interfaz en una estructura que envuelve System.TimeSpan, en lugar de implementarla en MyClass. Entonces solo tiene que cambiar el tipo en su propiedad
MyClass.TimeSinceLastEvent
103

Esta es solo una ligera modificación en el enfoque sugerido en la pregunta, pero este problema de Microsoft Connect recomienda usar una propiedad para la serialización 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); 
    }
}

Esto serializaría un TimeSpan de 0:02:45 como:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Alternativamente, el DataContractSerializersoporte TimeSpan.

Rory MacLeod
fuente
15
+1 para XmlConvert.ToTimeSpan (). Maneja la sintaxis de duración estándar ISO para intervalos de tiempo como PT2H15M, ver en.wikipedia.org/wiki/ISO_8601#Durations
yzorg
2
Corrígeme si me equivoco, pero el TimeSpan "PT2M45S" serlizado es 00:02:45, no 2:45:00.
Tom Pažourek
El enlace de conexión ahora está roto, tal vez se pueda reemplazar con este: connect.microsoft.com/VisualStudio/feedback/details/684819/… ? La técnica también se ve un poco diferente ...
TJB
Una pregunta extraña para seguir, ¿tenemos una manera de deserializar este valor PT2M45S a Tiempo en SQL?
Xander
28

Algo que puede funcionar en algunos casos es darle a su propiedad pública un campo de respaldo, que es un TimeSpan, pero la propiedad pública está expuesta como una cadena.

p.ej:

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

Esto está bien si el valor de la propiedad se usa principalmente con la clase que contiene o las clases heredadas y se carga desde la configuración xml.

Las otras soluciones propuestas son mejores si desea que la propiedad pública sea un valor TimeSpan utilizable para otras clases.

Wes
fuente
Con mucho, la solución más fácil. Se me ocurrió exactamente lo mismo y funciona a las mil maravillas. Fácil de implementar y entender.
wpfwannabe
1
Esta es la mejor solución aquí. Se serializa muy bien !!! Gracias por tu entrada amigo!
Desarrollador
25

Combinando una respuesta de serialización de color y esta solución original (que es genial por sí misma) obtuve esta solución:

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

donde XmlTimeSpanclase es así:

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
fuente
La mejor y la forma más fácil de resolver este problema ... para mí
Moraru Viorel
Esto es absolutamente ingenioso. ¡Estoy súper impresionado!
Jim
9

Puede crear un contenedor ligero alrededor de la estructura 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());
        }
    }
}

Resultado serializado de muestra:

<Entry>
  <StartTime>2010-12-06T08:45:12.5</StartTime>
  <Duration>2.08:29:35.2500000</Duration>
</Entry>
phoog
fuente
alguna idea de cómo hacer que la salida como XmlAttribute?
ala
@ala, si entiendo su pregunta correctamente, la respuesta es aplicar XmlAttributeAttribute a la propiedad que desea expresar como un atributo. Eso no es particular de TimeSpan, por supuesto.
phoog
+1 Agradable, excepto que no lo serializaría como una cadena, sino todo el Tickstiempo.
ChrisWue
@ChrisWue En mi oficina utilizamos la serialización xml cuando queremos una salida legible por humanos; serializar un intervalo de tiempo como largo no es del todo compatible con ese objetivo. Si usa la serialización xml por una razón diferente, por supuesto, serializar los ticks podría tener más sentido.
phoog
8

Una opción más legible sería serializar como una cadena y usar el TimeSpan.Parsemétodo para deserializarla. Podrías hacer lo mismo que en tu ejemplo pero usando TimeSpan.ToString()en el getter y TimeSpan.Parse(value)en el setter.

Runa Grimstad
fuente
2

Otra opción sería serializarlo usando la SoapFormatterclase en lugar de la XmlSerializerclase.

El archivo XML resultante se ve un poco diferente ... algunas etiquetas con prefijo "SOAP", etc ... pero puede hacerlo.

Esto es lo que se SoapFormatterserializó en un intervalo de tiempo de 20 horas y 28 minutos serializado para:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Para usar la clase SOAPFormatter, es necesario agregar referencias System.Runtime.Serialization.Formatters.Soapy usar el espacio de nombres del mismo nombre.

Liam
fuente
Así es como se serializa en .net 4.0
Kirk Broadhurst
1

Mi versión de la solución :)

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

Editar: suponiendo que es anulable ...

[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
fuente
1

El intervalo de tiempo almacenado en xml como número de segundos, pero espero que sea fácil de adoptar. Intervalo de tiempo 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));
            }
       }
    }
}

Hay un ejemplo más completo: https://bitbucket.org/njkazakov/timespan-serialization

Mira Settings.cs. Y hay un código complicado para usar XmlElementAttribute.

askazakov
fuente
1
Cite la información relevante de ese enlace. Toda la información necesaria para su respuesta debe estar en este sitio, y luego podría citar ese enlace como fuente.
SuperBiasedMan
0

Para la serialización del contrato de datos, uso lo siguiente.

  • Mantener la propiedad serializada en privado mantiene limpia la interfaz pública.
  • El uso del nombre de propiedad pública para la serialización mantiene limpio el 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
JRS
fuente
0

Si no desea ninguna solución alternativa, use la clase 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
fuente
-2

Prueba esto :

//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
fuente