Cómo serializar un intervalo de tiempo en XML


Estoy intentando serializar un objeto.NET TimeSpan en XML y no funciona. Un rápido Google ha sugerido que mientras TimeSpan es serializable, el XmlCustomFormatter no proporciona métodos para convertir TimeSpan objetos hacia y desde XML.

Un enfoque sugerido fue ignorar el TimeSpan para la serialización, y en su lugar serializar el resultado de TimeSpan.Ticks (y usar new TimeSpan(ticks) para la deserialización). Un ejemplo de esto es el siguiente:

[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 mi breve prueba, ¿es esta la mejor manera de lograr esto?

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

Author: John Saunders, 2009-03-12

13 answers

La forma en que ya has publicado es probablemente la más limpia. Si no te gusta la propiedad extra, podrías implementar IXmlSerializable, pero entonces tienes que hacer todo, lo que en gran medida frustra el punto. Me encantaría usar el enfoque que has publicado; es (por ejemplo) eficiente (sin análisis complejo, etc.), independiente de la cultura, sin ambigüedades, y los números de tipo de marca de tiempo son fáciles y comúnmente entendidos.

Como un aparte, a menudo añado:

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

Esto solo lo oculta en la interfaz de usuario y en haciendo referencia a DLL, para evitar confusiones.

 64
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-12 10:08:54

Esto es solo una ligera modificación en el enfoque sugerido en la pregunta, pero este problema de Microsoft Connect recomienda usar una propiedad para 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 un intervalo de tiempo de 0:02:45 como:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Alternativamente, el DataContractSerializer soporta TimeSpan.

 88
Author: Rory MacLeod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-21 15:58:02

Algo que puede funcionar en algunos casos es darle a su propiedad pública un campo de respaldo, que es un intervalo de tiempo, pero la propiedad pública se expone como una cadena.

Eg:

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 en la clase contenedora o clases herederas y se carga desde la configuración xml.

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

 28
Author: Wes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-06 19:01:17

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 XmlTimeSpan la clase 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); }
    }
}
 18
Author: Mikhail,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:40

Puede crear una envoltura de luz 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 la muestra:

<Entry>
  <StartTime>2010-12-06T08:45:12.5</StartTime>
  <Duration>2.08:29:35.2500000</Duration>
</Entry>
 8
Author: phoog,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-12-08 21:05:38

Una opción más legible sería serializar como una cadena y usar el método TimeSpan.Parse 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.

 8
Author: Rune Grimstad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-28 15:19:30

Otra opción sería serializarlo usando la clase SoapFormatter en lugar de la clase XmlSerializer.

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

Esto es lo que SoapFormatter serializó un intervalo de tiempo de 20 horas y 28 minutos serializado a:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Para usar la clase SoapFormatter, necesita agregar referencia a System.Runtime.Serialization.Formatters.Soap y usar el espacio de nombres del mismo nombre.

 2
Author: Liam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-28 15:20:12

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: asumiendo que es 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
Author: Gildor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-14 10:54:46

Timespan almacenado en xml como número de segundos, pero es fácil de adoptar, 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));
            }
       }
    }
}

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

Mira la configuración.cs. Y hay un código complicado para usar XmlElementAttribute.

 1
Author: askazakov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-26 08:14:31

Para la serialización de contratos de datos utilizo lo siguiente.

  • Mantener la propiedad serializada privada mantiene limpia la interfaz pública.
  • El uso del nombre de propiedad pública para la serialización mantiene el XML limpio.
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
Author: JRS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-03 01:10:43

No se puede comentar ni clasificar, pero el comentario SoapDuration

[XmlElement, Type=SoapDuration]
public TimeSpan TimeSinceLastEvent

O

public SoapDuration TimeSinceLastEventTicks
 0
Author: david valentine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-31 09:02:05

Si no desea ninguna solución alternativa, utilice la clase DataContractSerializer de System.Ejecución.Serialización.DLL.

        using (var fs = new FileStream("file.xml", FileMode.Create))
        {
            var serializer = new DataContractSerializer(typeof(List<SomeType>));
            serializer.WriteObject(fs, _items);
        }
 0
Author: Sasha Yakobchuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-21 18:13:49

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); }
        }
 -2
Author: Manvendra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-09 04:17:45