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/
Related
I have one issue which I resolved by myself but yet need some confirming words whether I am 100% correct on my thought, just because there is not any documentation I found to prove myself correct.
My server is in DST time currently, CRM UI is also showing 1 hour up then data stored in db. that's fine.
When I calculate and store date with plugin, after my plugin update operation finishes, CRM platform deducts 1 hour from data I saved. I have read that when we do some operation via SDK related date time, CRM stores date time as it is. is it the case that when time is in DST, platform also get involves to deduct 1 hour by then ?
As a resolution, I have commented out my line of deducting 1 hour and letting CRM to do it now.
Am I correct on my understanding or it would be appreciable if some one can provide any documentation URL.
Any time you're working in the SDK, DateTimes are retrieved and stored as UTC. The CRM web platform will convert the UTC time into the user's time. You shouldn't need to be doing any Conversions of time, just using UTC.
Let's assume your local time is UTC -1 (with DST since UTC doesn't observe it). So if UTC is 14:00, your local time is 13:00.
Let's also assume your plugin in going to populate a date attribute on the entity that is for the current time, tomorrow. If your code looks like this:
entity.new_SomeDate = DateTime.Now.AddDays(1);
service.Update(entity);
Assuming DateTime.Now is 13:00, it'll store 13:00 in the database (as if it was UTC). Then when you go to look at the time value from the CRM website, since you're UTC - 1 it'll display 12:00, even though you wanted 13:00.
Now if your code looks like this:
entity.new_SomeDate = DateTime.UtcNow.AddDays(1);
service.Update(entity);
Assuming DateTime.Now is 13:00, it'll store 14:00 in the database since DateTime.UtcNow is 14:00. Then when you go to look at the time value from the CRM website, since you're UTC - 1 it'll display 13:00, since it'll take the UTC value - 1.
Now if your code looks like this:
entity.new_SomeDate = entity.new_UserEnteredDateFromCrm.AddDays(1);
service.Update(entity);
The new_UserEnteredDateFromCrm will already have been converted from the users' Time Zone to UTC and it'll work as expected.
This makes DateTimes that you would prefer to store as Dates very difficult though (birthdays anyone?) and you may have to think through it a little more in depth.
I've figured it out myself. that date has component in it which explores what kind of date time is it in. it can be either Utc, Local or Unspecified.
when you pass any date to CRM attribute via code. make sure that date time kind is Utc, otherwise CRM service internal operation will convert it to be into Utc.
In my case, I was stucked to this because when I read date from CRM, I had to set office start in that date. e.g. I needed to make 03/02/2014 12:00 to 03/02/2014 8:30 to make incoming date aligned with office start time. I was doing operation like,
DateTime InDate = Case.CreatedOn.Value;
DateTime Dt = new DateTime(InDate.Year,InDate.Month,InDate.Day,8,30,0)
Having InDate in Utc Time, I was creating new date object so it lost DateTime kind from Utc to Local ( having in DST it signifies to be 1 hour up)
to avoid, always set DateTime kind to be exactly as provided In date to new object. e.g. above operation can be done alternatively like.
DateTime InDate = Case.CreatedOn.Value;
DateTime Dt = new DateTime(InDate.Year,InDate.Month,InDate.Day,8,30,0)
Dt = DateTime.SpecifyKind(Dt,DateTimeKind.Utc)
Hope that Helps.
I have an object that has properties currently as DateTime.
The object is marked as valid within a time frame. The default being 00:00:00 to 23:59:59
The user enters the value in the UI and the property is set via:
new DateTime(DateTime.Now.Year,
DateTime.Now.Month,
DateTime.Now.Day,
model.Hours,
model.Minutes,
model.Seconds)
This is then converted to UTC when it hits the database.
Today's date is 29th August 2013. If a colleague in India runs this program it will store the data in the database as 28th August 2013 18:30:00 as they are 5.5 hours ahead of UTC so 29th August 2013 00:00:00 becomes yesterday.
When the logic tries to determine if the object is valid the logic is:
if (DateTime.UtcNow.TimeOfDay > model.MyPropertyFromDB.TimeOfDay)
We are trying to determine if the current time is within a range of 00:00:00 and 23:59:59
This fails as 14:00 (current time) is not greater than 18:30
What would be the best approach to compare just times?
Would storing the values as DateTimeOffSet help, is using ToLocal() ok?
Other considerations are that a user in India is using the app which is hosted in the UK so it needs to be timezone aware.
Thanks
Like others, I'm still unclear on exactly what you are wanting. But clearly, you shouldn't do this:
new DateTime(DateTime.Now.Year,
DateTime.Now.Month,
DateTime.Now.Day,
model.Hours,
model.Minutes,
model.Seconds)
That would be much better as:
DateTime.Today.Add(new TimeSpan(model.Hours, model.Minutes, model.Seconds))
But why are you doing this to begin with? Either of these would give you back the local date. I assume this is going to run on a server, so do you really want the time zone of the server to influence this result? Probably not. Please read: The Case Against DateTime.Now.
If you wanted the UTC date, you could do this:
DateTime.UtcNow.Date.Add(new TimeSpan(model.Hours, model.Minutes, model.Seconds))
That would at least be universally the same regardless of your server's time zone. But still, I don't think this is what you are after.
What's not clear is why is the user only entering the time while you are assigning the current date. If the date is relevant, then shouldn't the user enter it and it would be part of your model?
If the date is not relevant, then why are you storing it? You can use a TimeSpan type for the time value internally. You didn't say what your database is, but let's just guess that it is SQL Server, in which case you could use the time type on the field in the table.
I suppose it's possible that the date is relevant, but you want to control it, while the user takes control of providing the time. If that's the case, then you must know the time zone of the user (or the time zone of whatever the context is if it's not the user). Assuming you had a Windows time zone identifier (see the timezone tag wiki), then you could do something like this:
var tz = TimeZoneInfo.FindSystemTimeZoneById(theTimeZoneId);
var local = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
var dt = local.Date.Add(new TimeSpan(model.Hours, model.Minutes, model.Seconds));
If you don't have the time zone information, then this wouldn't be possible to solve.
As general advice, you might want to try using Noda Time instead of the built-in stuff. It's much better at helping you figure out this sort of thing. From the main page:
Noda Time is an alternative date and time API for .NET. It helps you to think about your data more clearly, and express operations on that data more precisely.
That appears to be directly the problem you are having here. If you want to clarify some of the questions I asked, I'd be happy to edit my answer and show you exactly how to do this with Noda Time.
Why your question is confusing
We are trying to determine if the current time is within a range of 00:00:00 and 23:59:59
All times are within that range. Well, maybe a value like 23:59:59.1 would be outside of it, but you aren't collecting fractional seconds in your model, so that's irrelevant. But why would you need to validate that? Maybe you are just trying to avoid numbers that aren't valid times at all? Like 99:99:99?
This fails as 14:00 (current time) is not greater than 18:30
Wait - you didn't say anything about comparing one time greater than another. 14:00 and 18:30 are both still in the range you specified.
What would be the best approach to compare just times?
Hard to answer. Are they both UTC times? Is one UTC and one is local? Are they both local? Do you know the time zone of the local times? Are you prepared to deal with ambiguous or invalid local times do to daylight saving time transitions?
Would storing the values as DateTimeOffSet help?
Perhaps, but you haven't given me enough information. It would help only if the date portion is relevant and the you get the correct offsets.
is using ToLocal() ok?
I would argue that no, it's not ok. Local in this context will give you the time zone of the server, which you probably don't want to introduce into your business logic.
So if I understand this correctly you have a time saved in UTC in the database and you are trying to determine whether it falls within a particular time frame? I'm not sure if you want the time frame in local time or UTC so here are both:
DateTime dbTime = model.MyPropertyFromDB;
TimeSpan minTime = new TimeSpan(0, 0, 0);
TimeSpan maxTime = new TimeSpan(23, 59, 59);
if (dbTime.TimeOfDay > minTime && dbTime.TimeOfDay < maxTime)
{
//Within time range (UTC)
}
if (dbTime.ToLocalTime().TimeOfDay > minTime && dbTime.ToLocalTime().TimeOfDay < maxTime)
{
//Within time range (local)
}
Edit: If you want to compare Now to a start and end time from an object in database:
TimeSpan now = DateTime.UtcNow.TimeOfDay;
TimeSpan startDate = model.startDate.TimeOfDay;
TimeSpan endDate = model.endDate.TimeOfDay;
if (now > startDate && now < endDate)
{
//Within time range (UTC)
}
I would say that the methodology being used here is fundamentally flawed and that you need to take a different approach.
new DateTime(DateTime.Now.Year, // Server date
DateTime.Now.Month,
DateTime.Now.Day,
model.Hours, // Local time
model.Minutes,
model.Seconds)
I can't see a way of 'normalising' the input in this way, unless you have a way of reliably knowing exactly which timezone a user is in. Simply, there's no easy way to turn a date built in this way into UTC.
My first question to you is, how is the model being passed from client to server? If you're using javascript/ajax then the solution should be fairly straightforward to solve by constructing the datetime object on the client (which will include their timezone data) and then rely on the browser to convert it to UTC for transit.
If you are using Razor\MVC then you can achieve a similar thing with forms encoding, except that you will need to call ToUTC on the server as the browser won't automatically fix the date for you for this media format.
Both methods require that you build a full datetime object on the client and then submit it, rather than trying to build it from seconds, minutes, hours on the server. You don't need to expose all this to the client of course, as long as the datetime is fully formed at the point of submission.
Once you've got a nice UTC datetime, you can extract just the time if you don't need the rest of it.
Hope this helps.
Pete
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.
I have a C# time application where the user can create a timer and start it, it will do something like that on the server:
(the dates are stored in CEST (the problem is summer and winter time))
// insert into db
var entry = new TimeEntry();
entry.Start = DateTime.Now; (04/03/2013 12:00)
_timeEntries.Add(entry); // => now it's in the db
if he stops it then it do something like that:
// update the entry
var entry = _timeEntries.Get(1) // 1 is the id from the created entry before
entry.End = DateTime.Now; (04/03/2013 12:37)
_timeEntrires.Update(entry); // => now it updates it in the db
Something like that will be shown for the user in the browser:
TimerId: 1
Start: 04/03/2013 12:00
Stop: 04/03/2013 12:25
Total (in min): 25
Now I think there will be some problems when the user changes his timezone.
What's the best way to solve this? Store the dates as UTC and then convert with JavaScript?
Thanks for your helps :-).
From : Determine a User's Timezone
You will have to pass the offset from the client to the server like this:
new Date().getTimezoneOffset()/60;
getTimezoneOffset() will subtract your time from GMT and return the
number of minutes. So if you live in GMT-8, it will return 480. To put
this into hours, divide by 60. Also, notice that the sign is the
opposite of what you need -- it's calculating GMT's offset from your
time zone, not your time zone's offset from GMT. To fix this, simply
multiply by -1.
Since these are event times, you should be working strictly with UTC, and taking that value from the server.
When the client application calls you with whatever you're using for start and stop input commands, use DateTime.UtcNow() to get the time and save that in the database.
You really don't want to use local time at all for the recording of these times. Otherwise, if the start or end fall within an ambiguous period (such as a DST/SummerTime "fall-back" transition), you would have no way of knowing if the event fell before or after the transition.
Context always matters with DateTime questions, and in the context of "event times" - your only reliable options are UTC DateTime, or DateTimeOffset. Local calendar times (client or server) are not good for specific event times. They are useful in other contexts though.
When you actually show the data back to your user, if you're just showing the duration between start and end, then you can simply calculate it from the UTC times you recorded. If you want to show the specific times back to the user, that's when you would convert the UTC time to whatever time zone the user wants to display it in. If you opt for DateTimeOffset, the advantage is that you don't have to convert at this stage.
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.