While they haven’t gained much traction on the desktop, HTML5’s new input types are great when you’re developing for mobile. Whether you use them in a mobile-friendly website or a hybrid app built with Cordova, most mobile devices will complement those regular HTML inputs with task-appropriate keyboard layouts or even show native interfaces in some cases.

Though these new inputs are pretty handy, they do still have a few rough edges. In fact, I ran into a very frustrating issue while trying set the value of a datetime-local input just today.

In this post, I’ll show you the correct syntax to set the value of a datetime-local input, and how to set it to the current date and time, while correctly accounting for the user’s time zone.

Why datetime-local instead of just datetime?

There’s been some confusing instability around datetime inputs as HTML5 has coalesced into a recommendation.

  • Early on, the W3c HTML5 spec simply proposed a datetime input. Starting with iOS 5, Mobile Safari began displaying a UI for that input that matched the one you’d find in native apps. Other mobile browsers quickly followed suit.
  • Later, datetime was removed from the HTML5 draft spec in favor of combining individual date and time inputs to prompt users for a date/time value with two separate inputs. While reducing the number of new input types makes some sense, it ignores the utility of showing the combined date/time widget that’s already present and familiar on mobile devices.
  • Next, the datetime-local input was added to the spec to replace the void that removing datetime left. The new input would be similar to the old one, but lacking the notion of a time zone offset.
  • In late 2014, the W3C decided to drop datetime-local from the recommendation HTML5 spec because it could lead to confusion. However, the dominant mobile browsers (i.e. Mobile Safari and Chrome) had already implemented datetime-local and made it a de facto standard. So, the w3C relented and added it back to the latest recommendation draft.
  • Adding to the confusion a bit more, Apple still lists both datetime and datetime-local as supported input types for iOS 5.0 and later. That’s not exactly the case though. As of iOS 7, Mobile Safari started showing its native interface for datetime-local inputs and dropped support for datetime inputs at the same time. As with any input type that a browser doesn’t have enhanced support for, datetime inputs started falling back to plain text type fields on iOS 7.

Long story short, you must use datetime-local for iOS 7+ (at least through the current version of 8.1.3 as I write this) and it’s helpful to understand that the “local” means the input is time zone agnostic.

Setting the value of a datetime-local input

If you search online, you’ll find suggestions that all you need to do is set the input’s value to an ISO date string. JavaScript’s Date object even has a handy method that seems like exactly what you’d want in this situation.

Unfortunately, all that does in Chrome and iOS is clear the input without throwing any error or indication of what went wrong. Why? The time zone is key.

Marco? Zulu?

The ISO date string that toISOString() returns looks something like this:

2015-02-20T03:35:06.872Z

That’s exactly what toISOString() returns at 10:35pm, here in the UTC-05:00 Eastern Time zone, on February 19th, 2015. The Z at the end of the ISO string denotes Zulu time.

The lack of a time zone offset is going to be an issue for us later, but that Z at the end is our first problem. Since the datetime-local input specifically does not support any time zone offset information, even UTC+00:00 Zulu time, the string above is an invalid one for use with a datetime-local input.

As of this post, both iOS and Chrome silently clear datetime-local inputs when you try to assign an otherwise valid ISO date string that contains time zone information. Am I alone in thinking they really should throw an error or warning of some kind in that scenario?

We were just one character away…

If you have a Date object with its time component already adjusted for time zone offset, the solution is as simple as calling toISOString() and chopping that Z off of the end. For example, it’s 10:52pm now, so here’s JavaScript to take a standard ISO date string and use it to set the value of a datetime-local input:

It seems a little bit clunky to replace the Z away like that. You can also use substr and the string’s length to trim the last character:

I personally prefer the readability of explicitly removing the Z. There are significant performance differences between the two approaches though, which are unfortunately not consistent across different browsers.

Luckily, you should never need to set an input’s date frequently enough that sub-millisecond performance variations of a single operation will matter. Choose your own adventure.

So, what about time zones?

You may have noticed that I manually adjusted the ISO date string to account for my -5 hour time zone offset in that last example. Since datetime-local inputs don’t accept timezone information, you need to make any time zone adjustment before setting the an input’s value.

An especially common example of that is when you want to default the input to a value based on the current date and time. You might decide to try something straightforward like this:

That will actually work great… just so long as your users are all located in a zero-offset time zone. Everyone else will see a value that lacks their local timezone offset. For example, it would always display a time five hours in the future for me here in the Eastern time zone.

JavaScript’s Date object does offer a getTimezoneOffset() API for determining time zone offsets. Using that, combined with getTime(), this is the best solution I’ve come up with that doesn’t require a third-party date library:

Not pretty, but it works.

Beware of solutions that use methods like getHours or getMinutes to make the math seem easier. getTime returns the number of milliseconds since the epoch, suitable for use in the Date constructor. Those other methods simply return the second, minute, or hour portions of the current time (e.g. getMinutes at 9:07pm just returns 7).

I’m curious if anyone has a cleaner solution that doesn’t require a third-party library. It seems like there ought to be a better way to get this done.