gpbl / react-day-picker

DayPicker is a customizable date picker component for React. Add date pickers, calendars, and date inputs to your web applications.
https://daypicker.dev
MIT License
6.05k stars 728 forks source link

UTC mode #130

Closed jonathanong closed 5 years ago

jonathanong commented 8 years ago

i store my dates in GMT. my dates do not care about timestamps. i noticed that when using this library, dates are always shown based on the current timezone. this is annoying because from PST, when i select a date, the day before is actually selected in the calendar.

is there a way we can set the mode to GMT? my current work around is to create new date objects based on the current locale. works, but i'm not a fan of it.

thanks

gpbl commented 8 years ago

Hi @jonathanong!

Could you provide an example of this behavior and what do you expect instead? Thanks!

johnrhampton commented 8 years ago

Hi @gpbl.. I think you may have tagged me on accident.. I do love this library and would be more than happy to contribute. great stuff!

gpbl commented 8 years ago

thanks @johnrhampton, I'm so sorry I meant to answer to @jonathanong :smile: i clicked on the wrong user!

johnrhampton commented 8 years ago

no worries @gpbl!

janmyler commented 8 years ago

Hi @gpbl, I think I know what @jonathanong means, an example using moment:

  1. you have a date-time value stored as UTC/GMT string: "2016-02-22T00:00Z",
  2. you are in New York (GMT-5 offset).
const date = '2016-02-22T00:00Z';
let m = moment(date);
let mu = moment.utc(date);

m.format('YYYY-MM-DD HH:mm');
// output: "2016-02-21 19:00"
mu.format('YYYY-MM-DD HH:mm');
// output: "2016-02-22 00:00"

When you create an instance of Date object using UTC/GMT format, the value is shifted to the local timezone to represent a correct local time, which causes problems when you compare dates in modifiers (e.g. isSameDay for selected day).

I hope my explanation is understandable at least a little bit. :smile:

jonathanong commented 8 years ago

oh sorry. i forgot to follow up. yes, what @janmyler says :) i always handle all my dates with moment.utc().

another option is just to pass a date format like YYYY-MM-DD to date picker instead of a Date object so that it never picked up the locale and because the time isn't relevant.

gpbl commented 8 years ago

Thanks for the feedback! Sorry for the delay, as I'm currently on vacation.

So I take it, the issue is related to the DateUtils's isSameDay function - right? It would help to have some code to test this behavior, as I don't understand the role of moment.js here. Or just wait some more days, when back I'll try to reproduce the issue by myself :)

gpbl commented 8 years ago

So finally I could take my time and investigate more the issue.

I like the @jonathanong suggestion to add an option for using only UTC (aka GMT) dates. I think about a new boolean prop like useUTC. Setting this prop to true would make the calendar use native UTC function like getUTCFullYear or getUTCDate.

gpbl commented 8 years ago

Now that tests have been cleaned up and upgraded to enzyme, it would definitely help to have a failing test about this issue. Someone could help reproducing the problem?

code-by commented 7 years ago

How to get date without timestamp and w/o timezone? Only date (00H 00M). Thank you

gpbl commented 7 years ago

@code-by that is not that easy to implement since date calculations in javascript does consider timezones.

robwise commented 7 years ago

@gpbl I'm currently having issues with this because my CI and local environments are in different time zones, so my Jest snapshots keep failing when they are covering react-day-picker. Have you encountered that?

gpbl commented 7 years ago

@robwise that's weird, could you share some of your code?

robwise commented 7 years ago

I use enzyme's shallow renderer alongside the enzyme-to-json serializer. Then the test code is just:

import { shallow } from 'enzyme';
import DayPicker from 'react-day-picker';

it('works', () => {
  const wrapper = shallow(<div><DayPicker /></div>);

  expect(wrapper).toMatchSnapshot();
});

This will fail because JS Date objects show up in the snapshot. On my computer, these Date objects have a timezone of GMT-5, but on the CI server they are GMT+0.

gpbl commented 7 years ago

@robwise I remember now I've met this issue in my CI before. I usually set a fixed date to workaround the problem, e.g.<DayPicker initialMonth={new Date(2017, 5, 1)} />.

However I'd like to fix this issue once for all :) This may be solved using Date.UTC() within the component when specifying a new prop:

<DatePicker utc={true} />

We need to have a good test suite before. This means basically:

robwise commented 7 years ago

@gpbl I found a workaround that may be sufficient, just pass this env var when starting jest, like this:

NODE_ENV=test TZ='UTC' jest

This way when you run Jest it's always set to UTC timezone.

