4

my javascript app must create events, which will be stored as UTC in the database, so that they can be shown afterwards in 3 different timezones.

the tricky part that i found difficult to figure out is that, when creating the event, the user must select a timezone along with the date.

The idea is: - user selects date + desired timezone from an additional dropdownlist with timezones. - i store as UTC in database - all users see the date in 3 different timezones.

You might ask why would anyone need that additional dropdownlist to select another timezone, when selecting the date with a datepicker ALREADY contains a timezone by default.

Example: Let be Jim, US citizen, who planned events all his life using EDT Washington time; he is visiting China, enters a chinese internet cafe, and wants to plan an event using this app. the datepicker will choose the local timezone which is China Standard Time. But Jim wants to plan by EDT and to be sure that the app handles everything properly.

Therefore he must specifically choose from an additional dropdownlist, the desired timezone.

So my question is. because i allow the user to select the desired timezone, do i first have to CONVERT the date the user entered, to THAT timezone, before converting it to UTC and only then afterwards store it? Or am I not interested in the timezone conversion at all, when saving the event in the database?

So which step is correct: - get local date + selected timezone - convert local date to user selected timezone - convert date to UTC - store to DB - when reading, convert in 3 timezones by using the selected timezone

or - get local date + selected timezone - convert date to UTC, disregard timezone - store to DB - when reading, convert in 3 timezones by using the selected timezone


later edit - i do this in meteor , so javascript server side. DB is mongodb and therefore for performance reasons the date must be saved as a JS date object (in utc ofcourse).


2nd edit

Below is the implementation i tried (it does not work, as in when I enter an event date of 07:00 AM KST, when outputing the end result read back from the database and converted back in that KST timezone shows anything but 07:00 AM)

it all starts here - this is a server side method which reads the date from a datepicker, the time from a timepicker, and the timezone from a dropdownlist:

var pStartDate = GetDateAndTimeFromPostData(eventAttributes.startDate, eventAttributes.startTime, eventAttributes.timezone);

here I attempt to build the selected date from the different controls (datepicker, timepicker, timezone ddl):

function GetDateAndTimeFromPostData(dt, tm, timezone)
    {
        var t = tm.split(":");
        var hour =  t[0];
        var min = t[1];

        var finalDate = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), hour, min);
        var utcConverted =  ConvertUserTimezoneToServerTimezone(finalDate, timezone);

        return utcConverted;
    }

here i attempt the timezone conversion:

function ConvertUserTimezoneToServerTimezone(dateToConvert, tz)
    {
        var userTimezonedDate;

        switch(tz)
        {
            case "EDT":
            {
                userTimezonedDate = moment.tz(dateToConvert, "America/New_York");
                break;
            }
            case "CEST":
            {
                userTimezonedDate = moment.tz(dateToConvert, "Europe/Berlin");
                break;
            }
            case "KST":
            {
                userTimezonedDate =  moment.tz(dateToConvert, "Asia/Seoul");
                break;
            }
            case "CST":
            {
                userTimezonedDate = moment.tz(dateToConvert, "Asia/Shanghai");
                break;
            }
        }

        var utcDateFromUserTimezonedDate = userTimezonedDate.utc().toDate();
        return utcDateFromUserTimezonedDate; 
    }

The fault is already in the code above, as in the utc date is not saved as KST, but as GMT (my local timezone).

As a side note, i dont quite understand how moment timezone converts; when I go on the moment timezone website and I write in chrome dev tools this:

var x = new Date();
var y = moment.tz(x, "Asia/Seoul");
y.utc().toDate()

i actually expect back a date object that shows KST right? but it shows GMT+2, my local tz.

Mon Sep 08 2014 23:44:05 GMT+0200 (Central Europe Daylight Time)

I also tried thinking this backwards, like how should the stored date object look like but thats also confusing - should it be saved as the selected timezone? e.g. Mon Sep 08 2014 23:44:05 GMT+0900 (KST) If not, how must my stored date object look like?

again, i use javascript server side with meteor and mongoDB as the database.

many thanks,

Amc_rtty
  • 3,662
  • 11
  • 48
  • 73

2 Answers2

6

