5 min read
Your client has a form in their web application where they want to specify a datewhich will get submitted to an API and stored along with the rest of the form information. The web application is used all around the globe by users in different time zones. The client requires that when the date is stored, it displays the same for all users. So if a user sets a date of March 3rd while they are in the Eastern time zone, then when another user views the site anywhere from the Pacific to the other side of the planet, then they should see March 3rd also.
new Date(theDate), display it to the user in the browser, and discover that the date is showing a different day then what the API gave you. The browser shows that the date is for the previous day. 😕
Why is this happening?
When an API provides you with a date, it is commonly (and should be) in a universal standard ISO 8601 string format like
new Date('2020-03-03'), then your browser shows it as
Mar 2, 2020 19:00:00 (i.e. 7PM the previous day).
new Date('2020-03-03'), then it will automatically assume that the time is 00:00:00. To make matters a little more tricky, it will assume that the entire date/time has a time zone offset of 0 because an offset wasn't specified in the string when you created the object. So the Date object will represent a full date/time of 2020-03-03 00:00:00 GMT time.
When your browser displays this object, it will apply the machine's local timezone offset to it. If you are in the Eastern time zone (UTC-5), then 5 hours will be subtracted from the date whenever you present it in a human readable format via methods like toString() or toLocalDateString(). So 2020-03-03 00:00:00 GMT becomes 2020-03-02 19:00:00 EST - notice the date part is now in the previous day.
var theDateObject = new Date(theDateStringFromApi); theDateObject.setMinutes(theDateObject.getMinutes() + theDateObject.getTimezoneOffset());
What the above code will do is adjust the time according to your local offset. Then when you execute toString(), the date will match exactly what you received from the API and subsequently used to initialize your date object.
var d = new Date('2020-03-03'); d.toDateString(); // 'Mon Mar 02 2020' - notice date doesn't match what was passed to constructor d.setMinutes(d.getMinutes() + d.getTimezoneOffset()); d.toDateString(); // 'Tue Mar 03 2020' - now date matches what was passed to constructor
What about posting dates to APIs?
Posting isn't as much of an issue when you are only posting the date portion to an API. You'll typically do something like this:
- Use your favorite date formatting tool to format the date to YYYY-MM-DD format
- Post the formatted date string to the API
Your format tool will typically format based on your local date. It doesn't do any conversion like .toISOString() to convert it to an ISO string beforehand. However, if your API does require a date and time be posted, it likely will expect them to be in ISO format and you may have to make some adjustments before posting.
Apollo Client and GraphQL
Apollo Client and GraphQL are quite common nowadays - unless you're reading this far in the future in which case they may be obsolete by now (hello from the past btw). Depending on how you have your API defined, you may be able to pass your date object directly to the GraphQL API via Apollo Client - or so it would appear.
The reality is that your date object gets converted to an ISO datetime string with toJSON() or toISOString() before sending. This causes the time (and possibly date) to be shifted according to your local offset.
For example, if you are in Eastern time again (UTC-5) on March 3, 2020 and create a new date at 11 PM (23:00) and pass this date object as a variable to your mutation, then behind the scenes it is converted to the string
2020-03-04T04:00:00.000Z and that is what is sent to the API. The API receives this and stores it thinking it is in UTC and has no way of knowing that you sent it from a +9:30 offset. In the end, the date that it persists isn't what you thought would be stored (i.e. it is not the date that you created originally).
To accommodate for this, you need to do like we did above and adjust the time of the date object before you send it, but this time we subtract the minutes according to the offset.
var theDateObject = new Date(); theDateObject.setMinutes(theDateObject.getMinutes() + theDateObject.getTimezoneOffset());
var d = new Date(2020,2,3,23,0,0); // March 3, 2020 11 PM d.toJSON(); // '2020-03-04T04:00:00.000Z' - date is for the next day d.setMinutes(theDateObject.getMinutes() - theDateObject.getTimezoneOffset()); d.toJSON(): // '2020-03-03T23:00:00.000Z' - date now matches
Once you do this, you can supply this date object directly to your mutation variable and when it converts it to ISO/JSON format, then the resulting date/time sent will match exactly with the date/time you used to create the object.