I noticed that time information is converted to UTC or to the local time zone when converting XML messages elements of the datetime XML type to a DateTime .Net type. The side effect is that the original time is lost and can’t be recovered.
I will demonstrate this through a scenario and then postulate conclusion and best practice to keep in mind. As this post got a little lengthier than expected, you can jump right to the summary section if you just want the facts.
I had an orchestration for which I had to read a datetime XML element from an incoming message and put its value in a user friendly string message which would ultimately be visible in an application front-end. The datetime element was made a distinguished field so that it would be easier to access. When I did a ToString() on the distinguished field, the time part was modified to reflect UTC time instead of the original time from the XML datetime element. This was a problem as the log message had to reflect the actual time of the original message regardless of the time zone.
I wrote a little application to study what was going on through a few illustrating cases. The application has an Order Message containing a <ProviderTime> datetime element with a value using the time zone of Bangkok (UTC+7).
Case 1: Get the value from a distinguished field.
In this case I simply mark the datetime XML element as a distinguished field (in the message’s schema) and assign it to a DateTime .Net variable in an expression shape (* – See the footnote on a remark about this).
Calling the ToString() method on the DateTime variable prints: 9/22/2012 2:30:00 PM. This corresponds to UTC time and it means that BizTalk’s runtime created a UTC System.DateTime structure when reading the distinguished field and assigning to the variable.
While it is “correct” in the sense that both the XML datetime element and the .Net DateTime structure represent the same instant in time, it was not good for me as the user expected to see 9/22/2012 9:30:00 PM.
The reason this happens is that as the System.DateTime structure does not contain any information regarding the time zone, the BizTalk runtime converts the time to UTC time. To be exact, the BizTalk runtime calls the .Net framework XmlConvert.ToDateTime(String, XmlDateTimeSerializationMode) method which creates a DateTime object and loses the original time zone information. BizTalk then converts the resulting DateTime to UTC. There is thus no way to display the time as it was in the original message.
We would not want to use a promoted property just for reading a value out of a message but if we had a promoted property, using it would produce the same result.
Case 2: Get the value from XPath and convert it to a System.DateTime structure
In this case I use Xpath to get the element value and parse the resulting string into a System.DateTime structure with the following mothod: System.Xml.XmlConvert.ToDateTime(String). I also tried to various overload of the ToDateTime() method.
Calling the DateTime.ToString() method on the DateTime structure would now print: 9/22/2012 3:30:00 PM. This is UTC+1, Dublin’s time zone (+0) with Daylight Time Savings (+1 in summer). It means that the XmlConvert.ToDateTime(String) method creates a System.DateTime structure reflecting the Local Time. Note that this particular overload is deprecated and other exists, which I tried, but basically all they let you chose for is if you want to create a DateTime reflecting Local Time or UTC.
Using the DateTime structure is a lost cause as it is not time zone aware and I would thus never be able to hold anything else than local time or UTC time. To solve my problem, I had to do something else but did not want to do some ugly manual string parsing.
I did some research about Date and Time in the .Net framework and read from MSDN that:
DateTimeOffset should be considered the default date and time type for application development
So yes you read it correctly, System.DateTime is “sort of” deprecated! See for yourselves directly from the horse’s mouth: http://msdn.microsoft.com/en-us/library/bb384267.aspx
As The DateTimeOffset structure contains an Offset property (a TimeSpan) which represents the time difference between the time stored in the DateTime structure and UTC time, it can represent other times than local time or UTC time.
Case 3: Get the value from XPath and convert it to a System.DateTimeOffset structure
In this case I use Xpath to get the element value and parse the resulting string into a System.DateTimeOffset structure with the following mothod: System.Xml.XmlConvert.ToDateTimeOffset(String).
Bingo! Calling the ToString() method on the DateTimeOffset variable would print 9.30 PM as in the original message! Now all I had to do was to use an overload of the ToString() method taking a format string to display that in a user-friendly manner.
Here is a screenshot of the result of my investigations with cases 1,2&3 highlighted:
And here is a Visual Studio solution if you want to play around yourself (or for myself in the future).
- A distinguished field on a datetime xml element creates a System.DateTime structure adjusted for UTC Time. The DateTime.Kind property is DateTimeKind.Utc. So if in a different scenario than mine, the distinguished field is always Local Time, you can use DateTime.ToLocalTime () to convert the value back to local time. In that case, the DateTime.Kind property will have the value DateTimeKind.Local. Time in original time zone is lost.
- Reading a datetime xml element into a string by using XPath and then converting it to a System.DateTime structure by using one of the XmlConvert.ToDateTime() method overloads creates a Local Time or UTC time structure (depending of a parameter on one of the overload). Time in original time zone is lost.
- Reading a datetime xml element into a string by using XPath and then converting it to a System.DateTimeOffset structure by using one of the XmlConvert.ToDateTimeOffset() method overloads keeps the original time as it holds the time zone offset information (i.e. +7 hours). We can thus either display the time of the original time zone or convert it to another time zone offset.
|Type of XML datetime read
||.Net Type created
||Default Time zone
||Time in original time zone available?
|XPath and XmlConvert.ToDateTime()
||Local Time (for default overload)
|XPath and XmlConvert.ToDateTimeOffset()
||Original time zone
- Do not use distinguished fields on datetime elements; use XPath and a DateTimeOffset variable instead. This is a tip I will keep in mind so that I don’t have to worry about the original time value being lost. If you decide to use a distinguished field anyway, you must be aware of its limitation and if it impacts you or not.
- When using a promoted property on a datetime element, be aware that its value will be converted to UTC timezone. This might be of importance on subscriptions (port filters and so on). There is no work around on this at its part of the BizTalk engine.
I like to use distinguished field when it makes sense because it avoids having to use xpath query all over the place which can be an annoyance when a schema change. As Microsoft advises to use the System.DateTimeOffset structure, would it not be nice to have a distinguished field assignable to DateTimeOffset variable instead of DateTime? It would make a lot of sense as the XML datetime type is time zone aware while DateTime is not but DateTimeOffset is. Making the latter type is a much better match. Anyhow it is definitely something I would put in my wish list of BizTalk features!
It might of course not be straightforward to implement this feature as the code generated by the BizTalk compiler would depend of the type of the variable you assign to the distinguished field (either DateTime or DateTimeOffset). Or maybe that some automatic/easy casting is possible, there is a lengthy article on the conversion between DateTime and DateTimeOffset on MSDN but I did not try to play around with it.
While the distinguished field looks like a .Net variable accessed like an object’s member, it actually is not, it is just how it is displayed in the expression shape editor. If you have in the editor something like myVar = MyMsg.MyDistinguishedField, it is just the syntax to access the distinguished field in the expression shape. In real, the code generated from the expression shape will be: myVar = MyMsg.part.GetDistinguishedField(“MyDistinguishedField”). The method will return the correct .Net type depending of the XML type of the distinguished field.
This is why you can’t think of the distinguished field as an object member and can’t call any method such as ToString() directly on it, it will confuse the code generator and the orchestration won’t compile. Hence, we must always assign the distinguished field to a variable.