31

Our application was designed to handle user from different Geographic location.

We are unable to detect what is the current end user local time and time zone operate on it. They select different culture like sv-se, en-us, ta-In even they access from Europe/London timezone..

We hosted it in a hosting server in US, application users are from Norway/Denmark/Sweden/UK/USA/India

The problem is we used DateTime.Now to store the record created/updated date, etc.

Since the Server runs in USA all user data are saved as US time :(

After researching in SO, we decided to stored all history dates in DB as DateTime.UtcNow

PROBLEM:

enter image description here

There is a record created on 29 Dec 2013, 3:15 P.M Swedish time.

 public ActionResult Save(BookingViewModel model)
    {
        Booking booking = new Booking();
        booking.BookingDateTime = model.BookingDateTime; //10 Jan 2014 2:00 P.M
        booking.Name = model.Name;
        booking.CurrentUserId = (User)Session["currentUser"].UserId;
        //USA Server runs in Pacific Time Zone, UTC-08:00
        booking.CreatedDateTime = DateTime.UtcNow; //29 Dec 2013, 6:15 A.M
        BookingRepository.Save(booking);
        return View("Index");
    }

We want to show the same history time to the user who logged in in India/Sweden/USA.

As of now we are using current culture user logged in and choose the timezone from a config file and using for conversion with TimeZoneInfo class

<appSettings>
    <add key="sv-se" value="W. Europe Standard Time" />
    <add key="ta-IN" value="India Standard Time" />
</appSettings>

    private DateTime ConvertUTCBasedOnCulture(DateTime utcTime)
    {
        //utcTime is 29 Dec 2013, 6:15 A.M
        string TimezoneId =                  
                System.Configuration.ConfigurationManager.AppSettings
                [System.Threading.Thread.CurrentThread.CurrentCulture.Name];
        // if the user changes culture from sv-se to ta-IN, different date is shown
        TimeZoneInfo tZone = TimeZoneInfo.FindSystemTimeZoneById(TimezoneId);

        return TimeZoneInfo.ConvertTimeFromUtc(utcTime, tZone);
    }
    public ActionResult ViewHistory()
    {
        List<Booking> bookings = new List<Booking>();
        bookings=BookingRepository.GetBookingHistory();
        List<BookingViewModel> viewModel = new List<BookingViewModel>();
        foreach (Booking b in bookings)
        {
            BookingViewModel model = new BookingViewModel();
            model.CreatedTime = ConvertUTCBasedOnCulture(b.CreatedDateTime);
            viewModel.Add(model);
        }
        return View(viewModel);
    }

View Code

   @Model.CreatedTime.ToString("dd-MMM-yyyy - HH':'mm")

NOTE: The user can change the culture/language before they login. Its a localization based application, running in US server.

I have seen NODATIME, but I could not understand how it can help with multi culture web application hosted in different location.

Question

How can I show a same record creation date 29 Dec 2013, 3:15 P.M for the users logged in INDIA/USA/Anywhere`?

As of now my logic in ConvertUTCBasedOnCulture is based user logged in culture. This should be irrespective of culture, since user can login using any culture from India/USA

DATABASE COLUMN

CreatedTime: SMALLDATETIME

UPDATE: ATTEMPTED SOLUTION:

DATABASE COLUMN TYPE: DATETIMEOFFSET

UI

Finally I am sending the current user's local time using the below Momento.js code in each request

$.ajaxSetup({
    beforeSend: function (jqXHR, settings) {
        try {
      //moment.format gives current user date like 2014-01-04T18:27:59+01:00
            jqXHR.setRequestHeader('BrowserLocalTime', moment().format());
        }
        catch (e) {
        }
    }
});

APPLICATION

public static DateTimeOffset GetCurrentUserLocalTime()
{
    try
    {
      return 
      DateTimeOffset.Parse(HttpContext.Current.Request.Headers["BrowserLocalTime"]);
    }
    catch
    {
        return DateTimeOffset.Now;
    }
}

then called in

 model.AddedDateTime = WebAppHelper.GetCurrentUserLocalTime();

In View

@Model.AddedDateTime.Value.LocalDateTime.ToString("dd-MMM-yyyy - HH':'mm")

In view it shows the local time to user, however I want to see like dd-MMM-yyyy CET/PST (2 hours ago).

This 2 hours ago should calculate from end user's local time. Exactly same as stack overflow question created/edited time with Timezone display and local user calculation.

Example: answered Jan 25 '13 at 17:49 CST (6 hours/days/month ago) So the other viewing from USA/INDIA user can really understand this record was created exactly 6 hours from INDIA/USA current time

Almost I think I achieved everything, except the display format & calculation. How can i do this?

DL Narasimhan
  • 731
  • 9
  • 25
Billa
  • 5,226
  • 23
  • 61
  • 105
  • 3
    If you want to store date/times including time zone information, shouldn't you be storing `DateTimeOffset` values? –  Dec 25 '13 at 19:29
  • @hvd, I didnt get you. You mean want me to change `DateTime.UtcNow` to `DateTimeOffset.UtcNow?` How it will resolve my different end user choosing different culture problem? – Billa Dec 25 '13 at 19:33
  • @hvd, SQL server columns are `SMALLDATETIME` – Billa Dec 25 '13 at 19:34
  • I may have misunderstood you, I thought that by "We want to show the same history time" you meant you want to store the fact that the time should be displayed as IST. –  Dec 25 '13 at 19:35
  • "The problem is we used DateTime.Now to store the record created/updated date, etc." - that feels like the fundamental problem to fix, after which most of the rest should just sort itself out. It's really not clear what you're asking about beyond that. – Jon Skeet Dec 30 '13 at 16:16
  • Your question is confusing, especially the diagram. You talk about many locales, appointment and date/time and record creation date/time, but it is not clear what your requirement is within a locale for each date time field; in addition your QUESTION asks only about record creation time. – G. Stoynev Dec 30 '13 at 16:30
  • @JonSkeet, Yes I got it and I changed it to `DateTime.UtcNow` instead of `DateTime.Now` in `Save()`. The server runs in USA, and stores end user local time ` 29 Dec 2013, 3:15 P.M` to UTC time `29 Dec 2013, 6:15 A.M`. Initially the user seen the history with `29 Dec 2013, 6:15 A.M`, then we started using the culture based calculation code shown in `ConvertUTCBasedOnCuture()` to convert back to `29 Dec 2013, 3:15 P.M`. It was working fine. Now the problem is an Admin user, from India viewing a different time for his culture ta-IN or en-US. He should be able to see same 29 Dec 2013, 3:15 P.M. – Billa Dec 30 '13 at 16:36
  • @Billa: Well if you need to be able to get back to the same *local* time, you'll need to store a `DateTimeOffset` instead of a `DateTime` - and you'll need to make the database field a DateTimeOffset field too. – Jon Skeet Dec 30 '13 at 16:39
  • @JonSkeet, if I store it as `DateTimeOffset` in database, how can I show/convert for India user to see `29 Dec 2013, 3:15 P.M`? Is there any sample please. Sorry I was totally confused how to go with this new approach `DateTimeOffset` :( – Billa Dec 30 '13 at 16:44
  • @Billa: I've added an answer, but the question is a bit confused at the moment as there are lots of potential aspects. Basically you should store a `DateTimeOffset`, and you need to determine the offset at the client rather than the server. – Jon Skeet Dec 30 '13 at 16:57
  • > @Billa - We are unable to detect what is the current end user local time and time zone operate on it. They select different culture like sv-se, en-us, ta-In even they access from Europe/London timezone." - If you give them a selection, why can't you detect what they've chosen? – drankin2112 Jan 02 '14 at 23:51
  • @Billa: What's the status of this? You haven't left any comments for a while, so we don't know whether you're now happy with the answers... – Jon Skeet Jan 04 '14 at 09:08
  • @JonSkeet, I am working on this. Discussing with our team and it will take some time to update Hosting server and test it in real scenario. However we are in confusion, whether we go for your/Matt Johnson approach. All of us are having only 1 year experience in IT development and struggling to implement both approaches. However we need to quickly take a decision for this bounty offer. I should mark either you or Matt. – Billa Jan 04 '14 at 09:42
  • @Billa: Basically finding the *actual* time zone of the user is harder than finding the UTC offset at a particular date/time. However, it gives more information about *other* dates and times. We can't really tell whether you need that other information, or whether you just need the offset for each individual value that you record. – Jon Skeet Jan 04 '14 at 09:43
  • @JonSkeet, we are changing the code based on DateTime offset, like `Comment.AddedDateTime = DateTimeOffset.Now;` and SQL side column,parameter type is `DATETIMEOFFSET` and displaying `@Model.AddedDateTime.ToString("dd-MMM-yyyy - HH':'mm")`. We will update this in server and let you know. Thank you so much for helping us – Billa Jan 04 '14 at 10:35
  • @Billa: If you're using `DateTimeOffset.Now` on the *server* side, that will use the *server's* time zone to work out the offset - you don't want that. You want the *client's* offset. – Jon Skeet Jan 04 '14 at 10:51
  • @JonSkeet, How can I get client datetime? :( Thats what my problem, I could not grab that from Request Header. Do i need to send the current user time stored in hidden field using Javascript and send it to server? If i send that, I think i no need of changing DateTimeOffset. – Billa Jan 04 '14 at 11:12
  • @Billa: As I've said in my answer, you'll need to use Javascript. The reason to store a DateTimeOffset is so that you know the *absolute* time as well as the local time - just storing the local time means you'll never be able to compare two times, for example. – Jon Skeet Jan 04 '14 at 11:14
  • @JonSkeet, I got you. But I need to think about sending a Javascript new Date() to server and save it to equivalent DateTimeOffset. I believe I should use [Moment.js](http://momentjs.com) and send the valid 'CurrentBrowserOffsetTime' as string and use `DateTimeOffset.Parse(currentJSOffsettime)`. Is this sounds good? – Billa Jan 04 '14 at 11:28
  • @Billa: I haven't used Moment.js, but it sounds like it might be okay. – Jon Skeet Jan 04 '14 at 11:31
  • @JonSkeet, I tried and got atmost close to target. I updated my question `UPDATE:ATTEMPTED SOLUTOIN`. I think now i can display in the UI like what i want. But struggling for calculation and CST/IST display... – Billa Jan 04 '14 at 17:32
  • @hvd, Thank you. You gave me a right clue, initially i missed, but jon skeet corrected me :) – Billa Jan 04 '14 at 22:51
  • @Billa: You've changed the requirements. Before you only said you needed the original local time ("3:15 PM") to be displayed. Now you're saying you need to display the original time zone as well. That's a different matter. Stack Overflow doesn't really work well for a question which keeps changing. Note that the "2 hours ago" part does *not* need to be computed from any time zone - wherever we are in the world, two hours ago is two hours ago. – Jon Skeet Jan 04 '14 at 22:54
  • @JonSkeet, I was forced to change the display format after we discussed with our client. The current solution which I arrived was exactly fit for the initial requirement and when I shown this to our stackholder, they proposed new format :( Still working on it.. It was really good that I got answer for my original question with your help. Thank you!!! – Billa Jan 04 '14 at 23:22
  • @Billa: It would have been better to start a new question, to be honest - after doing research on it, of course. It looks like you need to go to plan B: detecting (with potential user intervention) the user's time zone, and storing that along with the date/time value. – Jon Skeet Jan 04 '14 at 23:23
  • Additionally: I believe there are other SO questions about detecting time zones in Javascript... and you'll need to decide whether to use the .NET framework time zones or the TZDB ones (with Noda Time). – Jon Skeet Jan 04 '14 at 23:28
  • @JonSkeet, Will I use the same approach and get it from DateTimeOffset stored? I think it included DATETIME and Timezone offset included. I am still in confuse why should i go with DATETime and Timezone columns – Billa Jan 04 '14 at 23:29
  • @Billa: Well an offset doesn't tell you the time zone. For example, and offset of UTC+1 could be British Summer Time or Central European Standard Time. You could still store a `DateTimeOffset` - that would make it easy to get back to the local time - or you could store the instant (i.e. a DateTime in UTC) and the time zone. – Jon Skeet Jan 04 '14 at 23:31
  • @JonSkeet, I think Matt Johnsons answer talks about it.. All i need to do is `save AddedDateTime in DateTime.UtcNow and a TimeZone string (set in header using javascript) into database`. While displaying i need to use stored UTC time and Timezone for date and a other user's timezone detected from request for showing `hours ago, etc`. Let me start working on this, if I am correct! i keep update on this:) – Billa Jan 04 '14 at 23:39
  • @Billa: No, you don't need to use any user's time zone *at all* for "hours ago" - just take the stored UTC time from the current UTC time. You use the user's time zone to show "3:15 CST" (or whatever). – Jon Skeet Jan 04 '14 at 23:40
  • @JonSkeet, I think I got you. `ActualDifference=current UTC time - stored UTC time`. Its really hard to work with DATETIME concepts like this, I never expected it will be tricky. Good time to start using NODATIME – Billa Jan 04 '14 at 23:47
  • @Billa: It's worth reading http://nodatime.org/unstable/userguide/concepts.html (and other bits of the user guide) early. – Jon Skeet Jan 04 '14 at 23:49

5 Answers5

16

It sounds like you need to store a DateTimeOffset instead of a DateTime. You could just store the local DateTime to the user creating the value, but that means you can't perform any ordering operations etc. You can't just use DateTime.UtcNow, as that won't store anything to indicate the local date/time of the user when the record was created.

Alternatively, you could store an instant in time along with the user's time zone - that's harder to achieve, but would give you more information as then you'd be able to say things like "What is the user's local time one hour later?"

The hosting of the server should be irrelevant - you should never use the server's time zone. However, you will need to know the appropriate UTC offset (or time zone) for the user. This cannot be done based on just the culture - you'll want to use Javascript on the user's machine to determine the UTC offset at the time you're interested in (not necessarily "now").

Once you've worked out how to store the value, retrieving it is simple - if you've already stored the UTC instant and an offset, you just apply that offset and you'll get back to the original user's local time. You haven't said how you're converting values to text, but it should just drop out simply - just format the value, and you should get the original local time.

If you decide to use Noda Time, you'd just use OffsetDateTime instead of DateTimeOffset.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    I updated my question. Please let me know if it is not clear, so I try to add as much as possible for easy understanding :) – Billa Dec 30 '13 at 16:58
  • 2
    @Billa: I don't think anything in the updated question affects my answer... it still stands. You should store a `DateTimeOffset` and then it's easy to retrieve the local time. The tricky bit is working out the `DateTimeOffset` to store - and that can't be done via culture; you'll need to use Javascript. – Jon Skeet Dec 30 '13 at 17:14
  • In a project I got in the middle, we had the same requirement, and the previous architect never try to solve this question in the beginning of the project. So the solution went to in each user that was created, there was presented the option to choose the time zone (from the country we only pre-selected the first of the "valids" timezones for that country). Whit that information we could get the OffsetDateTime, and to prevent the change in all Views to calculate the data, we went to the data layer (custom company API) and added a new parameter in every query that returned date/time columns... – Paulo Correia Dec 30 '13 at 17:42
  • .. and in the querys we added the logic to make the calculations on SQL Server side. So every datetime value was "corrected" for the user time-zone in the database. We also did the same, when we did an Insert or Update, but did the inverted operation (from the time-zone offset to UTC) – Paulo Correia Dec 30 '13 at 17:43
11

Standard approach is to always store any time data as UTC if particular moment in time is important. That time is not impacted by time zone changes and cultures.

Most common approach to showing time with time zone is to store time as UTC and convert to current user's culture/time zone combination when you display the value. This approach only requires single date time filed in the storage.

Note that for Web cases (like ASP.Net) you may need to figure out user's culture/time zone first and send it to server (as this information is not necessary available on GET requests) or do formatting of time in the browser.

Depending what "show the same history time" you may need to store additional information like current culture and/or current offset. If you need to show time exactly as original user seen it you may also save string representation (because formats/translations can change later and value will look different, also it is unusual).

Note: culture and time zone are not tied together, so you'll need to decide how you need to handle cases like IN-IN culture in US PST time zone.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
10

I'm a little confused by the phrasing of your question, but it appears that you would like to determine the time zone of your user.

  • Have you tried asking them? Many applications have the user pick their time zone in user settings.

  • You could pick from a drop-down list, or a pair of lists (country, then time zone within the country), or from a map-based time zone picker control.

  • You could take a guess and use that as the default unless your user changes it.

If you go down that route, you will need to be able to use IANA/Olson time zones, which is where Noda Time comes into play. You can access them from DateTimeZoneProviders.Tzdb.

The hosting location is irrelevant if you are using UTC. That's a good thing.

Also, if you're using Noda Time, then you probably should use SystemClock.Instance.Now instead of DateTime.UtcNow.

See also here and here.

Also - an alternative solution would be just to pass the UTC time to the browser and load it into a JavaScript Date object. The browser can convert that to the user's local time. You could also use a library like moment.js to make this easier.


Update

Regarding your approach of mapping culture codes to time zones:

<appSettings>
    <add key="sv-se" value="W. Europe Standard Time" />
    <add key="ta-IN" value="India Standard Time" />
</appSettings>

That will not work, for several reasons:

  • Many people use a different culture setting on their computer than the area that they are physically in. For example, I might be an a US-English speaker living in Germany, my culture code is likely still en-US, not de-DE.

  • A culture code containing a country is used to distinguish between dialects of a language. When you see es-MX, that means "Spanish, as spoken in Mexico". It does not mean that the user is actually in Mexico. It just means that user speaks that dialect of Spanish, as compared to es-ES which means "Spanish, as spoken in Spain".

  • Even if the country portion of the culture code could be reliable, there are many countries that have multiple time zones! For example, what would you put in your mapping list for en-US? You can't just assume that we are all on Eastern Standard Time.

Now, I've explained why your current approach won't work, I strongly suggest you take my original advice. Very simply:

  1. Determine the time zone of the user, preferably by asking them, perhaps with some assistance by one of the utilities I linked to above.

  2. You're storing UTC, so just convert to that time zone for display.

    Using Microsoft Time Zones
    TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
    DateTime localDatetime = TimeZoneInfo.ConvertTimeFromUtc(yourUTCDateTime, tz);
    
    Using IANA Time Zones and Noda Time
    DateTimeZone tz = DateTimeZoneProviders.Tzdb["Europe/Stockholm"];
    Instant theInstant = Instant.FromDateTimeUtc(yourUTCDateTime);
    LocalDateTime localDateTime = theInstant.InZone(tz);
    
Community
  • 1
  • 1
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Thanks a lot. I will go through it.. Basically end user will access from `Europe/Stockholm` and application will be hosted in `USA`. `Administrator from INDIA` will review the no of appointments made by sweden users with the time(history). Here the sweden people will select sv-se culture and Indian people will select In-In culture, `for localization view` – Billa Dec 28 '13 at 17:49
  • Updated a question with a visual picture for simple understanding – Billa Dec 29 '13 at 15:47
  • That's a nice picture, but I still don't understand what your question is. Culture only has to do with formatting, not with time zones. They are not related at all. – Matt Johnson-Pint Dec 29 '13 at 18:23
  • Now I added some coding with more explanation. Please let me know if it helps. Thank you! – Billa Dec 30 '13 at 13:52
  • Jon recommends using `DateTimeOffset`. How about his implementation?, still requires lots of sql table structure change:( – Billa Dec 30 '13 at 18:44
  • 1
    `DateTimeOffset` has its advantages. I use it all the time. I'm not sure if it helps you here or not, since I still don't fully understand what you are trying to accomplish. You can review the differences [here](http://stackoverflow.com/questions/4331189/datetime-vs-datetimeoffset) – Matt Johnson-Pint Dec 30 '13 at 18:48
  • How to send the browser date to c# in so that i can convert the same using DateTimeOffset.Parse() with the help of moment.js? Planning to try DateTimeOffset suggested by Jon – Billa Jan 04 '14 at 13:15
  • I updated my question. I think I achieved almost except display calculation. Thank you for moment js – Billa Jan 04 '14 at 17:35
  • Adding to the options in the answer, `TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tz)` would be returning `DateTimeOffset` instead of `DateTime`. – sunside Nov 30 '15 at 15:16
1

We faced a similar problem with an application I worked on recently. During development every one was in the same time zone and the issue wasn't noticed. And in any case there was a lot of legacy code that would have been a pain to change not to mention converting all the date time info that was already in the DB. So changing to DateTimeOffset was not an option. But we managed to get it all consistent by converting from server time to user time on the way out and converting from user time to server time on the way in. It was also important to do this with any date time comparisons that were a boundary. So if a user expected some thing to expire midnight their time then we would convert that time to server time and do all comparisons in server time. This sounds like a lot of work but was much less work then converting the entire application and DB to use DateTimeOffsets.

Hear is a thread that looks like has some good solutions to the time zone issue.

Determine a User's Timezone

Community
  • 1
  • 1
SzabV
  • 244
  • 1
  • 6
  • On what basis you made a conversion? – Billa Jan 04 '14 at 10:03
  • For us it was a bit easier as we could assume a single time zone to convert from. The servers were in Australian Eastern Standard Time and the users where all in US Eastern time. However as a number of people have pointed out figuring out what time zone the users are in is a different problem. The key is to store all times consistently. – SzabV Jan 04 '14 at 10:08
  • 1
    To figure out what time zone the client is in either need to get it from the user or make assumptions based on their location. The culture is definitely a red haring. I spent 6 years in Thailand and the entire time my computer was set to en-AU. – SzabV Jan 04 '14 at 10:10
  • How you detect their location and timezone? – Billa Jan 04 '14 at 10:14
  • Well you can ask the browser using JS. Or you can look up the client IP in an IP database. Either way you will not get it right 100% of the time so letting the user choose it wont hurt either. Telling the user what time zone they are being assumed to be in wouldn't hurt either. – SzabV Jan 04 '14 at 10:18
0

If you want to show consistent date/time history to the user, regardless of the locale they are viewing the history from, then:

  1. During Save, store not only UTC "creation" date/time, but also the detected locale
  2. Use stored saved from locale to compute original date/time and emit a string to display (i.e. do not use current user locale when you're diplaying it)

If you don't have the ability to amend your storage, then perhaps you can change your submit to send the "current client time", store it literally (do not convert to UTC) and then display literally (do not convert to detected culture)

But as I say in my comment under your question, I am not certain I got your requirements right.

G. Stoynev
  • 7,389
  • 6
  • 38
  • 49