React Native and the jumping datepicker

If you’re working with React Native you might have seen this:

Why is that? The React Native datepicker accepts a JS Date object as input which is maybe not the best idea. A JS Date marks a specific point in time. What a datepicker displays is a date without time and timezone.

What happens if you pass a Date object to the picker? React Native uses Calendar.getInstance() so it uses the local timezone of your device. When you construct a JS Date with less then two parameters JavaScript uses UTC. new Date('1978-08-07').toISOString() results in 1978-08-07T00:00:00.000Z with the timestamp 271296000000. In a timezone west of UTC it was August 6 at this point in time.

How can we fix that? We could add the timezone offset manually:

const date = new Date('1978-08-07');
const offset = -1 * date.getTimezoneOffset() * 60 * 1000;
const localDate = new Date(date.getTime() - offset);
// we could also use the more params constructor which uses the local timezone
new Date(1978, 7, 7);

This works quite well. Most of the time. For given date it’s still broken. Why? I’m currently in timezone “Europe/Berlin” and new Date('1978-08-07').getTimezoneOffset() returns -120. This is not correct. The same evaluated in the Chrome developer console on my MacBook outputs -60 which is correct. Berlin is GMT+01:00 (The JS result is inverted because it’s the difference, in minutes, from local time to UTC). Seems like the React Native JS Engine knows that I’m in Berlin, knows that in August we have DST and a offset of 2 hours but does not know that DST was introduced 1980 in Germany. The Android datepicker knows more and so you can find a lot of dates where these two are inconsistent. To make it even worse: If your simulator is attached to a debugger you can’t reproduce this as the JavaScript is evaluated via a browser engine on your host system and not the JS engine used by the device.

The easiest way to fix it is to rely on the timezone knowledge of moment.js:

moment.tz('1978-08-07', 'Europe/Berlin').toDate()
moment.tz('1978-08-07', 'Europe/Berlin').utcOffset() // 60

You can determine the local timezone via moment.tz.guess() but react-native-device-info may be more reliable.