Timezone Strategy - c#

I am building a MVC 3 application where the users may not be in the same time zone, so my intent was to store everything in UTC and convert from UTC to local time in the views and localtime to UTC on submissions.
Doing some browsing though there doesn't seem to be a lot of good solutions to this. To be honest, I sort of expected an attribute to be available to auto convert UTC time into local time at least, but it seems not to exist.
I feel like just trying to be diligent about manually converting every input to UTC and manually converting every view to local time display will be very error prone and lead to difficult to detect bugs where the time is not converted to or from.
Any suggestions on how to deal with this as a general strategy?
EDIT
Everyone seems very stuck on the "how do I get the client timezone" piece, which as I mention in one of the comments is not my concern. I am fine with a user setting that determines their timezone, so assume I already know what the client time zone is...that doesn't address my problem.
Right now, on each view when I render a date, I would need to call a method to render it in the local time zone from utc. Every time I send a date for submission to the server I need to convert it from the local timezone to UTC. If I forget to do this there will be problems...either a submitted date will be wrong or client side reports and filters will be wrong.
What I was hoping existed was a more automated method, especially since the view model is strongly typed in MVC 3 I was hoping for sum magic to be able to at least automatically render in a time zone, if not handle the submission, just like the date format or range can be controlled by an attribute.
So like
[DateRange]
Public DateTime MyDate
I could have something like
[ConvertToUTC(offset)]
Public DateTime MyDate
Anyway, I guess it look like my only approach would be to write a custom data annotation to render it in a time zone, and a override on the MVC 3 model binder so incoming dates are converted unless I want to wrap ever date in a method call. So unless anyone has further comments or suggestions it will be one of those two options, I'm just surprised something doesn't exist already to do this.
If I do implement a solution I will be sure to post it.
Edit 2
Something like This http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.aspx for MVC 3 views and view models is what I am looking for.
Final Edit
I marked epignosisx answer as correct, but also have a few comments to add.
I found something similar here:
http://dalldorf.com/blog/2011/06/mvc3-timezones-1/
With an implementation of getting the timezone from the client by placing it in the cookie for people that want that in part 2 (link below since the link on the first part of the article to part 2 doesn't work)
http://dalldorf.com/blog/2011/09/mvc3-timezones-2/
Its important to note with these approaches that you MUST you Editfor and Displayfor instead of things like TextForFor as only EditFor and DisplayFor make use of the metadata providers used to tell MVC how to display the property of that type on the model. If you access the model values directly in the view (#Model.MyDate) no conversion will take place.

You could handle the problem of converting UTC to user local time by using website-wide DisplayTemplate for DateTime.
From your Views you would use #Html.DisplayFor(n => n.MyDateTimeProperty)
The second problem is tougher to tackle. To convert from user local time to UTC you could override the DefaultModelBinder. Specifically the method SetProperty. Here is a naive implementation that demonstrates the point. It only applies for DateTime but could easily be extended to DateTime?. Then set it up as your Default binder in the Global.asax
public class MyDefaultModelBinder : DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
{
//special case for DateTime
if(propertyDescriptor.PropertyType == typeof(DateTime))
{
if (propertyDescriptor.IsReadOnly)
{
return;
}
try
{
if(value != null)
{
DateTime dt = (DateTime)value;
propertyDescriptor.SetValue(bindingContext.Model, dt.ToUniversalTime());
}
}
catch (Exception ex)
{
string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
bindingContext.ModelState.AddModelError(modelStateKey, ex);
}
}
else
{
//handles all other types
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
}
}
}

First this is mostly a duplicate of How can I determine a web user's time zone?, I agree with the majority vote there for this response:
The most popular (==standard?) way of determining the time zone I've
seen around is simply asking the user herself. If your website
requires subscription, this could be saved in the users' profile data.
For anon users, the dates could be displayed as UTC or GMT or some
such.
That being said the most common approach to automatically setting this value is to use javascript's Date getTimezoneOffset(). This can then be feed back to the server via a cookie or ajax request and stored with the user's profile, session, or cookie.
Ultimately I still think you should allow users to change this setting so that you can determine not just the UTC offset, but the actual timezone and daylight savings information as well.
When collecting input from users conversion to and from UTC via DateTime should suffice. DateTimeOffset is great when the client is managed code; however, with pure html/javascript it really isn't going to buy you much. Moreover the additional information of DateTimeOffset is not necessarily needed by most applications unless you intend to display to other users the originating timezone information.
I feel like just trying to be diligent about manually converting every
input to UTC and manually converting every view to local time display
will be very error prone and lead to difficult to detect bugs where
the time is not converted to or from.
You should not depend upon diligence for correct date+time formatting and parsing. The .NET framework should handle this for you, for starters see "How to: Set the Culture and UI Culture for ASP.NET Web Page Globalization".
Closing remark
Frankly this is all a pain in the neck. We threw out the implementation some years ago and started transferring all date+time information in UTC, then we use Javascript to convert to local time and display format. This is really the only working model IMHO.

You can use something like MomentJS to display dates/times. Helps with formatting and local times.

This is not possible automatically, you will have to do some manual work.
1 If you do not want to store user's timezone in db
1.1 Without masterpage: As csharptest.net suggested use java script's getDateTimeOffset() to get the timezone offset, set value in cookie, write a module to check for cookie if cookie is not present insert java script code and cookie using module.
1.2 Using masterpage: Same thing but no need to write module for checking.
2 Store user's timezone in db (best & easiest)
No need to use javascript to get timezone just convert datetime according to user's timezone.

Related

How to format url datetime parameter with timezone

I've searched everywhere and can't find the answer. I would like to understand what's going with the way I format the datetime (with timezone) url parameter.
Here is the situation:
The caller program is having DateTime value with UTC timezone.
The receiver Json WebAPI (C#) is running on my local pc which is having Central timezone.
The PoCreationDate is a DateTime type (C#). I do not write code to parse the PoCreationDate value. C# converted it to DateTime object for me auto-magically (thru Serialization?).
Here are the test cases:
Case 1:
This one works
http://******/api/ItemSource/GetItemSourceOption?OrganizationCode=OKC&PoCreationDate=2018-10-08T01:02:03.0000000-05:00
Case 2:
This one does not work and the browser is displaying
<Error>
<Message>The request is invalid.</Message>
</Error>
http://******/api/ItemSource/GetItemSourceOption?OrganizationCode=OKC&PoCreationDate=2018-10-08T05:00:00.0000000+00:00
Notice the different? one of them have a -05:00 the other have +00:00. My timezone is Central (which is -05:00 right now?)
Case 3:
My current work around is to format it this way
http://******/api/ItemSource/GetItemSourceOption?OrganizationCode=OKC&PoCreationDate=2018-10-08T05:00:00Z
=====================
So I am just trying to understand what's going on here and these my thoughts...
I believe that using the Z format is the best solution since the DateTime value (from the source) is always in UTC format.
About the -05:00 and +00:00, are these supposed to be set per the receiver's timezone (the destination Server local timezone)? So for this case, my PC is the receiver (WebAPI) and it's set to Central Time Zone, therefore this value must be -05:00 to represent the current value for Central Time Zone?
Please help me understand this. Thanks.
The + character in the offset is being interpreted as a space, as per URL encoding rules. You will need to encode it, such that it is replaced with %2B.
If you are building this URL from JavaScript, use the encodeURIComponent function.
If you are building it in C#, use the System.Net.WebUtility.UrlEncode method.
Note that doing so will also replace the : characters with %3A, which is optional in a querystring parameter, but still recommended.
In general, parameters passed in the querystring need to be encoded, unless you can guarantee that they contain no special characters.
Also, you may want to ask yourself if this field really needs to be a full date+time+offset. In many cases, one might expect a field like PoCreationDate to be just a date, as in "2018-10-08". Of course, that depends on your application logic and business requirements.

How to handle timezone difference

I have my aplication which uses BST time zone(UTC+1) and another application where i am using the value from the above specified application . On my second server the timezone is UTC.
Because of this when i get value in a datetime property there is a diufference of one hour and because of that the value is going wrong.
I wont be able to adjust the timezone on both the servers . How can i convert the value to a BST time zone in my second application using .net.
public DateTime? EmploymentStartDate { get; set; }
This is my property in my model.
the value from my first server is 29/8/2001 00:00:00 when it comes to the second server where the timezone is UTC it is 28/9/2001 23:00:00 .
It's simple, you must consider all the DateTime always in UTC, so simply call ToUniversalTime() to convert every instance in UTC timezone:
DateTime dt = DateTime.Now;
dt.ToUniversalTime();
I agree with Daniel A. White. You should store everything as UTC and convert the date to local date on the client.
Look this https://msdn.microsoft.com/en-us/en-en/library/system.timezoneinfo.converttimefromutc(v=vs.100).aspx
private DateTime? _employmentStartDate;
public DateTime? EmploymentStartDate
{
get
{
return _employmentStartDate != null ? TimeZoneInfo.ConvertTimeFromUtc(_employmentStartDate.Value, TimeZoneInfo.Local) : new DateTime();
}
set
{
_employmentStartDate = TimeZoneInfo.ConvertTimeToUtc((DateTime)value);
}
}
Also consult this previous StackOverflow post:
Should MySQL have its timezone set to UTC?
This article (and the numerous other articles and web-pages that it links to) discusses MySQL, but the concerns and techniques are similar for any database and programming language.
While your (the OP's) question seems to be about how to convert time information between time zones, the discussion in the comments has grown hot on the subject of choosing a format for storage. First things first – to convert your UTC time to BST you can use TimeZoneInfo.ConvertTimeFromUtc. This method “converts a Coordinated Universal Time (UTC) to the time in a specified time zone.”
Here’s an example:
var bstZone = TimeZoneInfo.FindSystemTimeZoneById("GMT Daylight Time");
bstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, bstZone);
NB 1: If what you really want is local time on the server when executing, obviously you should rather use ToLocalTime(), and that will yield the same result if that local time happens to be BST (and the Kind property of the DateTime variable is not DateTimeKind.Local).
NB 2: To the best of my knowledge, the TimeZoneInfo class referrers to "British Summer Time" as "GMT Daylight Time", and I’m assuming that’s what you refer to as BST. But don’t take my word for it – you need to make sure yourself that you have the right time zone ID.
Now, about storing:
TL;DR
A time signature without time zone information is unreliable information. Given this premise, there is however no true “right or wrong” time zone to use for storing. While most people might argue that UTC is to be considered a best practice, first of all that information (that it's in UTC) should still be stored explicitly alongside the timestamp, and secondly, there may be practical reasons why another format is preferable in a specific scenario.
When performing arithmetic calculations on time values you should use universal time representations to avoid hiccups with regard to daylight saving practices and such.
Be weary of complications that might arise from serializing and de-serializing mechanisms making the wrong assumptions about the information you provide, e.g. System.XML.Serialization or a database engine.
Obviously, when presenting your time information (that is, when creating a string representation suitable for interpretation by humans), you should do so in whatever format the user should be expecting. Regardless of how you persist your values, conversions (with offset) may be necessary to do so.
Check out these links for more information:
Converting Times Between Time Zones
Coding Best Practices Using DateTime in the .NET Framework

Asp.Net, SQL and TimeZones

It's been asked, but I am battling to grasp the concept of how to handle timezones in a web app. I have a system that tracks the progress of projects. I have a ProjectStartDate DATE in my SQL Server database. (There's a few more fields and tables, but lets focus on one).
The server is located somewhere in the United States. I live in Australia.
Calling SELECT GETDATE() returns "2013-08-11 14:40:50.630"
My system clock shows "2013-08-12 07:40"
In my database, I have 'CreateDateTime' columns on all tables. When I store that, within my c# code, I use CreateDate = DateTime.UtcNow
I use that, as I heard it was better to use UTC.
But, when a user is presented with a calendar control, and they select a Start Date for a project, I store what ever the user selected. No conversion... And as I said, the StartDate is a DATE type in the database.
The problem is, if a project started today - my front end says that the current project is No Started, because the server is still in Yesterday.
I think the dates should be stored as I am storing them. But maybe I need to somehow get the users timezone, and apply that on the UI level?
Problems I see are:
I don't know the users timezone. Add something to allow them to select it?
The status of the project maybe determined in a stored procedure, so when can I apply the conversion? In the proc, it might do a check, and return a VARCHAR stating "Not Started" if the StartDate <= DateTime.Now?
I use EntityFramework and Linq to get data most of the time. I need a strategy for inserting and retrieving data, both from a SQL sense, and a .Net sense.
I have added code to get the user to select their timezone based on this:
public List<TimeZoneDto> GetTimeZones()
{
var zones = TimeZoneInfo.GetSystemTimeZones();
var result = zones.Select(tz => new TimeZoneDto
{
Id = tz.Id,
Description = tz.DisplayName
}).ToList();
return result;
}
That is then persisted in their profile.
All dates are being stored as UTC, as advised in the answers below.
I'm still confused how to handle the dates when they go to and from the database, to the client. Here is an example of how I am storing a record:
public int SaveNonAvailibility(PersonNonAvailibilityDto n)
{
person_non_availibility o;
if (n.Id == 0)
{
o = new person_non_availibility
{
PersonId = n.PersonId,
Reason = n.Reason,
StartDate = n.StartDate,
EndDate = n.EndDate,
NonAvailibilityTypeId = n.NonAvailibilityTypeId,
CreateUser = _userId,
CreateDate = DateTime.UtcNow
};
_context.person_non_availibility.Add(o);
}
else
{
o = (from c in _context.person_non_availibility where c.Id == n.Id select c).FirstOrDefault();
o.StartDate = n.StartDate;
o.EndDate = n.EndDate;
o.Reason = n.Reason;
o.NonAvailibilityTypeId = n.NonAvailibilityTypeId;
o.LastUpdateDate = DateTime.UtcNow;
o.LastUpdateUser = _userId;
o.Deleted = n.Deleted ? DateTime.UtcNow : (DateTime?)null;
}
_context.SaveChanges();
return o.Id;
}
This method basically saves when a person is not available for work. Note the way I store the LastUpdateDate. Also, the 'Start' and 'End' dates. Those dates are more 'Business' dates.
On selection, and then date checking, is where I have issues. In this example, I am getting a persons rate of charge, based on NOW.
public decimal CurrentRate
{
get
{
if (ResourceCosts != null)
{
var i = ResourceCosts.FirstOrDefault(t => DateTime.UtcNow <= (t.EndDate.HasValue ? t.EndDate.Value : DateTime.UtcNow) && DateTime.UtcNow >= t.StartDate);
if (i != null) return i.Cost;
return 0;
}
return 0;
}
}
So, what I am wanting to do here, is, based on the current date, I want to see his rate (As his charge our rate maybe be $100 from the 1st Jan, to the 15th Jan, and then $110 from the 16th until the 31st of Jan. So, I look for the rate applicable today (if any). This doesn't work across time zones, and it's maybe here where I need to do some date manipulation based on the 'DateTime.UTCNow'?
Note, I now know the users timezone based on the code above where I am storing his timezone. I can use that somehow here? Maybe, when the user logs in, grab the date info from his profile (Zimezone info), and then have a global shared function, that returns the users datetime, based on adding or removing hours from the UTC Date... and using that where ever I am doing DateTime.UTCNow?
Hope someone can guide me.
You may find that there is not one single "right" way to handle all of this. There are multiple approaches to the several different problems you describe in your question. I will attempt to clarify a few points.
First, don't ever attempt to think about local time on a server. Your code and data should not have to change based on where you deploy it. You said your server was in the USA, but there are multiple time zones to consider, and many servers will have their time zone set to UTC regardless of their physical location.
You should avoid GETDATE() or SYSDATETIME() in SQL Server. If you need a current timestamp in SQL, use GETUTCDATE() or SYSUTCDATETIME(). If for some reason the server's time zone is important to you, use SYSDATETIMEOFFSET() instead.
Likewise, avoid using DateTime.Now in .Net from any server-side code. Use DateTime.UtcNow or DateTimeOffset.UtcNow for a UTC timestamp, or use DateTimeOffset.Now if for some reason the server's time zone is important to you.
You can read more about this in my blog post: The Case Against DateTime.Now
Next, let's talk about the data type you're using. The date type in SQL Server stores just a date. That's it. No time, no offset, and no time zone. An example would be 2013-08-11. You should use it when you really mean a whole calendar date. There is no worldwide uniform context of "today". Instead, everyone has their own meaning based on their time zone. Also, not every calendar day is 24 hours in length. A day could be 23, 23.5, 24, 24.5 or 25 hours long, depending on how daylight saving time is applied in the particular time zone, and if you are evaluating the day of a DST transition.
In .Net - there is no Date type, so a SQL date is converted to a DateTime with the time set to midnight (00:00:00), and the kind set to Unspecified. But don't fool yourself - the time isn't suddenly midnight, we are just filling in zeros for the time. This can lead to a lot of error and confusion. (If you want to avoid that, you can try Noda Time, which has a LocalDate type for this purpose.)
What you really need to be thinking about, and haven't defined in your question, is this:
What exact moment does a project start?
Right now you are just saying 2013-08-11, which doesn't refer to a specific moment in time. Do you mean the beginning of that day in a particular time zone? Or do you mean the beginning of that day according to the user's time zone? Those might not be the same thing. You can't compare to anyone's "now" (utc, local, or otherwise) unless you know what moment in time you are talking about.
If the project starts at an exact moment in time worldwide, then the easiest thing would be to store a datetime (or datetime2) type that contains that precise time in UTC. So you might say that a project starts at 2013-08-10T14:00:00Z - which would be exactly midnight on August 11th in Sydney, Australia. In .Net, you would use a DateTime type with the .Kind set to Utc.
Another way you could represent this is by storing a datetimeoffset type that has a value of 2013-08-11T00:00:00+10:00 - which is the same moment in time, but uses the offset to give you a value that is pre-converted. (Sydney is at UTC+10 on that date). You would use the DateTimeOffset type to work with this in .Net.
But if the project starts at different times depending on the user, then it's not really an exact moment in time. It's more of a "floating" start. If users from different places around the world are assigned to the same project, then some users could be starting before others. If that's your intention, then you can just use the date type if all projects start at midnight, or you can use a datetime or (datetime2) type if projects might start at different times. In your .Net code, you would use a DateTime type with the .Kind set to Unspecified.
With regard to getting the user's time zone, the best thing you could do would be to ask them. Despite the common misconception - you can't just get it from the browser. All you could tell from the browser is what their current offset is. (Read the "TimeZone != Offset" section of the timezone tag wiki).
When asking the user for their time zone, if you decide to use Windows time zones you can produce a dropdown list from the TimeZoneInfo.GetSystemTimeZones method. The .Id is the key you store in your database, and you show the .DisplayName to the user. Later you can use the TimeZoneInfo.FindSystemTimeZoneById method to get a TimeZoneInfo object that you can use for conversions.
If you wanted to be more precise, you could use IANA time zones instead of the Windows time zones. For that, I recommend using a map-based timezone picker control, such as this one. You might also use jsTimeZoneDetect to guess a default value for your control. On the server you would use Noda Time to perform time zone conversions.
There is an approach that doesn't require time zone conversions. Basically, you do everything in UTC. That includes transmitting the time to the browser in UTC. You can then use JavaScript to get the user's current time in UTC and compare against that.
You can use various functions of the JavaScript Date class to do this if you wish. But you may find it easier to work with a library such as moment.js.
While this approach is viable for many things, security is not one of them. Your user can easily change the clock of their computer to work around this.
Another approach would be to compare server-side against UTC. If you have the exact UTC starting time in your database, then you can just check DateTime.UtcNow in your .Net code and use that to decide what to do. You won't need the user's time zone to make this comparison, but you will need it if you want to show them what that means in their local time.
I hope this clears up the confusion and didn't make it worse! :) If you have additional concerns, please edit your question or ask in comments.
UPDATE
In response to your updated question, I suggest you try the following:
var timeZoneId = "Eastern Standard Time"; // from your user's selection
var timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
var nowInTimeZone = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, timeZone);
var todayInTimeZone = nowInTimeZone.Date;
var i = ResourceCosts.FirstOrDefault(t => t.StartDate <= todayInTimeZone &&
(!t.EndDate.HasValue || t.EndDate >= todayInTimeZone));
Of course this means that in your StartDate and EndDate fields, you are not storing these as UTC - but rather as the "business dates" that are relevant to the user. These only line up to a specific moment in time when you apply a time zone, so the same UTC timestamp could fall on different dates depending on what the user's time zone is.
Also, you are using fully inclusive ranges, which is usually OK for these kind calendar date ranges. But make sure you realize that there could be overlap. So if you have 2013-01-01 - 2013-02-01 and 2013-02-01 - 2013-03-01, then there is that one day 2013-02-01 that is in both ranges.
A common way around this problem is to use half-open intervals, [start,end). In other words, start <= now && end > now. But this is more common when using a full date and time instead of just a date. You might not need to do this, but you should at least think about it for your particular scenario.
You can get a user's timezone from the browser. This will only get the value that they've set on their computer. So they can manipulate this for their advantage. For example, if you were limiting access until midnight local time, they could change their time to get "early" access. So keep that in mind.
Next what you'll want to do is store all times in UTC in the database. Instead of using GetDate() you can use GetUTCDate().
Next, you can either store their timezone, or their offset from UTC in the database. You get into some danger just storing the UTC offset, because timezones that observe daylights savings will have different offsets at different time of the year.
But with these two pieces of information, you can calculate the "local" time of the user. You can either add the number of hours to your stored date, then compare that to the local server time (also adjusted from UTC.)
Of if you don't really care that much, you can store the time in UTC in the database, then simply compare DateTime.UTCNow on the server to determine if they should have access or not.

How to elegantly deal with timezones

I have a website that is hosted in a different timezone than the users using the application. In addition to this, users can have a specific timezone. I was wondering how other SO users and applications approach this? The most obvious part is that inside the DB, date/times are stored in UTC. When on the server, all date/times should be dealt with in UTC. However, I see three problems that I'm trying to overcome:
Getting the current time in UTC (solved easily with DateTime.UtcNow).
Pulling date/times from the database and displaying these to the user. There are potentially lots of calls to print dates on different views. I was thinking of some layer in between the view and the controllers that could solve this issue. Or having a custom extension method on DateTime (see below). The major down side is that at every location of using a datetime in a view, the extension method must be called!
This would also add difficulty to using something like the JsonResult. You could no longer easily call Json(myEnumerable), it would have to be Json(myEnumerable.Select(transformAllDates)). Maybe AutoMapper could help in this situation?
Getting input from the user (Local to UTC). For example, POSTing a form with a date would require converting the date to UTC before. The first thing that comes to mind is creating a custom ModelBinder.
Here's the extensions that I thought of using in the views:
public static class DateTimeExtensions
{
public static DateTime UtcToLocal(this DateTime source,
TimeZoneInfo localTimeZone)
{
return TimeZoneInfo.ConvertTimeFromUtc(source, localTimeZone);
}
public static DateTime LocalToUtc(this DateTime source,
TimeZoneInfo localTimeZone)
{
source = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
return TimeZoneInfo.ConvertTimeToUtc(source, localTimeZone);
}
}
I would think that dealing with timezones would be such a common thing considering a lot of applications are now cloud-based where the server's local time could be much different than the expected time zone.
Has this been elegantly solved before? Is there anything that I'm missing? Ideas and thoughts are much appreciated.
EDIT: To clear some confusion I thought add some more details. The issue right now isn't how to store UTC times in the db, it's more about the process of going from UTC->Local and Local->UTC. As #Max Zerbini points out, it's obviously smart to put the UTC->Local code in the view, but is using the DateTimeExtensions really the answer? When getting input from the user, does it make sense to accept dates as the user's local time (since that's what JS would be using) and then use a ModelBinder to transform to UTC? The user's timezone is stored in the DB and is easily retrieved.
Not that this is a recommendation, its more sharing of a paradigm, but the most agressive way I've seen of handling timezone information in a web app (which is not exclusive to ASP.NET MVC) was the following:
All date times on the server are UTC.
That means using, like you said, DateTime.UtcNow.
Try to trust the client passing dates to the server as little as possible. For example, if you need "now", don't create a date on the client and then pass it to the server. Either create a date in your GET and pass it to the ViewModel or on POST do DateTime.UtcNow.
So far, pretty standard fare, but this is where things get 'interesting'.
If you have to accept a date from the client, then use javascript to make sure the data that you are posting to the server is in UTC. The client knows what timezone it is in, so it can with reasonable accuracy convert times into UTC.
When rendering views, they were using the HTML5 <time> element, they would never render datetimes directly in the ViewModel. It was implemented as as HtmlHelper extension, something like Html.Time(Model.when). It would render <time datetime='[utctime]' data-date-format='[datetimeformat]'></time>.
Then they would use javascript to translate UTC time into the clients local time. The script would find all the <time> elements and use the date-format data property to format the date and populate the contents of the element.
This way they never had to keep track of, store, or manage a clients timezone. The server didn't care what timezone the client was in, nor had to do any timezone translations. It simply spit out UTC and let the client convert that into something that was reasonable. Which is easy from the browser, because it knows what timezone it is in. If the client changed his/her timezone, the web application would automatically update itself. The only thing that they stored were the datetime format string for the locale of the user.
I'm not saying it was the best approach, but it was a different one that I had not seen before. Maybe you'll glean some interesting ideas from it.
After several feedbacks, here is my final solution which I think is clean and simple and covers daylight saving issues.
1 - We handle the conversion at model level. So, in the Model class, we write:
public class Quote
{
...
public DateTime DateCreated
{
get { return CRM.Global.ToLocalTime(_DateCreated); }
set { _DateCreated = value.ToUniversalTime(); }
}
private DateTime _DateCreated { get; set; }
...
}
2 - In a global helper we make our custom function "ToLocalTime":
public static DateTime ToLocalTime(DateTime utcDate)
{
var localTimeZoneId = "China Standard Time";
var localTimeZone = TimeZoneInfo.FindSystemTimeZoneById(localTimeZoneId);
var localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localTimeZone);
return localTime;
}
3 - We can improve this further, by saving the timezone id in each User profile so we can retrieve from the user class instead of using constant "China Standard Time":
public class Contact
{
...
public string TimeZone { get; set; }
...
}
4 - Here we can get the list of timezone to show to user to select from a dropdownbox:
public class ListHelper
{
public IEnumerable<SelectListItem> GetTimeZoneList()
{
var list = from tz in TimeZoneInfo.GetSystemTimeZones()
select new SelectListItem { Value = tz.Id, Text = tz.DisplayName };
return list;
}
}
So, now at 9:25 AM in China, Website hosted in USA, date saved in UTC at database, here is the final result:
5/9/2013 6:25:58 PM (Server - in USA)
5/10/2013 1:25:58 AM (Database - Converted UTC)
5/10/2013 9:25:58 AM (Local - in China)
EDIT
Thanks to Matt Johnson for pointing out the weak parts of original solution, and sorry for deleting original post, but got issues getting right code display format... turned out the editor has problems mixing "bullets" with "pre code", so I removed the bulles and it was ok.
In the events section on sf4answers, users enter an address for an event, as well as a start date and an optional end date. These times are translated into a datetimeoffset in SQL server that accounts for the offset from UTC.
This is the same problem you are facing (although you are taking a different approach to it, in that you are using DateTime.UtcNow); you have a location and you need to translate a time from one timezone to another.
There are two main things I did which worked for me. First, use DateTimeOffset structure, always. It accounts for offset from UTC and if you can get that information from your client, it makes your life a little easier.
Second, when performing the translations, assuming you know the location/time zone that the client is in, you can use the public info time zone database to translate a time from UTC to another time zone (or triangulate, if you will, between two time zones). The great thing about the tz database (sometimes referred to as the Olson database) is that it accounts for the changes in time zones throughout history; getting an offset is a function of the date that you want to get the offset on (just look at the Energy Policy Act of 2005 which changed the dates when daylight savings time goes into effect in the US).
With the database in hand, you can use the ZoneInfo (tz database / Olson database) .NET API. Note that there isn't a binary distribution, you'll have to download the latest version and compile it yourself.
At the time of this writing, it currently parses all of the files in the latest data distribution (I actually ran it against the ftp://elsie.nci.nih.gov/pub/tzdata2011k.tar.gz file on September 25, 2011; in March 2017, you'd get it via https://iana.org/time-zones or from ftp://fpt.iana.org/tz/releases/tzdata2017a.tar.gz).
So on sf4answers, after getting the address, it is geocoded into a latitude/longitude combination and then sent to a third-party web service to get a timezone which corresponds to an entry in the tz database. From there, the start and end times are converted into DateTimeOffset instances with the proper UTC offset and then stored in the database.
As for dealing with it on SO and websites, it depends on the audience and what you are trying to display. If you notice, most social websites (and SO, and the events section on sf4answers) display events in relative time, or, if an absolute value is used, it's usually UTC.
However, if your audience expects local times, then using DateTimeOffset along with an extension method that takes the time zone to convert to would be just fine; the SQL data type datetimeoffset would translate to the .NET DateTimeOffset which you can then get the universal time for using the GetUniversalTime method. From there, you simply use the methods on the ZoneInfo class to convert from UTC to local time (you'll have to do a little work to get it into a DateTimeOffset, but it's simple enough to do).
Where to do the transformation? That's a cost you are going to have to pay somewhere, and there's no "best" way. I'd opt for the view though, with the timezone offset as part of the view model presented to the view. That way, if the requirements for the view change, you don't have to change your view model to accommodate the change. Your JsonResult would simply contain a model with the IEnumerable<T> and the offset.
On the input side, using a model binder? I'd say absolutely no way. You can't guarantee that all the dates (now or in the future) will have to be transformed in this way, it should be an explicit function of your controller to perform this action. Again, if the requirements change, you don't have to tweak one or many ModelBinder instances to adjust your business logic; and it is business logic, which means it should be in the controller.
This is just my opinion, I think that MVC application should separate well data presentation problem from data model management. A database can store data in local server time but it's a duty of the presentation layer to render datetime using local user timezone. This seems to me the same problem as I18N and number format for different countries.
In your case, your application should detect the Culture and timezone of the user and change the View showing different text, number and datime presentation, but the stored data can have the same format.
For output, create an display/editor template like this
#inherits System.Web.Mvc.WebViewPage<System.DateTime>
#Html.Label(Model.ToLocalTime().ToLongTimeString()))
You can bind them based on attributes on your model if you want only certain models to use those templates.
See here and here for more details on creating custom editor templates.
Alternatively, since you want it to work for both input and output, I would suggest extending a control or even creating your own. That way you can intercept both the input and outputs and convert the text/value as needed.
This link will hopefully push you in the right direction if you want to go down that path.
Either way, if you want an elegant solution, its going to be a bit of work. On the bright side, once you have done it once you can keep it in your code library for future use!
This is probably a sledgehammer to crack a nut but you could inject a layer between the UI and Business layers which transparently converts datetimes to the local time on returned object graphs, and to UTC on input datetime parameters.
I imagine this could be achieved using PostSharp or some inversion of control container.
Personally, I'd just go with explicitly converting your datetimes in the UI...
I wanted to store dates as DateTimeOffset so that I could maintain the Time Zone Offset of the user that writes to the database. However, I wanted to only use DateTime inside of the application itself.
So, local time zone in, local time zone out. Regardless of who/where/when the user is looking at the data, it will be a local time to the observer - and changes are stored as UTC + local offset.
Here is how I achieved this.
1.
Firstly, I needed to get the web client's local time zone offset and store this value on the web server:
// Sets a session variable for local time offset from UTC
function SetTimeZone() {
var now = new Date();
var offset = now.getTimezoneOffset() / 60;
var sign = offset > 0 ? "-" : "+";
var offset = "0" + offset;
offset = sign + offset + ":00";
$.ajax({
type: "post",
url: prefixWithSitePathRoot("/Home/SetTimeZone"),
data: { OffSet: offset },
datatype: "json",
traditional: true,
success: function (data) {
var data = data;
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
alert("SetTimeZone failed");
}
});
}
The format is intended to match that of the SQL Server DateTimeOffset type.
SetTimeZone - just sets the value of Session variable. When the user logs on, I incorporate this value into the User profile cache.
2.
As a user submits a change to the database, I filter the DateTime value through a utility class:
cmdADO.Parameters.AddWithValue("#AwardDate", (object)Utility.ConvertLocal2UTC(theContract.AwardDate, theContract.TimeOffset) ?? DBNull.Value);
The Method:
public static DateTimeOffset? ConvertLocal2UTC(DateTime? theDateTime, string TimeZoneOffset)
{
DateTimeOffset? DtOffset = null;
if (null != theDateTime)
{
TimeSpan AmountOfTime;
TimeSpan.TryParse(TimeZoneOffset, out AmountOfTime);
DateTime datetime = Convert.ToDateTime(theDateTime);
DateTime datetimeUTC = datetime.ToUniversalTime();
DtOffset = new DateTimeOffset(datetimeUTC.Ticks, AmountOfTime);
}
return DtOffset;
}
3.
When I and reading in the date from the SQL Server, I am doing this:
theContract.AwardDate = theRow.IsNull("AwardDate") ? new Nullable<DateTime>() : DateTimeOffset.Parse(Convert.ToString(theRow["AwardDate"])).DateTime;
In the controller, I modify the datetime to match the local time of the observer. (I am sure someone can do better with an extension or something):
theContract.AwardDate = Utilities.ConvertUTC2Local(theContract.AwardDate, CachedCurrentUser.TimeZoneOffset);
The method:
public static DateTime? ConvertUTC2Local(DateTime? theDateTime, string TimeZoneOffset)
{
if (null != theDateTime)
{
TimeSpan AmountOfTime;
TimeSpan.TryParse(TimeZoneOffset, out AmountOfTime);
DateTime datetime = Convert.ToDateTime(theDateTime);
datetime = datetime.Add(AmountOfTime);
theDateTime = new DateTime(datetime.Ticks, DateTimeKind.Utc);
}
return theDateTime;
}
In the View, I am just displaying/editing/validating a DateTime.
I hope this helps someone who has a similar need.

How to work with time zones in ASP.NET?

I am working on an "online reminder system" project (ASP.NET 2.0 (C#) / SQL Server 2005)
As this is a reminder service which will send the mail to users on a particular dates. But the problem is users are not from a specific countries, they are from all over the world and from different time zones. Now When I am registering I am asking for users time zone in the same way as windows asks our time zone at the time of installation.
But I am not getting the if the user selected (+5.30) or something timezone then how to handle this time zone in my asp.net application. How to work according to timezone.
And please suggest if there is any better way to handle timezones in this application ??
Thanks
First thing is to make sure which time zone your data is in. I would recommend making sure that any DateTime that you store, is stored in UTC time (use the DateTime.ToUniversalTime() to get hold of it).
When you are to store a reminder for a user, you will need the current UTC time, add or remove the user's time zone difference, and convert that new time back to UTC; this is what you want to store in the DB.
Then, when you want to check for reminders to send, you simply need to look in the database for reminders to send out now, according to UTC time; essentially get all reminders that have a time stamp that is before DateTime.Now.ToUniversalTime().
Update with some implementation specifics:
You can get a list of time zones from the TimeZoneInfo.GetSystemTimeZones() method; you can use those to show a list of time zones for the user. If you store the Id property from the selected time zone, you can create a TimeZoneInfo class instance from it, and calculate the UTC time for a given local date/time value:
TimeZoneInfo tzi = TimeZoneInfo.FindSystemTimeZoneById("<the time zone id>");
// May 7, 08:04:00
DateTime userDateTime = new DateTime(2009, 5, 7, 8, 4, 0);
DateTime utcDateTime = userDateTime.Subtract(tzi.BaseUtcOffset);
I would recommend to always use UTC (GMT) time on the server side (in code-behind, database, etc), and convert time from UTC to local time for display purposes only. This means that all time manipulations - including saving time in database, performing calculations, etc - should be be done using UTC.
The problem is: how does your code-behind know what is the time zone of the client browser? Say the user enters some date/time value (such as 12/30/2009 14:30) in the form and submits it to the server. Assuming that the user submitted local time, how does the server know how to convert this value to UTC?
The application can ask the user to specify the time zone (and save it in a persistent cookie or database), but it requires and extra effort from the user, and your app would need to implement the logic and screens for this. It would be nicer if the app could determine client's time zone automatically.
I have addressed this issue with the help of JavaScript's getTimezoneOffset function, which is the only API that can tell the server about the time difference between local time on the client and GMT. Since this is a client-side API, I did the following: on the server side check for a custom session cookie holding the time offset value, and if it's not available, reload the page (only during GET, and not POST, calls) with some JavaScript logic added to generate the time offset and save it in the cookie. From the client-side this is almost transparent (once during session I reload a page on GET). Once I have the offset in the cookie, I apply it to the time management functions depending on direction of time conversion (UTC to local time, or local time to UTC).
This may sound a bit complicated, and it is, but after I wrote helper functions, integrating this feature in the site was a matter of making a single call in Page_Load (of pages that needed time conversion), and using time conversion routines when sending and retrieving time values to and from the browser. Here is an example of how it can be used:
using My.Utilities.Web;
...
// Derive the form class from BaseForm instead of Page.
public class WebForm1: BaseForm
{
...
private void Page_Load(object sender, System.EventArgs e)
{
// If we only want to load the page to generate the time
// zone offset cookie, we do not need to do anything else.
if (InitializeLocalTime())
return;
// Assume that txtStartDate is a TextBox control.
if (!IsPostback)
{
// To display a date-time value, convert it from GMT (UTC)
// to local time.
DateTime startDate = GetStartDateFromDB(...);
txtStartDate.Text = FormatLocalDate(startDate);
...
}
else
{
// To save a date-time value, convert it from local
// time to GMT (UTC).
DateTime tempDate = DateTime.Parse(txtStartDate.Text);
DateTime startDate = ConvertLocalTimeToUtc(tempDate);
SaveStartDateInDB(startDate, ...);
...
}
}
...
}
If you need more specifics, check out the It’s About Time: Localizing Time in ASP.NET Applications article (sorry, but I do not have a direct link to the article on the publisher's site, since asp.netPRO restricts access to paid subscribers only; there are links to PDF copies, though). I wish I could post the sample from the article, but I don't want to violate the copyright; however, here is a project to build a helper library that has all necessary functionality and documentation (just ignore the stuff you do not need).
UPDATE: The article has been posted online with sample project by the new publisher here.
The problem with all of the answers so far is that they do not take into account what Prashant is trying to achieve. If the user of his system on the day before daylight savings changes has an offset of +12 and sets a reminder for the next day, his offset when the reminder is supposed to be triggered will be +13 instead.
That is why you can only use current offset for something that is happening now. Although I do agree with everyone else that all times server-side (except possibly those used only for display) should be stored in UTC.
You may want to look at using the DateTimeOffset structure instead of the DateTime if you are on framework 2.0 or later.
The DateTimeOffset represents a point in time relative to UTC time, so it should be easier to work with in this case.
There are 2 steps:
Detect different timezone at client side using Javascript:
var dt = new Date();
var diffInMinutes = -dt.getTimezoneOffset();
Then at server side, C# code to convert server time to client time based on detected timezone offset above:
------------------------;
string queryStr = Request.QueryString["diffInMinutes"];
int diffInMinutes = 0;
if (Int32.TryParse(queryStr, out diffInMinutes))
{
clientTime = serverTime.ToUniversalTime().AddMinutes(diffInMinutes);
}
Basically, all you need to do is add the offset (hours + minutes) to the local time that the user has entered. Adding in the offset basically gives you a DateTime in the UTC (basically GMT) timezone.
It's usually easiest to standardize all your times to UTC, so that your application logic doesn't have to deal with the offsets.
This page has a few good examples: http://msdn.microsoft.com/en-us/library/bb546099.aspx
The issue is that the offset from UTC will vary at different times of the year -- each time zone has its own rules. (I learned this the hard way when developing meeting room scheduling app.)
Looks like there is built-in support here: http://msdn.microsoft.com/en-us/library/system.timezoneinfo.converttime.aspx
Haven't tried it myself but it seems to promise the correct conversion, accounting for daylight savings.
If not, here is a (pricey) commercial tool I have used: http://www.worldtimeserver.com/time_zone_guide/

Categories

Resources