Remoted DateTime values in a DataSet object are converted to the equivalent local time of a remote computer that is in a different time zone (842545)



The information in this article applies to:

  • Microsoft .NET Framework 1.1
  • Microsoft .NET Framework 1.0

SUMMARY

When you pass an object of the DataSet class to a remote Web service or to a Web service that is in a time zone that is different from the time zone of the calling application, the DateTime columns in a contained table are converted to the equivalent local time. The DateTime columns are converted because the System.Data.DataSet code adjusts to the equivalent local time according to the time zone of the remote computer. For example, if a value of 5:00 is passed from a client computer in Pacific Time to a Web service in Eastern Time, the value in the DataSet object changes to 8:00 in the receiving method of the Web service. This article describes how to work around this problem by passing the time zone information from the client application to the Web service and by adjusting the DateTime columns at the remote computer. The System.Data is the namespace that contains the DataSet class.

SYMPTOMS

The values of the DateTime columns change when you pass a System.Data.DataSet object to a Web service or to a remote Web service. This problem occurs when your Web service or remote Web service is in a different time zone than the client application. The values in the DateTime columns are converted to the equivalent local time according to the time zone of the remote computer.

CAUSE

The time zone of the client application is stored during the DataSet object serialization. The DataSet object is deserialized at the receiving end, and the System.Data.DataSet code adjusts the DateTime columns to the equivalent local time of the remote computer that is in a different time zone.

WORKAROUND

To work around this problem, you must pass information about the time zone when the DataSet object is created in the client application, and then you must adjust the DataSet object after it has been received in the called process on the remote computer. To do this, follow these steps:
  1. In the Web service project on the remote computer, write code that adjusts the DateTime values according to the time zone of the client application. To do this, follow these steps:
    1. Add the following code in the Service1 class as specified in the "Create a Web service" section:
      [WebMethod]
      public string AdjustDSTimeZone( DataSet dataSet ) 
      {
      
       // Obtains the time difference on the sender computer that
       //remoted this dataset to the Web service.
       string str;	
       string sourceTicksString = dataSet.ExtendedProperties["UTCDifference"].ToString();
       long sourceTicks = long.Parse( sourceTicksString );
       // Obtain the UTC offset for the remote computer.
       DateTime baseUTC = DateTime.Now;
       long UtcTickslocal = TimeZone.CurrentTimeZone.GetUtcOffset( baseUTC ).Ticks;
       // Obtain the time difference between the sender computer and the remote computer.
       long ticksDifference = sourceTicks - UtcTickslocal;
       TimeSpan timespan = new TimeSpan( ticksDifference );
       
       // The following code iterates through each table, and find all the columns that are 
       // DateTime columns. After identifying the columns that have to be adjusted,
       // it traverses the data in the table and adjusts the DateTime columns back to their 
       // original values. You must leave the RowState of the DataRow in the same state 
       //after making the adjustments.
       foreach( DataTable table in dataSet.Tables ) 
       {
        DataColumnCollection columns = table.Columns;
        int[] ColumnNumbers = new int[columns.Count];
        int   ColumnNumbersIndex = 0;
        for( int i = 0; i < columns.Count; i++ ) 
        {
         DataColumn col = columns[i];
         if ( col.DataType == typeof( DateTime ) ) 
         {	
       	ColumnNumbers[ColumnNumbersIndex] = i;
      	ColumnNumbersIndex++;
         }
       }
       foreach( DataRow row in table.Rows ) 
       {
        switch ( row.RowState ) 
        {
         case DataRowState.Unchanged:
          AdjustDateTimeValues( row, ColumnNumbers,
          ColumnNumbersIndex, timespan );
          row.AcceptChanges();	// This is to make sure that the
          // row appears to be unchanged again.
          Debug.Assert( row.RowState == DataRowState.Unchanged );
          break;
         case DataRowState.Added:
          AdjustDateTimeValues( row, ColumnNumbers, ColumnNumbersIndex, timespan );
          // The row is still in a DataRowState.Added state.
          Debug.Assert( row.RowState == DataRowState.Added );
           break;
         case DataRowState.Modified:
           AdjustDateTimeValues( row, ColumnNumbers, ColumnNumbersIndex, timespan );
          // The row is a still DataRowState.Modified.
          Debug.Assert( row.RowState == DataRowState.Modified );
          break;
         case DataRowState.Deleted:
          //   This is to make sure that you obtain the right results if 
          //the .RejectChanges()method is called.
          row.RejectChanges();	// This is to "undo" the delete.
          AdjustDateTimeValues( row, ColumnNumbers, ColumnNumbersIndex, timespan );	
          // To adjust the datatime values.
          // The row is now in DataRowState.Modified state.
          Debug.Assert( row.RowState == DataRowState.Modified );
          row.AcceptChanges();	// This is to mark the changes as permanent.
          Debug.Assert( row.RowState == DataRowState.Unchanged );
          row.Delete();			
          // Delete the row. Now, it has the same state as it started.
          Debug.Assert( row.RowState == DataRowState.Deleted );
          break;
          default:
          throw new ApplicationException
          ( "You must add a case statement that handles the new version of the dataset." );
         }
        }
       } 
       str=dataSet.Tables["MyTable"].Rows[0][1].ToString() ; 
       return str;
      }
      
      The AdjustDSTimeZone method adjusts the DateTime values to reflect the original time of the client application. Here the ExtendedProperties property of the DataSet class is used to store the Coordinated Universal Time (UTC) offset with the DataSet object. With the ExtendedProperties property, you can store custom information with the DataSet object. If you want the DataSet object to be remoted, you must store the UTC offset as a string in the ExtendedProperties collection.
    2. Add the following code below the AdjustDSTimeZone method:
      void AdjustDateTimeValues( DataRow row, int[] ColumnNumbers, int columnCount, TimeSpan timespan) 
      {
        for ( int i = 0; i < columnCount; i++ ) 
        {
      	int columnIndex = ColumnNumbers[i];
      	DateTime original = (DateTime)row[columnIndex];
      	DateTime modifiedDateTime = original.Add(timespan);
      	row[columnIndex] = modifiedDateTime;
        }
      }
    3. On the File menu, click Save All to save the application.
    4. On the Build menu, click Build Solution to build the application.
    The AdjustDateTimeValues method makes the adjustment to the DateTime object. It obtains the original time from the DateTime column of a particular row and then modifies according to the local time of the client application.
  2. In the client application, write code that verifies that the Web service has adjusted the time zone information. To do this, follow these steps:
    1. Add the following code in the Main function after the code that is specified in the "Create a client application" section:
      str=myDatasetService.AdjustDSTimeZone(myDataset );
      Console.WriteLine (str);
      Console.ReadLine ();
    2. In Solution Explorer, expand all folders.
    3. Right-click WebReference1, and then click Update Web Reference.
    4. On the File menu, click Save All to save the application.
    5. On the Build menu, click Build Solution to build the application.
    6. On the Debug menu, click Start to run the application.
