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,
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,
Share Improve this question edited Sep 8, 2014 at 21:51 Amc_rtty asked Sep 8, 2014 at 15:03 Amc_rttyAmc_rtty 3,81311 gold badges50 silver badges76 bronze badges 4- That depends largely on what format you want to store in your database … – C3roe Commented Sep 8, 2014 at 15:15
- Just use a datepicker that doesn't display the local timezone, but is configurable to any, and then yields an UTC timestamp. – Bergi Commented Sep 8, 2014 at 15:18
- @Amc_rtty What solution did you end up with here? – geoboy Commented May 23, 2017 at 1:27
- @geoboy went with the solution from Matt Johnson, see below – Amc_rtty Commented May 24, 2017 at 6:46
2 Answers
Reset to default 6Neither 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 ing 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 pute 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 remend 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:
- in PHP (recently)
- in PHP (more detailed)
- in PHP (yet again)
- in Java
- in Java (again)
- in .NET
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 forAsia/Shanghai
, it could also apply forAmerica/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 aDate
object. You need to keep in mind that theDate
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 puter 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 aDate
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 callingmoment.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, thenmoment.tz(x, "Asia/Seoul")
is just the same asmoment().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 aDate
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 tonew 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 aDate
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, pass2014-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.
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 plex.
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.