4

The odd task that I have been given is to serialize a LARGE object using XML Serialization. This object contains multiple Nested UserDefined classes, with multiple DateTime fields. The Requirement for the DateTime data is that it must ALWAYS be displayed in the TimeZone of the user who initially created and set the data. Thus, I Cannot use UTC OR Local times because when de-serialized, they wouldn't be the same as they were. I also cannot display the values in UTC, they must be displayed in Local Time. What I need is some odd serialization format that represents the concept of "Absolute Local Time"...that would be "Local Time without TimeZone".

I can strip the TZ from the date string using Regex, that's easy. but the sheer size of the object I'm dealing with means that more often than not I get an OutOfMemoryException. I watched it run without debug once and my used memory spiked from 100k to 800k during the operation. Not nice. And that was one of the smaller files.

Doc.DocumentElement.InnerXML = Regex.Replace(Doc.DocumentElement.InnerXML, "(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})(\\+|-)(\\d{2}:\\d{2})", "$1")

So far, the only option I have seen is to create duplicates of ALL the dateTime fields, set the DT fields themselves as "XmlIgnore()", and then manually restore all the dates from the serialized string data after the doc is re-loaded. This is also not practical. See Custom DateTime XML Serialization

Is there any way to force the serialization engine to serialize DateTime objects without their TimeZone data? Preferably something generic that doesn't have to be individually applied to every DT property in the object?

!!EDIT!!

I may have found a partial solution. It might at least help moving forward. DateTimeKind.Unspecified, when serialized, doesn't seem to have any TimeZone data attached to it. Is this the solution I'm looking for. Forcefully cast all my DateTime data using DateTime.SpecifyKind?

public DateTime? StartDate
    {
        get 
        { return _StartDate; }
        set
        {
            if (_StartDate == value)
                return;

            if (value != null)
                _StartDate = DateTime.SpecifyKind(value.Value, DateTimeKind.Unspecified);
            else
                _StartDate = value;

            OnPropertyChanged("StartDate");
        }
    }
Community
  • 1
  • 1
Nevyn
  • 2,623
  • 4
  • 18
  • 32
  • You want to preserve timezone ("displayed in the TimeZone of the user who initially created") and remove it at the same time? Pick one you want... seems like fixing display code to handle timezone the way you want would be easier. – Alexei Levenkov Apr 25 '12 at 23:36
  • 1
    Preserving the TimeZone is the visible effect. What I really want to do is completely ignore the TimeZone data entirely. If the TimeZone is never included in the serialized data, then it wont effect the stored dateTime value when its de-serialized. – Nevyn Apr 26 '12 at 13:17
  • So you want to let user enter "May 5, 2011 9:00AM" and it should stay 9AM irrespective to timezone? (similar to alarm clock to wake you up in the morning) I.e. enter/serialize 9AM in India's half-an-hour time zone and when de-serialized in one of US timezones (which is almost 12 hours apart and round hour) it is still shown as 9AM? – Alexei Levenkov Apr 26 '12 at 16:03
  • This is more or less opposite to what is stated in your question... Suggestion posted as answer. – Alexei Levenkov Apr 26 '12 at 19:49
  • I may have gotten my initial terminology wrong then. Found something new, DateTimeKind.Unspecified. The serializer treats it like what im looking for...the question is, is there a way to tell it that every dateTime it reads in is going to be one of these, or will this solution only help me moving forward? – Nevyn Apr 26 '12 at 21:04
  • 1
    damn weird!! Person who is asking question has answered his own question with no upvotes. The answer marked as correct, having a downvote. Whats happening. +1 for your answer and not for question. This little thing was puzzling me for last few hours. – Sandy May 29 '12 at 21:18

3 Answers3

3

I think you need to re-evaluate your requirements or assumptions.

you wrote:

The Requirement for the DateTime data is that it must ALWAYS be displayed in the TimeZone of the user who initially created and set the data. Thus, I Cannot use UTC OR Local times because when de-serialized, the wont be the same as they were.

I don't think your analysis is correct. Seems to me you are unnecessarily co-mixing serialization to storage with "display" to the user. But those two things should not be related. The requirements as I understand them are:

  • You want to serialize and de-serialize a number of different time values.
  • when "displaying" those times, you want the display to use the original timezone.

These are distinct requirements.

Serializing a DateTime will store a moment in time, but you lose the TimeZone information. It seems to me you need to separately serialize the timezone information, once for each XML document. If you do that, then deserialization of times works automatically - you always get the exact moment in time out of storage that you had originally put into storage.