The client application connects to the MyTable table that contains a DateTime column. It creates an object of the Web service and passes an object of the DataSet class to the AdjustDSTimeZone method of the Web service. The AdjustDSTimeZone method in the Web service updates the DateTime column of the MyTable table according to the time zone of the client application and then returns the DateTime value of the first row of the MyTable table. The DateTime value that is received by the client application is the same as the value that is passed by the client application to the Web service.

STATUS

This behavior is by design.

MORE INFORMATION

Steps to reproduce the problem

Create a database table

To create a database table in an instance of Microsoft SQL Server, follow these steps:
  1. Start SQL Query Analyzer.
  2. On the File menu, click New.
  3. Click Blank Query Window, and then click OK.
  4. Paste the following code in the query window. This code creates the MyTable table.
    CREATE  TABLE MyTable (
    
                [ID] [int] NOT NULL ,
    
                [DateTimeCol] [datetime] NOT NULL 
    
    ) ON [PRIMARY]
    
    Go
    Insert into MyTable Values (1, '2004-05-19 15:00:00.000')
    Go
    
    Insert into MyTable Values (2, '2004-05-19 13:00:00.000')
    Go
    
    
  5. On the Query menu, click Execute to run the query. The MyTable table is created, and the table is updated with two values.

Create a Web service

