90

I have a XSD schema for some RESTful service. When used in conjunction with xsd.exe tool to generate C# code, XSD's xs:date generates the following code:

[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="date")]
public System.DateTime time {
    get {
        return this.timeField;
    }
    set {
        this.timeField = value;
    }
}

When deserializing XML to objects using XmlSerializer all seems to be well. The problem I am facing is that the service expects dates to be formatted as YYYY-MM-DD hh:mm:ss and the XSD generated code seems to produce only YYYY-MM-DD.

If I modify XSD manually to xs:dateTime type, the generated C# code produces: 2010-08-20T20:07:03.915039Z.

Basically, how do I force serialization to produce YYYY-MM-DD hh:mm:ss? Is there something to do to XSD or is there something I can do to alter generated C# code?

dtb
  • 213,145
  • 36
  • 401
  • 431
wpfwannabe
  • 14,587
  • 16
  • 78
  • 129
  • 1
    This is a bug in the XSD, the type `xs:date` is explicitly described to refer to a *date*, without the time part! – skolima Oct 29 '13 at 21:14
  • Look at http://stackoverflow.com/questions/101533/serializing-datetime-to-time-without-milliseconds-and-gmt?answertab=votes#tab-top – TNT Jan 13 '17 at 14:16

6 Answers6

177

In the past, I've done the following to control datetime serialization:

  • Ignore the DateTime property.
  • Create a dummy string property that serializes/deserializes the way I want

Here is an example:

public class SomeClass
{
    [XmlIgnore]
    public DateTime SomeDate { get; set; }

    [XmlElement("SomeDate")]
    public string SomeDateString
    {
        get { return this.SomeDate.ToString("yyyy-MM-dd HH:mm:ss"); }
        set { this.SomeDate = DateTime.Parse(value); }
    }
}
kbrimington
  • 25,142
  • 5
  • 62
  • 74
  • 4
    +1, Ok, I have thought about a similar solution but it would require me to modify the tool generated file to add `[XmlIgnore]` to offending property. Even though it is a good one-time solution, it doesn't sound like a good thing when source XSD is often updated with new features. I am thinking it might be best to modify XSD type from `xs:date` to `xs:string` and take it from there. – wpfwannabe Aug 20 '10 at 20:33
  • I agree with your analysis. I've run into similar issues due to the format SharePoint expects dates to be. An alternative solution would be to insist the service be more flexible with date formats; however, as you are well aware, this is not always an alternative. – kbrimington Aug 20 '10 at 20:42
  • Yet another alternative solution is to roll your own `XmlSerializer`, but I imagine that is more effort than you were looking for. – kbrimington Aug 20 '10 at 20:49
  • Probably not. `IXmlSerializable` looks like a better bet even though not too perfect. – wpfwannabe Aug 20 '10 at 22:30
  • 2
    [XmlElement(DataType="date")] is what you actually want! :) (see the answer below). – Tod Thomson Sep 12 '13 at 08:06
  • 4
    While `[XmlElement(DataType="date")]` seems to be "correct", it does not format the XML the way the OP requested. I got `yyyy-MM-dd` instead of `yyyy-MM-dd HH:mm:ss`. Even in .NET 4.0, the method above is the generally accepted way to apply custom formatting to `DateTime` objects in XML. – kbrimington Sep 26 '13 at 18:16
  • 3
    This is a good solution, but it's worth pointing out that the setter is required. I attempted to do this with only the get (since this was for an output only situation) but without the setter the serialization of the SomeDateString would not happen. – Michael Murphy May 19 '16 at 03:35
  • @MichaelMurphy, you are correct. The way `XmlSerializer` works, it ignores read-only properties. It is ok if the setter does nothing. I've had to do that in another situation; but, it must be present. – kbrimington May 24 '16 at 14:55
  • This is the correct solution. The [XmlElement(DataType="date")] doesn't provide formatting capabilities. It would be nice if it did. You would think an attribute would exist for such a common case? – code5 Oct 05 '18 at 14:55
77

Use [XmlElement(DataType = "date")] attribute to format your DateTime property value as you need.

From MSDN:

Note:
The attribute that annotates the publicationdate field has a DataType property. There is no type in the .NET Framework that matches the type xs:date completely. The closest match is System.DateTime, which stores date and time data. Specifying the DataType property as a "date" ensures that the XmlSerializer will only serialize the date part of the DateTime object.

Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Bill Gates
  • 771
  • 5
  • 2
  • 6
    This looks like the most simple solution. Just add the attribute `[XmlElement(DataType="date")]` to your property – MadBender Jul 30 '13 at 06:35
  • 20
    Old post, but to serialize both date and time the correct attribute for your property would be: [XmlElement(DataType = "dateTime")] – devHead Aug 07 '17 at 12:01
  • 2
    Old post, but you can also do the same thing with `[XmlAttribute(DataType="date")]` to serialize as an Xml attribute instead of an element – Simply Ged Jan 03 '19 at 00:13
  • Caveat: `DataType` is completely ignored by `XmlSerializer`, when using attribute overrides, according to my testing. `XmlAttribute` is also ignored. – Suncat2000 Mar 05 '20 at 15:22
5

If you only need to clear out the millisecond part. Refer to:

How to truncate milliseconds off of a .NET DateTime

And basicly do something like:

  startDateTimeToUse = startDateTimeToUse.AddTicks(-(startDateTimeToUse.Ticks % TimeSpan.TicksPerSecond));
  endDate = endDate.AddTicks(-(endDate.Ticks % TimeSpan.TicksPerSecond));

I can confirm that this serializes to:

            <startDate>2015-10-31T12:13:04</startDate>
            <endDate>2016-11-10T12:13:06</endDate>

I must also state that Before clearing the milliseconds I'm doing this:

            var startDateTimeToUse = ssStartDateTime.ToUniversalTime();
            var endDate = DateTime.Now.ToUniversalTime();
            startDateTimeToUse = DateTime.SpecifyKind(startDateTimeToUse, DateTimeKind.Unspecified);
            endDate = DateTime.SpecifyKind(endDate, DateTimeKind.Unspecified);

Which I don't know if it's having any effect on the serialization or not at this point

Community
  • 1
  • 1
João Antunes
  • 811
  • 9
  • 16
  • 1
    +1 Was the hack I needed in order to avoid touching our dal lib. DateTimeKind.Unspecified will drop the 'Z' symbol when serialised. – Quinton Smith Nov 16 '17 at 09:14
  • @JasonCapriotti: I have done that to change the way the value is serialized, so it does have to do with serialization. Simpler and actually the fix that I needed to interact with what probably was an unruly PHP unserializer on the other side – João Antunes Nov 20 '17 at 11:28
3

I believe implementing IXmlSerializable interface will do a trick. You can then control how you serialize and deserialize your object.

Eric
  • 911
  • 1
  • 10
  • 18
  • 1
    Sounds promising in terms of being able to plug it in easily with all classes being generated as `partial`. On the other hand, I lose all the good stuff of automatic public property serialization so I must reinvent the wheel. This would not be a problem if the class had just a handful of properties. It has many of them and it sounds like a daunting task to have to maintain this as XSD suffers changes. – wpfwannabe Aug 20 '10 at 22:29
1

see answers above but to add-- if you only wanted output when the value is non-null (e.g. XML maxOccurs=0) you can utilize something like this:

private System.DateTime? someDateField;

public string someDate
{
    get
    {
        return someDateField?.ToString("MM-dd-yyyy");
    }
    set
    {
        dobField = System.DateTime.Parse(value);
    }
}
Brian
  • 11
  • 2
0

I may have another option. When setting your DateTime just subtract the number of ticks of everything after the seconds, like:

    public DateTime Dt
        {
        get => _dt;
        set
            {
            _dt = value;
            long elapsedTicks = _dt.Ticks - new DateTime(_dt.Year, _dt.Month, _dt.Day, _dt.Hour, _dt.Minute, _dt.Second).Ticks;
            TimeSpan elapsedSpan = new TimeSpan(elapsedTicks);
            _dt = _dt.Subtract(elapsedSpan);
            }
        }
private DateTime _dt = default(DateTime);

That way when you serialize your DateTime (Dt) the milliseconds won't be used and you'll have a value hh:mm:ss, that is at least what it gave me. That way no need to modify anything inside your XML definition.

philippe
  • 78
  • 6