Bagaimana cara membuat serial TimeSpan ke XML

206

Saya mencoba untuk membuat serial TimeSpanobjek .NET ke XML dan tidak berfungsi. Google cepat telah menyarankan bahwa sementara TimeSpanserializable, XmlCustomFormattertidak menyediakan metode untuk mengkonversi TimeSpanobjek ke dan dari XML.

Salah satu pendekatan yang disarankan adalah untuk mengabaikan TimeSpanserialisasi, dan bukannya membuat serialisasi hasil TimeSpan.Ticks(dan digunakan new TimeSpan(ticks)untuk deserialisasi). Contohnya sebagai berikut:

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

Meskipun ini tampaknya berfungsi dalam pengujian singkat saya - apakah ini cara terbaik untuk mencapai ini?

Apakah ada cara yang lebih baik untuk membuat serial TimeSpan ke dan dari XML?

joeldixon66
sumber
4
Jawaban Rory MacLeod di bawah ini sebenarnya adalah cara yang disarankan Microsoft untuk melakukan ini.
Jeff
2
Saya tidak akan menggunakan kutu panjang untuk TimeSpand karena jenis durasi XML adalah pencocokan tepat. Masalah ini diangkat ke Microsoft pada tahun 2008 tetapi tidak pernah terselesaikan. Ada solusi yang didokumentasikan saat itu: kennethxu.blogspot.com/2008/09/…
Kenneth Xu

Jawaban:

71

Cara Anda sudah memposting mungkin adalah yang terbersih. Jika Anda tidak menyukai properti tambahan, Anda bisa menerapkan IXmlSerializable, tetapi kemudian Anda harus melakukan segalanya , yang sebagian besar mengalahkan intinya. Saya dengan senang hati menggunakan pendekatan yang Anda posting; itu (misalnya) efisien (tidak ada penguraian kompleks dll), bebas tipe kultur, tidak ambigu, dan timestamp mudah dan umum dipahami.

Selain itu, saya sering menambahkan:

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

Ini hanya menyembunyikannya di UI dan referensi dll, untuk menghindari kebingungan.

Marc Gravell
sumber
5
Melakukan semuanya tidak terlalu buruk jika Anda mengimplementasikan antarmuka pada struct yang membungkus System.TimeSpan, daripada mengimplementasikannya pada MyClass. Maka Anda hanya perlu mengubah jenis pada properti MyClass Anda.
TimeSinceLastEvent
103

Ini hanya sedikit modifikasi pada pendekatan yang disarankan dalam pertanyaan, tetapi masalah Microsoft Connect ini merekomendasikan penggunaan properti untuk serialisasi seperti ini:

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

Ini akan membuat cerita berseri TimeSpan dari 0:02:45 sebagai:

<TimeSinceLastEvent>PT2M45S</TimeSinceLastEvent>

Atau, DataContractSerializerTimeSpan mendukung.

Rory MacLeod
sumber
15
+1 untuk XmlConvert.ToTimeSpan (). Ini menangani sintaks durasi standar ISO untuk rentang waktu seperti PT2H15M, lihat en.wikipedia.org/wiki/ISO_8601#Durations
yzorg
2
Perbaiki saya jika saya salah, tetapi TimeSpan yang disingkat "PT2M45S" adalah 00:02:45, bukan 2:45:00.
Tom Pažourek
Tautan penghubung sekarang rusak, mungkin bisa diganti dengan yang ini: connect.microsoft.com/VisualStudio/feedback/details/684819/… ? Tekniknya juga terlihat sedikit berbeda ...
TJB
Sebuah pertanyaan aneh untuk ditindaklanjuti, apakah kita memiliki cara deserialise nilai PT2M45S ini ke Time dalam SQL?
Xander
28

Sesuatu yang dapat bekerja dalam beberapa kasus adalah memberi properti umum Anda bidang dukungan, yang merupakan TimeSpan, tetapi properti publik terekspos sebagai string.

misalnya:

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

Ini ok jika nilai properti digunakan sebagian besar di kelas mengandung atau kelas warisan dan diambil dari konfigurasi xml.

Solusi yang diusulkan lainnya lebih baik jika Anda ingin properti publik menjadi nilai TimeSpan yang dapat digunakan untuk kelas lain.

