Explanation:
I had a hard time to find a good solution for my Dotnet-friends.com forum clients to display them the time in their regional time zone format instead of the server Timezone.
Whenever time is saved in your Database it is always a good practice to save UTC or GMT time in your database. If you save the responded client time then it will be very hard to mange it for the all different Timezones.
On the other hand if you are saving the Server time in your Database then there is a chance that you might change your Web Provider at some point of time. And then it is also possible that new Web host servers are not located at the same Timezone. So you will be lost!
How to change a DateTime to UTC or GMT?
DateTime DateTimeUtc = DateTime.Now.ToUniversalTime();
How to Change Server time to the Client time?
To change one Timezone DateTime to other we need the Timezone difference between both Timezones which is called Timezone Offset. Offset is basically calculated against or with the reference to UTC time.
So what is the Basic problem?
I mean we can save all the Timezones in our Database and then we will be able to calculate against our client Timezone. But for that, every Client must have told us his/her Timezone. This will be not possible at least in the anonymous visitor case.
So what are the ways we can adopt to tackle this issue?
There is a way to use JavaScript. This is very successful in a case when you are only using Plain page but not embedded Data Presentation Control (like Gridview, DataList, Details View, etc) and also you don’t need a time calculation against the server time.
To calculate the time against the server time you have to inform the server with Client Offset.
Ok, at this point of time if you are thinking that yeah we can do this with a hidden field inside our page then it’s not possible because data binding with your Data Presentation Controls happens earlier then your Page reads the Client side information with the Help of the JavaScript.
It is also possible to add JavaScript at the page load (and this will be one of our trick also) but still you won’t be able to change the dates in your Form because you have to tell your server the offset before the data Presentation Control loads.
So the 1st trick is to read the Client Timezone information at Page load which happens earlier then Load event of any other Control in the Page. The 2nd thing is to somehow pass the offset to the Server. The 3rd issue is to retrieve back the calculated information to the Client (without a Postback).
I had two methods in my mind to perform the 2nd and 3rd tricks (i.e. the Bi-directional Talk between a Client and the Server without the Page Postback)
(A)
1. Create a Profile Filed for each user (Registered or Anonymous both)
2. Get the Timezone Offset through JavaScript
3. Save the Timezone Offset to a Cookie
4. Read this Cookie in the server side
5. Save the Timezone Offset in the User Profile.
I did not like the idea because:
1. We have to create (depends upon the website Visitors per second) thousands of Anonymous Profiles. This could get a too much load at some level
2. We have to create Cookies. This might not be possible for every Browser and Client. Because many people don’t like Cookies
(B)
1. Use a Session variable
2. Get the Timezone Offset through JavaScript
3. Use the client Callback to send the offset to the server side
To use a Global Session Variable initialize it in Global.asax
protected void Session_Start(Object sender, EventArgs e)
{
Session["TimeZoneOffset"] = 0;
Session.Timeout = 10;
}
Initialize Session["TimeZoneOffset"]to zero as a default and set the Session Timeout to 10 minutes.
With the evolution of ASP .NET 2.0 we got very nice new Page tools. One of them is the Master Page creation. As it is very common to use Master pages in new generation web sites we are going to answer the both Problems at the same time.
Now, we are going to create a Client Callback system in our Master page (so it is accessible to each and every page). Add one Interface to your master page. Inherit your master page from System.Web.UI.ICallbackEventHandler Interface (keep in mind that Multiple-Class-Inheritance is not allowed in C# but Multiple-Interface- Inheritance can be used). This interface informs the server that it can be contacted in the form of a callback from the Client Computer.
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
ClientScriptManager cm = Page.ClientScript;
String cbReference = cm.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context");
String callbackScript = "function CallServer(arg, context) {" + cbReference + "; }";
cm.RegisterClientScriptBlock(this.GetType(),"CallServer", callbackScript, true);
cm.RegisterClientScriptBlock(this.GetType(), "ReceiveData",
"function ReceiveServerData(context){callbackMessage.innerText = context;}", true);
}
}
Here we are registering and connecting Bi-directional methods. “CallServer” is a call from the Client to server. CallServer is taking one argument from Client to the server (Timezone offset in our case) whereas “ReceiveServerData” is where Server sends back the data after desired process on it.
You noticed that “ReceiveServerData” is sending data to an HTML Control “callbackMessage”. So insert a Tag on the page side.
<span id="callbackMessage" style="color:red">span>
This will display the returned calculated string inside the . Most probably you don’t want to show such information of the page. The Problem with the Tag is that you cannot hide your state information from the user. So I choose the Hidden field to solve my problem.
System.Web.UI.HtmlControls.HtmlInputHidden Hidden1;
cm.RegisterClientScriptBlock(this.GetType(), "ReceiveData",
"function ReceiveServerData(context){Hidden1.value= context;}", true);
We still need a JavaScript function which will be used to call the “CallServer” method. So let’s add the function to our Page.
cm.RegisterClientScriptBlock(this.GetType(), "SendValue",
@"function SendClientTimeZoneOffset(){
var now = new Date();
var offset = now.getTimezoneOffset();
CallServer(offset,'');}", true);
The IF block of the Page_Load event will look like this after adding new script.
if (!Page.IsPostBack)
{
ClientScriptManager cm = Page.ClientScript;
String cbReference = cm.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context");
String callbackScript = "function CallServer(arg, context) {" + cbReference + "; }";
cm.RegisterClientScriptBlock(this.GetType(), "CallServer", callbackScript, true);
cm.RegisterClientScriptBlock(this.GetType(), "SendValue",
@"function SendClientTimeZoneOffset(){
var now = new Date();
var offset = now.getTimezoneOffset();
CallServer(offset,'');}", true);
System.Web.UI.HtmlControls.HtmlInputHidden Hidden1;
cm.RegisterClientScriptBlock(this.GetType(), "ReceiveData",
"function ReceiveServerData(context){Hidden1.value= context;}", true);
}
Further, we need to implement two Methods RaiseCallbackEvent(String) and GetCallbackResult() of the ICallbackEventHandler Interface:
string returnValue = "0";
public void RaiseCallbackEvent(String Offset)
{
if (Offset.Length == 0)
returnValue = "Date Error!";
else
{
Session["TimeZoneOffset"] = Offset;
DateTime TestUTCDate=DateTime.Now.ToUniversalTime();
returnValue = TestUTCDate.AddMinutes(
Convert.ToDouble(Offset) * -1).ToString();
}
}
public string GetCallbackResult()
{
return returnValue;
}
Here we are creating Test date “TestUTCDate”. Normally this can be your Server UTC Date either taken from the database or some manual date. For example, in Our Forum case we are getting Saved Dates on which Topics and messages were Created or edited. The return value is set to “0” by default. You noticed that how we are assigning the Timezone offset to our session sate variable “Session["TimeZoneOffset"]”.
There could be a Case that you need an Inline code inside your Data Presentation Control. In this particular case you can still use the “Session["TimeZoneOffset"]” like this:
<%#DateTime.Parse(Eval("LastDate").ToString()).ToUniversalTime().AddMinutes(
Convert.ToDouble(Session["TimeZoneOffset"]) * -1)%>
“Eval ("LastDate")” is a Database field. This is accessed through a DataSource Object.
The Last thing which have to be done is to add the JavaScript Function "SendClientTimeZoneOffset()" to your Body Load event.
<body onload ="SendClientTimeZoneOffset();">
That’s all. Now you execute your code and enjoy the result.
|