Neither of the choices you provided are appropriate.

  • If you're allowing the user to schedule an event in US Eastern Time, then he will be entering the time in that time zone. The fact that he's currently in China is irrelevant. So converting from his local China Standard Time to UTC is not something you'd want to do.

  • Since you are storing future events, storing only a UTC value is not the best advice. You should store the original entered value, and the original selected time zone. You can store the UTC time as well, but you should be prepared to recalculate it at any time.

    This is important, because time zone rules might change between the time the event was entered and the actual time of the event. For example, perhaps instead of the USA, the user is in Russia. There are changes coming this year (2014), and will require an update to your time zone data. If the event was scheduled before you applied the data update, you will have used the old rules to compute the UTC value. If the event falls after this change (say, November 2014), then the event time will change erroneously as soon as you apply the update.

If you want to do this all in JavaScript, there are several libraries for working with time zones in JavaScript. However, I recommend this primarily when the back-end is in JavaScript, such as with Node.js applications.

This type of problem usually is easier with server-side code. You should probably investigate what options there are for working with time zones in the language you are using in your back-end code.

For further reading, I've written about this several times:

Regarding your edits - I can offer the following advice about the code you've presented:

  • Never try to map a time zone abbreviation to a particular time zone. As you can see in this list on Wikipedia, there are just too many ambiguities. Even in your own list of 4 time zones there are some problems. Specifically, EDT is only used for part of the year in America/New_York - the other part of the year is EST. And while you have CST for Asia/Shanghai, it could also apply for America/Chicago, and several other places. (CST has 5 different meanings.)

  • Instead of a dropdown list of time zone abbreviations, there are a few other options:

    • If you only need to handle a few time zones, you can provide a single drop down. Just use the time zone id in the value, and the full name of the time zone in text. For example:

      <select name="tz">
          <option value="America/New_York">Eastern Time (North America)</option>
          <option value="Europe/Berlin">Central European Time</option>
          <option value="Asia/Seoul">Korean Standard Time</option>
          <option value="Asia/Shanghai">China Standard Time</option>
      </select>
      
    • If you want to list all time zones of the world, you may find it too long to put in a single dropdown list. In that case, provide two drop down lists. The first would select a country, and then the second would select the time zone within the selected country. Since many countries only have a single time zone, some users will not have to make a selection from the second list at all.

    • If you want a more interactive approach, consider a map-based time zone picker control, such as this one, or this one.

  • In your GetDateAndTimeFromPostData function, you construct a Date object. You need to keep in mind that the Date object is always UTC internally, but takes on the behavior of the local time zone for most input and output. By local, I mean local to the computer where the code is running. In a server-side function, that's going to be the time zone of your server - which is not appropriate in this case. There's no need to use a Date object, since you're already using moment.js.

  • Make sure you understand that there is a difference between calling .tz(zone) on an existing moment object, vs calling moment.tz(value, zone). The prior adjusts a moment in time to a particular time zone, and the latter creates a new moment in time that's already represented in a particular time zone.

  • Regarding your side note, you are defeating the purpose of moment and moment-timezone:

    • Since x represents the current date and time, then moment.tz(x, "Asia/Seoul") is just the same as moment().tz("Asia/Seoul")

    • y.utc() is converting the value back to UTC, so there was no point in calling .tz(...) to begin with

    • .toDate() puts everything back into a Date object, which represents UTC internally, but always shows its output using the local time zone.

    • So in the end, you just got the current date and time as a Date object, so the whole thing would reduce to new Date().

  • With regards to MongoDB, its ISODate type is just storing UTC values anyway. So for the part of your code that needs to convert to UTC, consider something like:

    moment.tz([2014,0,1,10,0],'Asia/Seoul').toISOString()
    

    Or perhaps you can just pass the equivalent Date object directly - depending on how you're using the Mongo client:

    moment.tz([2014,0,1,10,0],'Asia/Seoul').toDate()
    
  • Do consider though what I said in my initial response. The UTC value will help you know the instant in time that the even should run - but you should not lose track of the original input values! If eventAttributes.startDate is already a Date object, then it's probably already been converted to UTC and you've thus lost the original input value. You should make sure to pass the value from client to server using an ISO string that represents what the user provided. For instance, pass 2014-12-25T12:34:00. Don't try to pass an offset here, or convert to UTC, or pass an integer number. Unless you pass exactly what the user provided, you have no way to store that value.

    When you store the original input value in Mongo - store it as a string. If you store it as a Date, then it too will get adjusted to UTC by Mongo.

