When a UTC date object is passed to react-calendar (e.g. Date('2021-04-20') or Date(1618876800000)), it assumes the date is local time. This results in incorrect date values, as react-calendar calls Date methods internally that are only intended for local time (setHours() vs setUTCHours()).
I understand there have been a number of prior issues opened around timezone support (#174, #492, etc.), but I believe that this date mutation may be the actual cause of many of these issues. It is not well-known that the result of the Date constructor may be in local or UTC depending the parameters passed. At a minimum, updating the documentation to reflect that the Date object passed to value must be in local time may help alleviate some confusion. Ideally, detecting (or providing a flag) for UTC dates would ensure that users get the output they expect, even if they have to perform their own timezone conversion in post.
At a minimum, updating the documentation to reflect that the Date object passed to value must be in local time may help alleviate some confusion.
Absolutely agree.
@wojtekmaj What would be the correct approach to deal with the location-specific timezone in the Calendar component or to default to using UTC time?
I have a use case where the locations I display the Calendar for can vary, and so will their time zones.
I want to let users across the world see the exact same date in the location-specific timezone, as there are times associated with dates that also need to be displayed in the location-specific timezone. I thought to use UTC for that under the hood and just convert the times to location-specific timezone, but it does seem like it's being converted to local time regardless of me manually formatting the date to UTC.
Note: I've gone through the mentioned issues & documentation a couple of times, but haven't found the answer so far.
For example, the following code always treats the date as local date. I would instead want to treat it as UTC or date in a specific timezone. Is that possible? This is somewhat obfuscated code to keep only relevant parts.
const [selectedDate, setSelectedDate] = useState(new Date()); // even if this is initialized as UTC date, it treats it as a local one.
const handleDateChange = useCallback((date) => setSelectedDate(date), [setSelectedDate])
const tileDisabled = useCallback(({ date }) => !isAvailableDate({ date, data }), [data]);
// the `date` in both functions here is in local time, but i need it to be UTC or timezone specific.
<Calendar
onChange={handleDateChange}
value={selectedDate}
tileDisabled={tileDisabled}
/>
@aamorozov Dates in JavaScript in general are "local" and "timezone specific". There's no concept of "UTC" date, or dates in any other timezones than the one you're in right now.
Consequently, React-Calendar, you'll always select century/decade/year/month/day "start time", which is local midnight.
There are a number of approaches you can take to get "non-local" midnight, like midnight in UTC, or midnight in other timezone.
You can create a new Date object that will have UTC midnight using getTimezoneOffset():
function toUTC(d) {
return new Date(d.getTime() - d.getTimezoneOffset() * 60 * 1000);
}
This way, given date with midnight local time, you will get a date that's 2 AM local time if your local timezone is UTC+2, which is midnight in UTC. So when using methods that apply to UTC, like toISOString(), you will get the expected result, e.g. "2020-05-01T00:00:00.000Z".
Alternative way would be to store the Date as is, sparing you all the conversion e.g. when keeping date in local state, and built a function that stringifies the date to ISO-like string based on local date:
function toISOLikeString(d) {
return `${
d.getFullYear()
}-${
`${d.getMonth() + 1}`.padStart(2, '0')
}-${
`${d.getDate()}`.padStart(2, '0')
}T${
`${d.getHours()}`.padStart(2, '0')
}:${
`${d.getMinutes()}`.padStart(2, '0')
}:${
`${d.getSeconds()}`.padStart(2, '0')
}Z`;
}
Conversion the other way around, from UTC to local time, is even simpler than that. This is because"2020-05-01T00:00:00.000Z" is UTC midnight, but without the Z, it's local midnight. So:
function UTCStringToLocal(utcString) {
if (!utcString.endsWith('Z') {
throw new Error('utcString is not a valid UTC string');
}
return new Date(utcString.slice(0, -1));
}
will do the job. This is likely what you need if you save the date as UTC midnight, and need to show the exact same local date to everyone.
Lastly, there are 3rd party utilities like date-fns-tz that let you do complex timezone operations if you need more than simply converting to UTC or back.
@racoleman Date('2021-04-20') is not the correct way to initialize JavaScript date and will return a string with current time (kinda WTF, I know). new Date('2021-04-20') will, however, be interpreted as Apr 20 2021 UTC midnight just fine. With Date(1618876800000) it's exact same story.
@aamorozov Dates in JavaScript in general are "local" and "timezone specific". There's no concept of "UTC" date, or dates in any other timezones than the one you're in right now.
Are you referring to the prototype methods of Date? Most of them operate in the local timezone, but the core value of the object itself is still the number of milliseconds since 1/1/1970 UTC.
@racoleman
Date('2021-04-20')is not the correct way to initialize JavaScript date and will return a string with current time (kinda WTF, I know).new Date('2021-04-20')will, however, be interpreted as Apr 20 2021 UTC midnight just fine. WithDate(1618876800000)it's exact same story.
Yep, accidentally left that out of my opening post 馃槄
Most helpful comment
@aamorozov Dates in JavaScript in general are "local" and "timezone specific". There's no concept of "UTC" date, or dates in any other timezones than the one you're in right now.
Consequently, React-Calendar, you'll always select century/decade/year/month/day "start time", which is local midnight.
There are a number of approaches you can take to get "non-local" midnight, like midnight in UTC, or midnight in other timezone.
You can create a new Date object that will have UTC midnight using
getTimezoneOffset():This way, given date with midnight local time, you will get a date that's 2 AM local time if your local timezone is UTC+2, which is midnight in UTC. So when using methods that apply to UTC, like
toISOString(), you will get the expected result, e.g."2020-05-01T00:00:00.000Z".Alternative way would be to store the Date as is, sparing you all the conversion e.g. when keeping date in local state, and built a function that stringifies the date to ISO-like string based on local date:
Conversion the other way around, from UTC to local time, is even simpler than that. This is because
"2020-05-01T00:00:00.000Z"is UTC midnight, but without theZ, it's local midnight. So:will do the job. This is likely what you need if you save the date as UTC midnight, and need to show the exact same local date to everyone.
Lastly, there are 3rd party utilities like date-fns-tz that let you do complex timezone operations if you need more than simply converting to UTC or back.