Making time related inputs work with local timezones - JavaScript
The problem
HTML supports multiple time-related input types like: time
, date
, week
, month
, and datetime-local
. All this is great, however, there can be one small problem: we need a way to set the value of the input from a JavaScript Date object, as well as a way to get the value from the input into a date object.
Unless you're on GMT you should see that the hour in the input is different than in the p element
valueAsDate and valueAsNumber
The time-related inputs have a property named valueAsDate
or valueAsNumber
(some have both and some only one), which is a JavaScript Date object or a epoch timestamp number, great no? Well, no, not really, let me explain, the time-related inputs are always on UTC, that is, if the user enters say 9:00 AM in the input, valueAsDate
will return 9:00 AM on UTC not local time, which unless you're on UTC will result in the time being a number of hours off.
Another problem that valueAsDate
has, is that since some browsers don't support (at the time of this writing) some of the time-related inputs, valueAsDate
is therefore also not supported which means that this won't work at all in those browsers instead of gracefully degrading.
Solution for timezone problem
A possible solution for the timezone problem with valueAsDate
and valueAsNumber
is to move the time with the offset of the current timezone from UTC, so that its actually in the wrong time (because JS Dates are always on UTC, not on local time), but will work to get the input to display the correct time (as mentioned that the input is always on UTC). And then, when we get the date out we can move the time back with the timezone offset.
When using this technique we have to be careful not to change the source date but to always first make a copy of the date and work on the copy.
JavaScript to HTML
let htmlDate = new Date(jsDate); // make a copy of the source date
htmlDate.setMinutes(htmlDate.getMinutes() + htmlDate.getTimezoneOffset());
input.valueAsDate = htmlDate; // Setting by date
// Or:
input.valueAsNumber = htmlDate.getTime(); // Setting by number
HTML to JavaScript
// make a copy of the source date
let jsDate = new Date(input.valueAsDate); // Getting by date
// Or:
let jsDate = new Date(input.valueAsNumber); // Getting by number
jsDate.setMinutes(jsDate.getMinutes() - jsDate.getTimezoneOffset());
Where to use valueAsDate
or valueAsNumber
.
Input type | valueAsDate |
valueAsNumber |
---|---|---|
datetime-local |
No 1 | Yes |
time |
Yes | Yes |
date |
Yes | Yes |
week |
Yes | Yes |
month |
Yes | No 2 |
1 datetime-local
doesn't have a valueAsDate
property.
2 The valueAsNumber
property of the month
input is not an epoch but the numbers of months since 1970.
However this only solves the timezone problem, not the browser compatibility problem
The solution
Since valueAsDate
and valueAsNumber
won't work in unsupported browsers we can't really use them, rather we'll need a way that can gracefully fallback when the input type isn't supported.
To get the value from a JavaScript Date object to the HTML input we can use the Date.toLocalString
method that can get us the proper format for most of these inputs. (I found that the sv-SE
locale is quite close to the HTML format.)
To get the value from HTML to a JavaScript Date can be a bit more tricky, JavaScript Date objects require both date and time down to the millisecond, there is no such a thing as a date-only or time-only JavaScript object, while datetime-local
has both date and time information, none of the others have all the information to create a proper date just from the input, which means that if we want to get the information from a time-related input to a JavaScript Date we will need to provide the missing information another way.
type=datetime-local
Expected value
The expected value for the datetime-local
input is yyyy-mm-ddThh:mm:ss
.
JavaScript to HTML
We can use sv-SE
but we'll have to replace the space between the date and time with a T
to make it compatible with HTML.
If you have an input without seconds remove
second: "2-digit"
.
input.value = date.toLocaleString("sv-SE", {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
}).replace(" ", "T");
HTML to JavaScript
Getting the value from a datetime-local
input to a Date object is simple, just pass in input.value to a Date constructor, it will default to the local timezone.
new Date(input.value);
type=time
Expected value
The expected value for the time
input is hh:mm:ss
.
JavaScript to HTML
If you have an input without seconds remove
second: "2-digit"
.
input.value = date.toLocaleString("sv-SE", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
HTML to JavaScript
To get a Date object from the time the user entered we will need a day in the following format yyyy-mm-dd
to attach to the time.
Tip to get the day from a Date object you can use the same
.toLocaleString
options astype=date
uses to get from JavaScript to HTML.
const day = "2020-01-01";
new Date(day + "T" + input.value);
type=date
Expected value
The expected value for the date
input is yyyy-mm-dd
.
JavaScript to HTML
input.value = date.toLocaleString("sv-SE", {
year: "numeric",
month: "2-digit",
day: "2-digit"
});
HTML to JavaScript
The JavaScript Date object constructor has a weird rule, if it gets a date without a time it defaults to UTC instead of the local timezone.
To get around this we need to enter the time manually, I use 00:00 (12:00 AM) as here but you can change it to whatever time in the day you want.
new Date(input.value + "T00:00");
type=month
Expected value
The expected value for the month
input is yyyy-mm
.
JavaScript to HTML
input.value = date.toLocaleString("sv-SE", {
year: "numeric",
month: "2-digit"
});
HTML to JavaScript
As mentioned before (type=date) we'll need to add the missing parts (day in month, and time) manually.
new Date(input.value + "-01T:00:00");
type=week
Expected value
The expected value for the week
input is yyyy-Www
.
This one is not easy, .toLocaleString
doesn't support getting weeks and I'm not aware of any way to create a Date object from week data, so the only way is to use the valueAsDate
or valueAsNumber
with the technique described above, resulting in limited browser support.
So if you do have any ideas that you think might make this cross platform please let me know.
Top comments (0)