Community
  • 1
  • 1
Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • thank you for your time; i am doing this with JS server side (meteor), and am using moment.js and moment timezone. at this moment i am still a bit confused about how to exactly implement this; i will update the post with what code i got so far. – Amc_rtty Sep 08 '14 at 21:21
  • added all my code in the initial post - can you please take a look? thank you – Amc_rtty Sep 08 '14 at 21:53
  • I've updated my answer to address your code. However, please consider asking more specific individual questions (as new questions) going forward. Long posts like these are likely useful only to you, and StackOverflow is meant to build material that is useful to others as well. Also, if you spend some time searching and browsing the various tags, you'll see that many of these point are already addressed in other posts. Thanks. – Matt Johnson-Pint Sep 09 '14 at 17:00
  • Thank you very much! I will read all you wrote when i get home. Regarding your last note, to store in the DB as a string instead of Date - will that not cause problems when querying by date afterwards? "give me between date X and Y" - if the date is stored as a string, that will take a performance hit, no? – Amc_rtty Sep 09 '14 at 17:17
  • 1
    Store your UTC values as Mongo recommends, so you can query it. Store the *original* input value as a string. You won't need to query it. You'll use it if/when you need to recompute the UTC value, such as if you update your moment-timezone data to reflect new tz changes. – Matt Johnson-Pint Sep 09 '14 at 17:28
2

In JavaScript, you can either work with UTC, or with the local time on the machine (but without knowing its timezone; you can at most determine the offset to UTC).

Many people have failed trying to convert timezones in JS, but all these approaches are doomed to fail, because of DST, leap years/seconds, etc. There are so many pitfalls which make an implementation extremely complex.

Time conversion operations are best done on a backend.

You say that you store the date in a DB, so I assume you're transfering it to some backend anyway.

Let's assume your backend is some PHP application, and you would collect the pure date as simple string (e.g. 2014-09-08 15:12:00) from a datepicker, as well as the timezone.

In PHP, you would now do the following:

$timestamp = strtotime($_POST['date']);
$dateLocal = new \DateTime("@$timestamp", new \DateTimezone($_POST['timezone']));
$dateUtc = $dateLocal->setTimezone(new \DateTimezone('UTC'));

Now, $dateUtc contains the date as rock-solid DateTime object in UTC, which you can now further process.

By the way if you would want to show the converted timezone to the user before doing anything else, I would implement this as some sort of AJAX service. As mentioned before, trying to convert timezones in JavaScript itself is doomed to fail.

lxg
  • 12,375
  • 12
  • 51
  • 73
  • 1
    It's not necessarily doomed to fail. It's true of course that for any timezone that is neither to local one nor UTC you should use a date library, not the native `Date` object. – Bergi Sep 08 '14 at 15:24
  • 1
    @Bergi: I'd love to know of a JS library which has a near-flawless implementation of time conversion. At this time, I wouldn't trust any of those I've seen in the past. – lxg Sep 08 '14 at 15:26
  • https://github.com/mde/timezone-js seems pretty mature. I admit that I haven't used any, though. – Bergi Sep 08 '14 at 15:36
  • Thanks, that does indeed look interesting. – lxg Sep 08 '14 at 15:39
  • There are 5 such libraries, [as listed here](http://stackoverflow.com/a/15171030/634824). They have different levels of functionality. – Matt Johnson-Pint Sep 08 '14 at 16:26
  • Ok, I stand corrected. :) Then maybe one of you guys might want to give a JS-based answer to the OP's question. I might add, in my defense, that all of them add a huge download overhead, and should only be used if the time conversion stuff is a central feature of the application in question. – lxg Sep 08 '14 at 16:45
  • Thank you all for your contribution; i forgot to mention I use javascript server side (meteor). I am actually using moment timezone but got stumped on how to properly do these conversions - i know how to convert as the docs are very detailed, but the end result always is in wrong timezone, hence the question. – Amc_rtty Sep 08 '14 at 21:20
  • just added the code in initial post - could you please have a look? thanks – Amc_rtty Sep 08 '14 at 21:52