Create a Web service project on a remote computer that receives the DateTime values from a client application. To do this, follow these steps:
  1. Start Microsoft Visual Studio .NET.
  2. On the File menu, point to New, and then click Project.
  3. Under Project Types, click Visual C# Projects, and then click ASP.NET Web Service under Templates.
  4. In the Name box, type MyWebService, and then click OK. By default, the Service1.asmx file is created.
  5. In Solution Explorer, right-click the Service1.asmx file, and then click View Code.
  6. Add the following code at the beginning of the file:
    using System.Data.SqlClient;
    using System.Timers;
  7. Locate the following code in the Service1 class:
    public class Service1 : System.Web.Services.WebService
    {
  8. Add the following code after the code that you located in step 7:
    [WebMethod]
    public String DataSetReturn( DataSet clientDataSet )
    {
      string str;				
      str=clientDataSet.Tables["MyTable"].Rows[0][1].ToString() ;
      return str;	
    }
    The DataSetReturn method receives an object of the DataSet that is passed by the client application. This method returns the DateTime value of the first row of the MyTable table to the client application.
  9. On the File menu, click Save All to save the application.
  10. On the Build menu, click Build Solution to build the application.

Create a client application

To create a client application that passes a DataSet object to the remote Web service, follow these steps:
  1. Start Microsoft Visual Studio .NET.
  2. On the File menu, point to New, and then click Project.
  3. Under Project Types, click Visual C# Projects, and then click Console Application under Templates.
  4. In the Name box, type MyDataSet, and then click OK. By default, the Class1.cs file is created.
  5. Add the following code at the beginning of the file:
    using System.Data;
    using System.Data.SqlClient;
    using System.Timers;
    using System.Diagnostics;
  6. Add the following code in the Main function:
    string auth="Server=YourServer;Database=YourDatabase;User ID=YourUserID;password=YourPassword";
    WebReference1.Service1 myDatasetService = new WebReference1.Service1();
    DataSet myDataset = new DataSet();
    SqlDataAdapter da = new SqlDataAdapter("Select * From MyTable",auth);
    da.Fill(myDataset, "MyTable");
    //Modify first row to have the current time.
    myDataset.Tables["MyTable"].Rows[0][1] = DateTime.Now;
    string str=myDataset.Tables["MyTable"].Rows[0][1].ToString() ;
    System.Console.WriteLine(str);
    //Store the ticks from UTC in the ExtendedProperties collection of the DataSet
    DateTime clientDateTime = DateTime.Now;
    myDataset.ExtendedProperties["UTCDifference"] = TimeZone.CurrentTimeZone.GetUtcOffset
    ( clientDateTime ).Ticks.ToString();    
    str= myDatasetService.DataSetReturn(myDataset );
    Console.WriteLine (str);
    Console.ReadLine ();
    
    Note To access the MyTable table, you must modify the server information and the database information together with the user name and the password for establishing the connection with the Microsoft SQL Server.
  7. In Solution Explorer, right-click References, and then click Add Web Reference.
  8. In the URL box, type http://RemoteServer/MyWebService/Service1.asmx, and then click Go. By default, in Visual Studio .NET 2002, a Web reference that is named WebReference1 is created.

    Note RemoteServer is a placeholder for the name of the remote server where the Web service is created.
  9. In Microsoft Visual Studio .NET 2003, type WebReference1 in the Web reference name box. Here WebReference1 is used to match the code syntax.
  10. Click Add Reference.
  11. On the File menu, click Save All to save the application.
  12. On the Build menu, click Build Solution to build the application.
  13. On the Debug menu, click Start to run the application.
The client application modifies the DateTime column of the first row. The code sets the DateTime value to the current date and time of the client application. It passes a DataSet object to the Web service. The Web service receives the DataSet object, retrieves the value of the DateTime column from the first row, and then returns the value back to the client application. The DateTime value that is returned by the Web service is changed according to the time zone information of the remote server as specified in the "Symptoms" section.

.NET Framework remoting

If you are implementing the .NET Framework remoting services in your application and you do not call a .NET Framework Web service method, you can use the DataSetSurrogate class code. For additional information about the DataSetSurrogate class, click the following article number to view the article in the Microsoft Knowledge Base:

829740 Improving DataSet serialization and remoting performance

The DataSetSurrogate class prevents the local time zone adjustments on the DateTime columns. This class also provides better performance during .NET Framework remoting. The DataSetSurrogate class overrides the default serialization of the DataSet class and serializes the DataSetSurrogate class and its contained members in binary form.

REFERENCES

For more information, visit the following Microsoft Developer Network (MSDN) Web sites:

Modification Type:MinorLast Reviewed:7/20/2004
Keywords:kbtshoot kbWebServices kbRemoting kbClient kbSystemData kbSqlClient kbService kbprb KB842545 kbAudDeveloper