CoverStory

LANGUAGES: C# | VB.NET

ASP.NET VERSIONS: 2.0

 

It s About Time

Localizing Time in ASP.NET Applications

 

By Alek Davis

 

Time zoning is a reality that application developers must take seriously before releasing their software. If you build an application that works fine in a one-server environment but corrupts date and time values once you deploy it to a distributed infrastructure, correcting the problem will most likely require substantial design and coding changes.

 

Consider a Web-based calendar used by a multinational corporation for scheduling meetings. Imagine that the server-side components of this application run in two data centers: a Web server in Dallas and a database server in Boston. If a user in Los Angeles schedules a meeting to start at 8:00 AM Pacific Standard Time (PST) and invites team members in Phoenix and Bangalore to attend, the application must make sure that all software modules as well as the meeting participants understand that the meeting starts at 8:00 AM PST, and not any other time zone.

 

Although preserving date and time accuracy may seem like a trivial task, it poses challenges to software developers especially to developers of Web-based applications. This article explains how distributed applications can handle time differences. It comes with two accompanying code samples: the class library and ASP.NET project (see end of article for download details). The class library implements classes that simplify time handling. The ASP.NET project illustrates how a Web-based application can use this library. The code samples are available in Visual Basic .NET and C#.

 

Methodologies

The most straightforward approach to handling time differences in distributed applications would be using Universal Time Coordinates (UTC) also called Greenwich Mean Time (GMT) instead of local time. To get current time, the application must call UTC-specific APIs. For example, to calculate current Universal time in C# or Visual Basic .NET, the application should invoke DateTime.UtcNow instead of DateTime.Now. The table in Figure 1 shows how to get current GMT values in different programming languages.

 

Language

To get current time in UTC, use

C

