How to fix DateTime values after .NET Xml Serialization






3.40/5 (4 votes)
Sep 15, 2004
3 min read

119111

2
This will explain what happens when you use the .NET XmlSerializer class directly or indirectly (via .NET Remoting or Web Service calls) to send DataTables with DateTime values in them across different time zones. .NET framework automatically converts the values to the caller's timezone.
Introduction
The new .NET technologies, Remoting and Web Services has made life much easier than the days of trying to get DCOM to work. Although with anything that has been made easier there are some details that have been made too easy. In the case of Remoting or calling a web service, the Microsoft .NET Framework includes an automatic feature that converts all returned DataTables
with DateTime
values to the caller's time zone. So if you're in Seattle and need to find out a certain DateTime
value in a database table row (let's say sale_date
) on a server that runs in New York City you can make a web service call to find out. What happens is the sale_date
value may have a value of 8/22/2004 9:05 am on the server in New York, but your web service call will result in a value of 8/22/2004 6:05 am. Which is clearly wrong. This article will tell you how to fix this problem.
Background
The problem seems to only occur whenever you send a DataTable
as a return value. This is because .NET Framework will automatically serialize the DataTable
into xml using it's System.Xml.Serialization.XmlSerializer
class. The XmlSerializer
will convert the DateTime values upon deserialization on the client. The idea here is to take control of the xml serialization process and manipulate the xml using regular expressions to give us the correct result.
Using the code
1. In the web service we first need to convert the DataTable
to an xml string and send back the string. We use the System.IO.StringWriter
class to write out the xml string:
using System.Data;
using System.IO;
using System.Web.Services;
...
namespace NYDataServices
{
...
// Web service is running in New York City
public class MyWebService : System.Web.Services.WebService
{
...
[WebMethod]
public string GetData()
{
DataTable dataTable = null;
// Get data from database as a DataTable
...
// Now convert the DataTable to an xml string and return it to client
return convertDataTableToString( dataTable );
}
private string convertDataTableToString( DataTable dataTable )
{
DataSet dataSet = new DataSet();
dataSet.Tables.Add( dataTable );
StringWriter writer = new StringWriter();
dataSet.WriteXml( writer, XmlWriteMode.WriteSchema );
return writer.ToString();
}
2. On the client side we make the call to get the data and receive the data as an xml string.
using System.Data;
using System.Text.RegularExpressions;
...
namespace SeattleClient
{
...
// Client program running in Seattle
public class MyClient : System.Windows.Form
{
...
public void GetDataFromServer()
{
NYDataServices.MyWebService ws = new NYDataServices.MyWebService();
string xmlString = ws.GetData();
DataTable dataTable = convertStringToDataTable( xmlString );
// Do something with dataTable
...
}
3. Converting the xml string back to a DataTable
requires the use of regular expressions to search, adjust time values and replace. The DateTime
values take on the form of 2004-08-22T00:00:00.0000000-05:00. The last 5 characters in the string indicate the UTC (Universal Time Coordinate) time. During xml deserialization back into a DataTable
, the XmlSerializer
class reads this value and creates an offset value based on the client's UTC time. It then adds this offset into all DateTime
values upon deserialization. The kicker here is that if the DateTime
value happens to be on DST (Daylight Savings Time) and the client is not on DST it will adjust for this too. We use some of the magic of the System.Text.RegularExpression
namespace such as the Regex.Replace()
function, Match
class and MatchEvaluator
delegate.
private DataTable convertStringToDataTable( string xmlString )
{
// Search for datetime values of the format
// --> 2004-08-22T00:00:00.0000000-05:00
string rp = @"(?<DATE>\d{4}-\d{2}-\d{2})(?<TIME>T\d{2}:\d{2}:\d{2}."+
"\d{7}-)(?<HOUR>\d{2})(?<LAST>:\d{2})";
// Replace UTC offset value
string fixedString = Regex.Replace( xmlString, rp,
new MatchEvaluator( getHourOffset ) );
DataSet dataSet = new DataSet();
StringReader stringReader = new StringReader( fixedString );
dataSet.ReadXml( stringReader );
return dataSet.Tables[ 0 ];
}
private static string getHourOffset( Match m )
{
// Need to also account for Daylights Savings
// Time when calculating UTC offset value
DateTime dtLocal = DateTime.Parse( m.Result( "${date}" ) );
DateTime dtUTC = dtLocal.ToUniversalTime();
int hourLocalOffset = dtUTC.Hour - dtLocal.Hour;
int hourServer = int.Parse( m.Result( "${hour}" ) );
string newHour = ( hourServer + ( hourLocalOffset -
hourServer ) ).ToString( "0#" );
string retString = m.Result( "${date}" + "${time}" +
newHour + "${last}" );
return retString;
}
Points of Interest
I know this problem happens when sending back DataTables. I'm not sure if the same applies to custom classes, although I suspect it does.
Here are links that I found very useful --
- http://blogs.msdn.com/brada/archive/2004/04/13/112784.aspx
- http://blogs.msdn.com/dareobasanjo/archive/2004/04/14/113179.aspx
- Google groups post
- Highly recommended Regular Expressions tutorial
- 829740 - Improving DataSet Serialization and Remoting Performance
- MSDN - .NET Framework Regular Expressions (.NET Framework Developer's Guide)
- MSDN - Regular Expression Examples (.NET Framework Developer's Guide)