When you display the time, use the timezone information that is separately stored in the XML document. If a property containing TimeZone is not present in the original object, then it seems to me that your object model is not well-suited to the requirements of the application, in which case you need to modify the object definition to include a string identifying the TimeZone. (See http://msdn.microsoft.com/en-us/library/system.timezoneinfo.aspx)

As for the out-of-memory error, that may be an unrelated problem. It also may be due to you futzing with large XmlDocument objects. That should be unnecessary when using Xml serialization.

Cheeso
  • 189,189
  • 101
  • 473
  • 713
  • You are correct about the distinct requirements. I already have the serialization of the object (and its dateTimes) working. The second point needs to be re-stated slightly though. When Displaying the DateTime information, i should be completely ignoring TimeZone. Its not even that I should be displaying the original TimeZone, although that is the effect, but more that the idea of TimeZone should never have even entered the equation in the first place. – Nevyn Apr 26 '12 at 13:14
  • Well you cannot do that unless you store two quantities. To serialize and deserialize a time accurately without regard for timezone requires UTC. When you add the special display requirement, then it's clear you need to store or serialize an additional bit of information - either the "timezone free" time, or the original timezone in which the time should always be displayed. I think you may need to think a little more about your requirements. – Cheeso Apr 26 '12 at 13:17
  • The requirement is for "static" data. It shouldn't change. The Goal. Jimmy over in China enters all his data, then sends the serialized object via our data transmission system over to Bob in New York, who de-serializes it in order to do the data analysis and Bonus Calculations. Bob doesn't care what time it was in New York when Jimmy entered the data. – Nevyn Apr 26 '12 at 14:02
  • ok, well by default DateTime gets serialized with an offset. It is a "moment in time" and is expressed relative to UTC. To deserialize and then display that value with respect to its original timezone, you can either: (a) fiddle with deserialization to "strip" offset information, or (b) serialize the original timezone in addition to the time and apply that timezone when formatting the time values for display. It seems like (a) is the wrong way to do things, greater potential for confusion. – Cheeso Apr 26 '12 at 15:47
  • @Cheeso DateTime does not get serialized with an offset by default; the offset is an optional component. Serialization of the offset is relative the DateTimeKind of the DateTime value: `Local` adds the offset; `Unspecified` adds no offset; `Utc` adds "Z". – Suncat2000 Feb 01 '18 at 21:46
0

I would recommend to create custom type to hold such date. It will let you deal with serialization any way you want. As simpler approach consider saving them as strings in ISO8601 format (2012-05-04-26T12:57) without timezone which is specified exactly for this case.

Wholesale removing timezone from serialized data may not be a good idea as it will cause interesting problems as soon as you actually need to save absolute time. Especially if the code is shared.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
0

Found an answer. This is not exactly what I was looking for, but serves as an effective work around.

private static readonly Regex DTCheck = new Regex(@"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})([\+|-]\d{2}:\d{2})");

    /// <summary>
    /// Removes any instances of the TimeZoneOffset from the RigX after it has been serialized into an XMLString ++ Called from the "Save" process
    /// </summary>
    /// <param name="rigx"></param>
    /// <returns>StringReader referencing the re-formatted XML String</returns>
    private static StringReader RemoveTZOffsetFromRigX(RigX rigx)
    {
        StringBuilder sb = new StringBuilder();
        StringWriter sw = new StringWriter(sb);
        XmlSerializer ser = new XmlSerializer(typeof(RigX));

        ser.Serialize(sw, rigx);

        string xmlText = sb.ToString();

        if (DTCheck.IsMatch(xmlText))
            xmlText = DTCheck.Replace(xmlText, "$1");

        StringReader Sreader = new StringReader(xmlText);

        return Sreader;
    }

    /// <summary>
    /// Removes the TimeZone offset from a RigX as referenced by stream.  Returns a reader linked to the new stream  ++ Called from the "Load" process
    /// </summary>
    /// <param name="stream">stream containing the initial RigX XML String</param>
    /// <returns>StringReader referencing the re-formatted XML String</returns>
    private StringReader RemoveTZOffsetFromXML(MemoryStream stream)
    {
        stream.Position = 0;
        StreamReader reader = new StreamReader(stream, Encoding.UTF8);

        string xmlText = reader.ReadToEnd();
        reader.Close();
        stream.Close();

        if (DTCheck.IsMatch(xmlText))
            xmlText = DTCheck.Replace(xmlText, "$1");

        StringReader Sreader = new StringReader(xmlText);

        return Sreader;
    }

After reading in the XML From the file, and before running it through the serializer, run the Regex on the bare XML Text to remove the offset. The function returns a string reader running against the modified XML string, which can then be run through a de-serialization into the object.

Rather than using the serializer to save the xml directly to the output stream, you can use a stringBuilder to intercept the serialized xml. Then using the same process as during the loading procedure, you remove the TimeZone offset via RegularExpression, then return a StringReader linked to the modified text, which is then used to write the data back out into a file.

Slightly hackish feeling, but effective. Very memory intensive though, avoid debugging the functions directly if you can, or if you have to, try not to evaluate the strings, last time I tried that it crashed my VS instance completely.

Nevyn
  • 2,623
  • 4
  • 18
  • 32