gmtime (#include <time.h>)

C#

DateTime.UtcNow

Visual Basic .NET

DateTime.UtcNow

Transact-SQL

GETUTCDATE

JavaScript

new Date((new Date).getTime +

                ((new Date).getTimezoneOffset * 60000))

Figure 1: Getting current GMT value.

 

The application must always keep date and time values in UTC and treat them as such everywhere, including the database layer (Transact-SQL), code-behind (C#, VB.NET), back-end services (C/C++, C#, VB.NET), client-side code (JavaScript), and so on. And don t forget such less obvious places as database table definitions, rules, constraints, and so on. For example, to define a table column holding date and time of the record creation, you can use code like this:

 

/* Transact-SQL */

CREATE TABLE Invoice

(

   ...

   CreateDate  DATETIME DEFAULT GETUTCDATE()

   ...

)

 

Implementing an application that operates in UTC is easy: just be consistent and remember not to use local time under any circumstances. This approach works best for machine-to-machine operations, but you can also extend it to user interfaces as long as you make it obvious to the users that the displayed date and time values imply UTC, not local time (see Figure 2). However, if you re planning to apply this methodology to client applications, be aware that your customers at least the ones living outside of London will hate you. Because most people prefer to use local time, forcing them to convert values to and from UTC will degrade application usability.

 


Figure 2: Make it obvious to users that the displayed date and time values imply UTC, not local time.

 

To make the application more user-friendly, I suggest an alternative methodology that will guarantee data accuracy and, at the same time, allow customers to use local time. This methodology is based on date and time localization (I will call it time localization in the interest of brevity). Notice that I m using the term localization here more loosely than generally accepted (see http://www.w3.org/International/questions/qa-i18n). In particular, this methodology does not address translation to foreign languages, locale-specific formatting, and other issues relevant to data presentation; it simply ensures consistent date and time handling (see the sidebar Resources if you are interested in other aspects of localization).

 

Time Localization

To make the front end of your distributed application more user-friendly, while preserving time accuracy, follow this simple rule: Use UTC for all date and time manipulations, convert data from UTC to local time for display purposes only. To make this technique work, always keep date and time values in UTC when performing time calculations, passing data between the application modules, and storing them in data sources (databases, files, Windows registry, and so on). Convert values from UTC to local time right before displaying them in the GUI and change them back to UTC immediately after the user submits data. Client-side code, such as JavaScript executed in the Web browser, can use local time, but as soon as the user submits the form and data values arrive from the client to the server, code-behind must convert them to UTC. When implementing this technique don t forget to process values assigned to non-editable fields, data grids, hidden variables, query string parameters, cookies, and so on.

 

Localizing Time in Windows Applications

Localizing time in non-Web-based client applications is easy. Because all client-side code runs on the client, you can convert date and time values between UTC and local time via the DateTime.ToLocalTime and DateTime.ToUniversalTime methods, such as:

 

' Visual Basic.NET

' Retrieve start time (in UTC) from database.

Dim startTimeUTC As DateTime = GetStarTimeFromDB()

' Convert time from UTC to local and show it to user.

label.Text = startTimeUTC.ToLocalTime().ToString()

' Get new local time entered by the user.

Dim endTime As DateTime = DateTime.Parse(textbox.Value)

' Convert time to UTC and save it in database.

SaveEndTimeInDB(endTime.ToUniversalTime())

 

Unfortunately, you cannot simply use ToLocalTime and ToUniversalTime to localize time in ASP.NET applications. Because code-behind runs on the Web server, these APIs will convert data to and from the local time on the server and not on the client. To localize time in an ASP.NET application you need to apply some ingenuity and write a few more lines of code.

 

Localizing Time in ASP.NET Applications

To properly localize time, the ASP.NET application must first determine the time zone of the client system running the Web browser. Knowing the difference between the client s time zone and UTC, the ASP.NET code will be able to change values between local and Universal time. But how does code-behind determine local time on the client?

 

One may think the answer could be found in the Request property (System.Web.HttpRequest class) of the Web form (System.Web.UI.Page class). Because the HttpRequest object can tell code-behind a few things about the client such as browser agent, supported character sets, and so on it must have some information about the client s time zone, right? Unfortunately, neither HttpRequest nor any other .NET Framework class can tell server-side logic anything about client-side time settings.

 


Figure 3: Display the time zone offset value in the alert box.

 

The only way to get relevant information I was able to find comes from JavaScript. JavaScript s Date class exposes the getTimezoneOffset method, which returns the time difference (in minutes) between UTC and local time of the computer executing JavaScript. Figure 3 illustrates how to display the time zone offset value in the alert box by entering the following information in the Microsoft Internet Explorer s address bar:

 

javascript:alert((new Date()).getTimezoneOffset())

 

Although getTimezoneOffset is useful, it presents a question: Because JavaScript code invoking getTimezoneOffset runs on the client, how can it pass the returned value to the server-side code? The solution described in this article addresses this question, but it has two limitations. First, the client (Web browser) must accept session cookies. Second, it requires client-side scripting (JavaScript) be enabled. If the client accepts session cookies and supports JavaScript, it can pass the time zone offset to the server-side code via a session cookie. After retrieving the time difference from the cookie, server-side code can apply it to the time conversion routines. The utilities library (My.Utilities.dll) accompanying this article shows how this can be done; again, see end of article for download details.

 

Using the Utilities Library

This is how you can implement time handling in an ASP.NET application using the provided utilities library (the compiled HTML help file, My.Utilities.chm, located in the Docs folder of the My.Utilities project in the TimeLocalizationSamplesCS solution, describes the classes implemented in the library).

 

First, add a reference to My.Utilities.dll to your project and include a reference to the My.Utilities.Web namespace in the source file, such as:

 

' Visual Basic.NET

Imports My.Utilities.My.Utilities.Web

' C#

using My.Utilities.Web;

 

Second, derive your Web forms from My.Utilities.Web.BaseForm instead of System.Web.UI.Page; the BaseForm class enhances System.Web.UI.Page by providing a number of helper methods, including time localization routines:

 

' Visual Basic.NET

Public Class DefaultForm

   Inherits BaseForm

   ...

End Class ' DefaultForm

 

Third, call the InitializeLocalTime function in the very beginning of the Page_Load method. InitializeLocalTime must be called at least once per user session. If you cannot guarantee that the user enters your site at a specific page, you must call it on every page that uses localized time:

 

' Visual Basic.NET

Private Sub Page_Load(sender As Object,

                     e As System.EventArgs)

   InitializeLocalTime()

   ...

End Sub ' Page_Load

 

Finally, use the ConvertLocalTimeToUtc and ConvertUtcToLocalTime functions for date and time conversion. You can also call FormatLocalDate or FormatLocalDateTime for converting the Universal time value to local time and formatting the result as a string in one step. These functions use the overloaded Format methods of the DateTimeHelper class (under My.Utilities.Common) to generate formatted string values (this class is also implemented in the utilities library). The following code snippet illustrates how to display localized time in a data grid column bound to a data set column named CreateDateGMT:

 

<asp:TemplateColumn>

<HeaderTemplate>Create Date</HeaderTemplate>

<ItemTemplate>

<%# FormatLocalDateTime( _

 DataBinder.Eval( _

   Container.DataItem, _

     "CreateDateGMT")) %>

</ItemTemplate>

</asp:TemplateColumn>

 

If you want to incorporate time localization in .aspx files that do not have the corresponding code-behind files (such as .aspx.vb or .aspx.cs), make sure the page inherits from the BaseForm class (or any class derived from BaseForm) and also call the InitializeLocalTime in the Page_Load method:

 

<%@  Page

 Language="vb"

 AutoEventWireup="true"

 Inherits="My.Utilities.Web.BaseForm" %>

...

<script language="VB" runat="server">

Sub Page_Load(sender As Object, e As EventArgs)

   InitializeLocalTime()

End Sub

</script>

 

Time Localization Logic

Now that you know how to use the utilities library, I can explain how it implements the time localization logic.

 

The time localization process starts in the BaseForm s InitializeLocalTime function, which is responsible for creation of the session cookie holding the time zone difference. By default, InitializeLocalTime looks for the cookie named TimeZoneOffset. You can give the cookie a different name by setting the BaseForm s TimeZoneOffsetCookieName property before calling InitializeLocalTime for the first time during the application life time; for example, in the Application_Start method (BaseForm keeps the name of the cookie name in a static member variable). In addition to the name, you can override the default values of the cookie timeout (in minutes), domain, and path by calling one of the overloaded InitializeLocalTime functions.

 

If InitializeLocalTime doesn t find the cookie, or determines that the value stored in the cookie timed out, it generates the JavaScript code similar to that shown in Figure 4 and inserts it in the beginning of the page (right after the opening <form> tag).

 

// JavaScript

// Check if the time zone offset cookie exists.

if (document.cookie.indexOf("TimeZoneOffset=") == -1)

{

   // Create the cookie and assign it the difference

   // (in minutes) between local time and UTC.

   // The cookie will be sent to all sites on the server

   // hosting the Web page. Path and domain attributes

   // can be omitted, while the timeout value (in GMT)

   // can be added.

   document.cookie = "TimeZoneOffset=" +

      (new Date()).getTimezoneOffset() +

     "; path=/mysite/; domain=".myserver.com;";

   // If browser supports cookies,

   // reload the page so the cookie becomes visible

   // on the server.

   // This code will not be generated during postback.

   if (document.cookie)

       document.location.reload(true);

}

Figure 4: JavaScript similar to this is generated if InitializeLocalTime doesn t find the cookie, or determines that the value stored in the cookie timed out.

 

When the Web browser processes the generated script block, it reloads the page (notice that the JavaScript code generated by code-behind during postback will not include the statement to reload the page). After the page reloads, the Page_Load method calls InitializeLocalTime again. This time the localization routine finds the cookie and essentially turns into a no-op.

 

As you can see, the local time initialization routine requires little overhead: once per user session or per configurable timeout a Web page gets reloaded to generate or refresh the time offset cookie, and once per page load the function checks cookie existence. It is worth noting that special caution may be needed to handle cases when the user submits the form without first loading the page. In this rather unlikely scenario the page can still generate the cookie for use in the subsequent requests, but if it displays date and time values and the cookie has not been created yet, these values will be in UTC.

 

After the page creates the time zone offset cookie, the time conversion and formatting routines (ConvertLocalTimeToUtc, ConvertUtcToLocalTime, FormatLocalDate, and FormatLocalDateTime) can use it for time localization. These functions add or subtract the number of minutes defined in the cookie to or from the specified date and time value to adjust it to the local time zone. If any of these functions gets called before InitializeLocalTime, they will return unchanged the passed date time values.

 

Sample Project

The accompanying sample ASP.NET application shows how to localize time with the help of the utilities library. The application uses the Microsoft Access database file (Data.mdb) located in the application s home directory to display, add, delete, and update users (see Figure 5). To test the application, create an IIS application for the corresponding project (see Figure 6) and set permissions on the MDB file to allow full access to Everyone. Once you do this, and launch the application, you can switch between pages that use local time and UTC. If you change the time zone settings on your system, you can see that the local time displayed on the localized pages will be adjusted automatically.

 


Figure 5: The accompanying sample application uses Data.mdb to display, add, delete, and update users.

 


Figure 6: Test the application.

 

Conclusion

If your application moves date and time values across different time zones it must guarantee time accuracy. To achieve this goal without degrading application usability, implement time localization. Whether you use the code samples that accompany this article or implement your own methodology, make sure that time handling is consistent in all application modules.

 

The source code accompanying this article is available for download.

 

Alek Davis is a senior software engineer at Intel Corp., where he develops enterprise applications. Alek has a B.S. degree in Computer Science and an M.S. degree in Software Engineering, both from California State University, Sacramento. Reach Alek by e-mail at mailto:alek.davis@intel.com.

 

Resources

Coding Best Practices Using DateTime in the .NET Framework

http://msdn.microsoft.com/netframework/default.aspx?pull=/library/en-us/dndotnet/html/datetimecode.asp

 

Formatting Date and Time for a Specific Culture

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconformattingobjectsforspecificculture.asp