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 <Span> Tag on the page side.
<span
id="callbackMessage"
style="color:red"></span>
This will display the returned calculated string
inside the <Span>. Most probably you don’t want to show such information
of the page. The Problem with the <Span> 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.
|