gpbl commented 7 years ago

@robwise awesome, thanks for sharing! This is a Operating System feature, right?

robwise commented 7 years ago

I was under the impression it was a Node feature, but maybe I'm mistaken?

DmitryOlkhovoi commented 7 years ago

So did you fix it?

rluncasu commented 6 years ago

I know it's kindof a dumbass solution but I ended up feeding dates into date picker by first passing them through this function:

/**
 * Strips the timezone from a date and returns a new, local, date object
 * @param {String} Expects a date formatted like "2018-03-31T00:00:00.000Z"
 */
export const getLocallyFormattedDate = d => (d) ? moment(d.split("T")[0]).toDate() : new Date();
robwise commented 6 years ago

I've been using the TZ=UTC env var solution for 6 months now and it works very well.

p-j commented 5 years ago

The problem is more with how inconsistent new Date() // or Date.parse() is across browsers and even within its API. This below runs in latest Chrome as of this writing:

// Executed with an env running TZ=UTC-8
new Date('2019-01-01')
// Mon Dec 31 2018 16:00:00 GMT-0800 (Pacific Standard Time)

// While Executed in TZ=UTC
new Date('2019-01-01')
// Tue Jan 01 2019 00:00:00 GMT+0000 (Greenwich Mean Time)

This is due to new Date() treating date only input as UTC and effectively parsing 2019-01-01T00:00:00Z and then converting it to local time.

An easy workaround if you don't care about the time is to instead use

const dateParts = '2019-01-01'.split('-')
// Executed with an env running TZ=UTC-8
new Date(dateParts[0], dateParts[1] - 1, dateParts[2], 0, 0, 0) // Note the dateParts[1] - 1, that's because month are 0 based.
// Tue Jan 01 2019 00:00:00 GMT-0800 (Pacific Standard Time)

// And Executed in TZ=UTC
new Date(dateParts[0], dateParts[1] - 1, dateParts[2], 0, 0, 0)
// Tue Jan 01 2019 00:00:00 GMT+0000 (Greenwich Mean Time)

This effectively forces the time that is now parsed in the current timezone and therefore the date is the expected one.

As a side note, Firefox seems to be handling the Date only input "correctly" as in: assuming it is in local time, the same way it works for all other input methods.

Hope this helps.

raRaRa commented 5 years ago

This has been really confusing. In my use case I want everyone to pick a date in UTC. Is this possible with this library? When I select a date such as 2019-02-03 in UTC, and I change the time zone to something way off on my PC, and I open the date 2019-02-03 which was previously selected in the date picker, it shows 02-02 as selected. Does selectedDays convert the Date to local time? Thanks.

eliyahen commented 5 years ago

For day picker input, it is possible to convert input value to date with local time as UTC time, and onDayChange convert the date back to normal.

Input: 01-01-2010 12:00:00 GMT+0100 (UTC=01-01-2010T11:00:00Z) -> 01-01-2010 11:00:00 GMT+0100 (UTC=01-01-2010T10:00:00Z).

Input: 01-01-2010 12:00:00 GMT+0100 (UTC=01-01-2010T11:00:00Z) -> 01-01-2010 13:00:00 GMT+0100 (UTC=01-01-2010T12:00:00Z).

I used this helper function:

// make date with local time to be as given date UTC time (and reverse - date with UTC time to be as given date local time).
function dateLocalAsUTC(date, reverse = false) {
    retDate.setMinutes(retDate.getMinutes() + (retDate.getTimezoneOffset() * (reverse ? -1 : 1)));
    return retDate;
};
gpbl commented 5 years ago

I'm closing this, since in the upcoming release react-day-picker will be based on date-fns, and hopefully we won't have this issue anymore.

KamaniBhavin commented 1 month ago

@gpbl The issue continues to occur even on v9, which uses date-fns. I'm in the UTC +5:30 timezone, and when I select a date, for example, 19/12/2024, it shifts to 18/12/2024. This happens because I handle all my dates in UTC, but the date picker operates in the local timezone, which is ahead of UTC, leading to the mismatch. Is there any solution to this, or would I need to patch it?

gpbl commented 1 month ago

@gpbl The issue continues to occur even on v9, which uses date-fns. I'm in the UTC +5:30 timezone, and when I select a date, for example, 19/12/2024, it shifts to 18/12/2024. This happens because I handle all my dates in UTC, but the date picker operates in the local timezone, which is ahead of UTC, leading to the mismatch. Is there any solution to this, or would I need to patch it?

Hi @KamaniBhavin! Could you provide some code to replicate your issue?