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
{
...
public class MyWebService : System.Web.Services.WebService
{
...
[WebMethod]
public string GetData()
{
DataTable dataTable = null;
...
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
{
...
public class MyClient : System.Windows.Form
{
...
public void GetDataFromServer()
{
NYDataServices.MyWebService ws = new NYDataServices.MyWebService();
string xmlString = ws.GetData();
DataTable dataTable = convertStringToDataTable( xmlString );
...
}
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 )
{
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})";
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 )
{
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 --