Wes
sumber
Sejauh ini solusi termudah. Saya telah menemukan hal yang persis sama dan itu berfungsi seperti pesona. Mudah diimplementasikan dan dipahami.
wpfwannabe
1
Ini solusi terbaik di sini. Ini bersambung sangat baik !!! Terima kasih atas masukan teman Anda!
Pengembang
25

Menggabungkan jawaban dari serialisasi Warna dan solusi asli ini (yang sangat bagus dengan sendirinya) Saya mendapatkan solusi ini:

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

di mana XmlTimeSpankelas seperti ini:

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
sumber
Cara terbaik dan mudah untuk menyelesaikan masalah ini ... bagi saya
Moraru Viorel
ini benar-benar cerdik - Saya sangat terkesan!
Jim
9

Anda bisa membuat pembungkus ringan di sekitar struct 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());
        }
    }
}

Contoh hasil serial:

<Entry>
  <StartTime>2010-12-06T08:45:12.5</StartTime>
  <Duration>2.08:29:35.2500000</Duration>
</Entry>
phoog
sumber
tahu bagaimana cara membuat output sebagai XmlAttribute?
ala
@ala, Jika saya memahami pertanyaan Anda dengan benar, jawabannya adalah menerapkan XmlAttributeAttribute ke properti yang ingin Anda ungkapkan sebagai atribut. Itu tidak khusus untuk TimeSpan, tentu saja.
phoog
+1 Bagus, kecuali saya tidak akan membuat serial sebagai string tetapi Ticksselama.
ChrisWue
@ChrisWue Di kantor saya, kami menggunakan serialisasi xml ketika kami ingin keluaran yang bisa dibaca manusia; membuat serial suatu rentang waktu sebagai panjang tidak cukup kompatibel dengan tujuan itu. Jika Anda menggunakan serialisasi xml untuk alasan yang berbeda, tentu saja, membuat serial kutu mungkin lebih masuk akal.
phoog
8

Opsi yang lebih mudah dibaca adalah membuat cerita bersambung sebagai string dan menggunakan TimeSpan.Parsemetode ini untuk membatalkan deserialisasi. Anda bisa melakukan hal yang sama seperti pada contoh Anda tetapi menggunakan TimeSpan.ToString()pada pengambil dan TimeSpan.Parse(value)penyetel.

Rune Grimstad
sumber
2

Pilihan lain adalah membuat cerita bersambung menggunakan SoapFormatterkelas daripada XmlSerializerkelas.

File XML yang dihasilkan terlihat sedikit berbeda ... beberapa "SABUN" - tag yang telah diperbaiki, dll ... tetapi dapat melakukannya.

Berikut ini SoapFormatterserialisasi jangka waktu 20 jam dan 28 menit untuk serial:

<myTimeSpan>P0Y0M0DT20H28M0S</myTimeSpan>

Untuk menggunakan kelas SOAPFormatter, perlu menambahkan referensi ke System.Runtime.Serialization.Formatters.Soapdan menggunakan namespace dengan nama yang sama.

Liam
sumber
Ini adalah bagaimana serialisasi di .net 4.0
Kirk Broadhurst
1

Versi solusi saya :)

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

Edit: dengan asumsi itu 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
sumber
1

Timespan disimpan dalam xml sebagai jumlah detik, tetapi mudah untuk diadopsi, saya harap. Timespan diserialisasi secara manual (menerapkan 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));
            }
       }
    }
}

Ada contoh yang lebih komprehensif: https://bitbucket.org/njkazakov/timespan-serialization

Lihatlah Settings.cs. Dan ada beberapa kode rumit untuk menggunakan XmlElementAttribute.

askazakov
sumber
1
Silakan mengutip informasi yang relevan dari tautan itu. Semua informasi yang diperlukan untuk jawaban Anda harus ada di situs ini, dan kemudian Anda dapat mengutip tautan itu sebagai sumber.
SuperBiasedMan
0

Untuk serialisasi kontrak data, saya menggunakan yang berikut ini.

  • Menjaga properti berseri tetap menjaga antarmuka publik tetap bersih.
  • Menggunakan nama properti publik untuk serialisasi menjaga XML tetap bersih.
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
sumber
0

Jika Anda tidak ingin ada solusi, gunakan kelas DataContractSerializer dari 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
sumber
-2

Coba ini :

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