Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Date Selected is One Day Off #1018

Open
YOO629 opened this issue Sep 9, 2017 · 92 comments
Open

Date Selected is One Day Off #1018

YOO629 opened this issue Sep 9, 2017 · 92 comments

Comments

@YOO629
Copy link

YOO629 commented Sep 9, 2017

Hi, I'm having an interesting issue with my usage of the datepicker component.

My code is pretty basic, it looks like this:

<ReactDatePicker
  onChange={(val) => this.handleChange(val)}
  selected={this.state.currentDate}
/>

and my handler fn

  handleChange(momentDate) {
    console.log(momentDate.toISOString())
    this.setState({currentDate: momentDate});
  }

The issue that I see is that when I select a date from the dropdown, the visual aspects of the component appear to be working fine -- I select 09/10 (sept 10th) and that's what I see in the input field.

However, in my console.log() in my handler function, I see that when I select 09/10, the output is 2017-09-09T21:00:00.000Z. momentDate.toISOString() is ultimately what I'm sending to the DB to be saved, so it's problematic that it's one day off.

I'm not sure what to make of this. I was expecting to see 2017-09-10T21:00:00.000Z. It looks like
momentDate is localized, so if I'm in a UTC + 3 timezone and choose 09/10 00:00:00, I'm actually choosing 09/09 21:00:00 (my hypothesis). Even if I set the utcOffset prop to 0 it looks like the localization is still happening.

Am I doing something wrong? Is there a way for me to just kind of...turn off the localization? I'm using the component purely to just pick a day in the calendar. I don't care what timezone they're in, if they pick 09/10, they pick 09/10.

Thanks!

@nwkeeley
Copy link

Also experiencing this issue.

I open it up, select date its off by 1 day. I then open it up and select any other subsequent day and its correct.

@xarxziux
Copy link

I've run into this problem too. This seems to appear in release 0.44.0. From my tests, 0.43.0 behaves correctly and reports the date selected. Release 0.44.0 reports the day prior to the day selected.

From reading through the commits, it's likely that the problem is somewhere in commit a95cd86, but I've not been able to identify the exact cause.

@xarxziux
Copy link

Scrap that last comment. Release 0.43.0 worked fine on a test project, but it's now showing the same error on my main project. The hunt continues...

@xarxziux
Copy link

xarxziux commented Sep 25, 2017

From further tests, I can actually turn this bug on in a test project using release 0.41.0 just by setting the startDate to null in the constructor function. Setting it to moment() turns the bug off again.

Release 0.40.0 doesn't seem to have this problem.

@Hacker0x01 Hacker0x01 deleted a comment from pyaesone17 Sep 29, 2017
@romanveryovkin
Copy link

The same issue for me as well. Do anybody have some workarounds?

@RobMaple
Copy link

I've just run into a similar sounding issue to this although on closer inspection it seems to be relating to when daylight's savings time ends at the end of October. Selecting a date after this point initially causes the date picker to be one day behind, selecting a date a second time seems to fix this. The 'fix' seems to relate to the moment object having no utc offset the second time - why this is though I'm not sure.

To get around this i'm currently setting the 'utcOffset' prop on the datpicker component to 0 -- this prevents the initial selection from shaowing as the day previous. I'm wondering if this is actually a bug with react-datepicker though or more to do with how dates are handled and manipulated within my app.

@aij
Copy link
Contributor

aij commented Oct 17, 2017

@RobMaple Are you able to reproduce it with the examples at https://hacker0x01.github.io/react-datepicker/ ?

DST ends on Nov 5 for me, but selecting days before and after seems to be working. (I tried clearing and not clearing at various points, but it didn't seem to make a difference.)

@RobMaple
Copy link

@aij The example worked fine although when I updated the version of react-datepicker to 0.55 (the same as in my project) the issue returned. Just updated it to 0.56 however and all looks to be working correctly again 😀

@aniruddhashevle
Copy link

This issue is there in v0.58!

@mwickett
Copy link

mwickett commented Dec 7, 2017

We're seeing this issue on 0.61 as well. With some testing, it looks like it only happens in a GMT negative timezone (WEST of GMT). Setting my machine's timezone to a positive value returns a proper date.

Setting the utcOffset prop to 0 doesn't change anything.

@swellmar
Copy link

@mwickett @RobMaple Where do you find this utcOffset prob? I can find it on 100 different places

@tastypackets
Copy link

tastypackets commented Dec 22, 2017

Using v0.39 it corrects after selecting a second date. For example select 12/20/17 and it will be 12/19/17, then select 12/22/17 and it will be 12/22/17.

Initializing it with a date like @xarxziux mentioned does work.

Edit:

I just undated to v0.64 and it is now working properly even with defaulting to null.

@guyzmo
Copy link

guyzmo commented Feb 14, 2018

I'm having that exact same bug, and I just understood what's wrong after chasing my own tail for hours. As I'm only interested in dates, in the date selector, the handled value is a Date object with a time set to midnight.

As the selection of the date is in current local time (I'm in CET time), and the storage is in UTC, local time gets shifted by one hour backward when stored. That makes selection of 2018-02-20 with a default to midnight getting stored as 2018-02-19T23:00:00.000Z.

And because in my code I was doing a Q&D conversion from Datetime to string by splitting over T, I was setting the value back to the wrong date.

Solution, in my case? use momentsjs e-ve-ry-whe-re!

@azza85
Copy link

azza85 commented Mar 12, 2018

I had the same issue,, with the date selected going back a day. I fixed the issue for me by adding

selected={null}

The reason I had it blank was if I had it set as

moment()

it was displaying today rather than my placeholder text

@alexjjseppala
Copy link

alexjjseppala commented Apr 6, 2018

The dates in my app's database were in UTC time with the ISOString format with a "Z" at the end
(ie: 2011-10-05T14:48:00.000Z)
and I was converting them to Moments as such:
Moment("2011-10-05T14:48:00.000Z")
but this was using my local UTC offset, causing them to appear one day off.

Creating my dates like this:
Moment.utc("2011-10-05T14:48:00.000Z")
fixed my issue

ref: https://maggiepint.com/2016/05/14/moment-js-shows-the-wrong-date/

@omerdn1
Copy link

omerdn1 commented May 8, 2018

Still having this issue in 1.4.1, couldn't find a proper solution

@jimmytb
Copy link

jimmytb commented May 16, 2018

Having the same problem

@evolve2k
Copy link

evolve2k commented Jun 7, 2018

Edit: 17 October - Workaround helps a bit but doesn't solve the issue - see #1018 (comment)

Workaround

Explicitly add a utcOffset paramater and set it to zero.

It seem the system uses your local utfOffset by default causing a difference in time.

The 'fix' seems to relate to the moment object having no utc offset the second time but using your local system offset the first time.

Example applying the workaround:

<DatePicker
    utcOffset={0}
    dateFormat="DD-MMM HH:mm"
    todayButton="Today in Puerto Rico"
    onChange={this.handleChange}
/>

https://reactdatepicker.com/#example-12

This worked for me. Thanks to @RobMaple

@Definence
Copy link

I have the same problem!!

@olanMbakop
Copy link

The problem still exists in ^1.6.0

@evolve2k
Copy link

Updated since I listed my 'Workaround' post on 7 June, seems it's working only when the local time is on the same day as UTC. For me in Australia, this means that half the day it works and the other half of the day it's off-by-one. This is when the user has the datepicker control open and selects a date with the mouse the first time (eg when the date is empty).

Once there is a date present the control works correctly for future selections of dates.

@klijakub
Copy link

klijakub commented Dec 7, 2018

I have the same issue

@fathurhidayat3
Copy link

Still exists and mine has 7 hours behind. I realize utcOffset props was removed in #1527 #1581 . I tried to set locale using registerLocale, setDefaultLocale and locale props to "id" but nothing happened.

@jesseseligman
Copy link

Still having this issue...

@jesseseligman
Copy link

Wrapping the string in a moment object and then calling .toDate() seemed to fix this for me.
Something like:
selected={moment("2019-02-07").toDate()}

@madhukosuri
Copy link

Still having this issue...

@cturkdogan
Copy link

I had this issue when I used the dateFormat as "yyyy/MM/dd"
When I changed it as below the issue is resolved;
showTimeSelect timeFormat="HH:mm" dateFormat="yyyy/MM/dd HH:mm"

@tdubrova
Copy link

tdubrova commented Jul 4, 2019

@cturkdogan thank you, it helps :)

@guillecro
Copy link

guillecro commented Jul 8, 2019

I am still having this problem, yeah it has something to do with the UTC offset

@dgreene1
Copy link

dgreene1 commented Jun 15, 2022

Update: @D-Thrane's issue is a locale issue, not a timezone issue. If you need an internationalized date picker, you'll need to account for format differences. I believe that js-joda has a plugin for locales.


Original:

@D-Thrane if you’d like to send me a gist or a PR I can check out what your code is doing but my examples at https://dev.to/dgreene1/making-your-datepicker-easier-to-work-with-21n4 should be handling time zones by the very nature of not including time at all. So if you’re experiencing the problem with JsJoda maybe you’re utilizing Instant or ZonedDate time instead of LocalDay. This is just a suspicion since I can’t see your source. But if the issue is in my examples, I look forward to fixing them. But the key is to not even concern time.

@D-Thrane
Copy link

D-Thrane commented Jun 15, 2022

@dgreene1

My date picker is part og a bigger custom datepicker.

But if I go to https://reactdatepicker.com/ and put the following code in the first default example I get the same problem. It selects one day off.

() => {

const [startDate, setStartDate] = useState(new Date());

function setSelected(date) {
  const year = date.getUTCFullYear();
  const month = ("0" + (date.getMonth()+1)).slice(-2);
  const day = ("0" + date.getUTCDate()).slice(-2);
  const newDate = month + "/" + day + "/" + year;
  setStartDate(new Date(newDate));
}

  return (
    <DatePicker selected={startDate} onChange={(date) => setSelected(date)} />
  );
};

@cesaugo
Copy link

cesaugo commented Jul 4, 2022

Adding timeZone: "UTC" as an argument solved the issue for me
props.date.toLocaleString("en-US", { timeZone: "UTC", day: "2-digit", });

@Ugoku
Copy link
Contributor

Ugoku commented Oct 24, 2022

All the solutions workarounds in this thread did not seem to work for us, but this seems to do the trick. Tested with timezones UTC -4 and UTC +7.

In your ReactDatePickerProps options, add something like selected: getRenderValueAsDate(), and create that function as such:

getRenderValueAsDate() {
    let val = this.getStateValue();
    if (val) {
        return new Date((new Date(val)).toLocaleString('en-US', {
            timeZone: 'UTC',
        }));
    }
    return null;
}

@mathos77
Copy link
Contributor

mathos77 commented Nov 1, 2022

I solved it with the following, still have to test with extreme timezone differences:

const year = e.getUTCFullYear();
const month = e.getMonth();
const day = e.getUTCDate();
const newDate = new Date(Date.UTC(year, month, day, 0, 0, 0));

@AbdoulBaguiM
Copy link

AbdoulBaguiM commented Jan 14, 2023

I used moment from moment-timezone to solve the issue :

`const correctDate = moment(date).format("MMMM Do YYYY");`

@michaelmarziani
Copy link

michaelmarziani commented Jul 4, 2023

I ended up using a similar solution, but applied differently than the others in this thread. I hope it helps the people for which they didn't find the workarounds in here workable.

As others have noted, there aren't issues with selecting the date and showing the correct date on the client. The problem comes when you send the date to an API.

I wrote a function using the solution that seemed the best. I'll include my full comments, as I found this a bit complex and wanted to write it up for my future self:

export const dateToIsoString = (date: Date): string => {
  // Create a new Date subtracting the local timezone offset, thus when converted to JSON i.e. an
  // ISO string, the date in UTC will be correct. This is required because the Date constructor
  // creates a Date object in the local timezone.
  //
  // Detailed explanation:
  // When a person in France selects 7/7/2023 in a date picker, the date object is created like:
  // new Date('2023-07-07 00:00:00'), which is a local datetime with the local timezone offset,
  // with a toString() representation of 'Fri Jul 07 2023 00:00:00 GMT+0200'.
  //
  // When converted to a JSON string with toJSON(), same as calling toISOString(), it becomes
  // '2023-07-06T22:00:00Z' (GMT), which the API reads and saves as 6 July 2023 in the database,
  // a day earlier than what was selected in the date picker.
  //
  // To correct for this, one must create a Date that adjusts for the local timezone offset, again
  // using France as an example, one must create a local Date of 7/7/2023 02:00:00, which when
  // converted toJSON() it becomes '2023-07-07T00:00:00Z', saving correctly as 7 July 2023.
  //
  // The timezone offset in France is -120. This is calculated as UTC time - local time, so
  // 2am UTC - 4am Paris time = -2 hours = -120 minutes, returned by getTimezoneOffset() as -120.

  // Luxon solution (untested)
  // import library: import { DateTime } from 'luxon'
  // const isoDate = DateTime.fromJSDate(date).toISODate();
  // if (!isoDate) throw Error('dateToIsoDate() failed');
  // return isoDate;

  // Native JS solution
  // Get the local Date (date + time) in milliseconds from epoch (1 January 1970 00:00:00 UTC)
  //
  // YES, if one runs new Date('2023-07-07 00:00:00').getTime() in different timezones,
  // the number will differ by the timezone offset.
  const utcTimeInMilliseconds = date.getTime();

  // Get the local timezone offset from UTC in milliseconds
  const timezoneOffsetInMilliseconds = date.getTimezoneOffset() * 60 * 1000;

  // Create a new date subtracting the timezone offset from the local time (subtracting -120
  // for Paris adds 2 hours to the local time, so 00:00:00 becomes 02:00:00).
  const dateMinusTimezoneOffset = new Date(utcTimeInMilliseconds - timezoneOffsetInMilliseconds);

  // Convert the adjusted date to an ISO string, which is the format the API expects
  const isoString = dateMinusTimezoneOffset.toJSON();

  return isoString;
};

Then, as I'm preparing a payload to send to the API, I run the dates through my function:

// Re-map the asset entries, converting the dates to ISO strings
const payload = assetEntries.map((entry) => {
  const { saleDate, assetEndDate } = entry;

  return {
    ...entry,
    saleDate: saleDate && dateToIsoString(saleDate),
    assetEndDate: assetEndDate && dateToIsoString(assetEndDate),
  };
});

Good luck! I'm waiting with bated breath for Temporal's PlainDate!

@dgreene1
Copy link

dgreene1 commented Jul 4, 2023

@michaelmarziani i would recommend deleting that solution for two reasons:

  1. An instant in time (what ISO-8601 represents) is a totally different concept than a date in a users local time and you’re packaging the second concept into the first.
  2. Your product seems to know what local time the code should be offset by, but that offset likely changes for every product based on where the server exists which can change depending on which AWS zone your server is running in. Like if the server falls back from East to West when East goes down, should the offset change? That’s not math the client side should be doing. Instead, you should send the instant in time and the offset to the server so that the server can do the math in a consistent way, which is what my solution above describes.

tl;dr: I’m glad you found a solution that works for your product at this time, but it’s dangerous for others since your solution in their product could cause a problem. It’s like sending 4K picture down to 1080p because you’re losing fidelity and you’re losing information. It’s best to send all the info you have (offset and instant) in the form of a LocalDate or ZonedDatedTime.

@michaelmarziani
Copy link

@dgreene1 Hi Dan, thanks for your thoughts.

To your 1st point, I don't think I'm trying to do either of those things. I am attempting to create an approximation to Temporal.PlainDate, allowing the user to pick a calendar date with no time zone, passing unchanged from the client to the server, and stored in the database as a date type with no time.

To your 2nd point, regarding the code I posted, as you can see this code uses the client's offset from UTC to adjust the time of the Date object such that when it's serialized into an ISO string with toJSON(), it should end up being at midnight UTC at whatever calendar date the user selected, no matter the timezone of the server or database.

At least that's what I'm trying to accomplish; I'm still learning.

I appreciate any further comments you may have. I read your write-up, by the way, but I can't say I fully understood it. I've gone over the Temporal spec a couple times briefly, but I didn't notice an object in js-joda that would map one-to-one with PlainDate.

Warm regards.

@dgreene1
Copy link

dgreene1 commented Jul 4, 2023

@michaelmarziani PlainDate should not be sent with a Z on the end with is what toJSON is going to send. I say this because the Z denotes Zulu which means that it has an offset which makes it not PlainDate but actually an Instant.

it should end up being at midnight UTC at whatever calendar date the user selected

“Midnight UTC” is not a PlainDate, it’s just midnight and the server who is getting that ISO string has no idea if you meant a day, and they are supposed to interpret it as date and time because you sent a Z on the end. A LocalDate in ISO8601 looks like "2022-04-28" not "2022-04-28T15:00:00.000Z"

If you want to send PlainDate then you can’t really use the Date constructor at all and you need to use JsJoda until Temporal is supported.

linking to my breakdown of these concepts: https://dev.to/dgreene1/temporaljoda-concept-breakdown-51gk

tl;dr: if you ever convert a Day to a Date then you risk experiencing the bug that this Github issue expresses. So the best way to avoid this bug is to forever keep the value in LocalDay/PlainDate which I share how here:
https://dev.to/dgreene1/making-your-datepicker-easier-to-work-with-21n4

Note: I’m not trying to promote myself. I’m not a blogger and I don’t want to be. I just want to try to help prevent date bugs since they’re extremely hard to fix later. The cheapest time to fix them is when you first build your date picker.

@michaelmarziani
Copy link

@dgreene1 Thank you for your further thoughts, and I think your passion for date problems is both commendable and understandable as we can see the volume of developers here and all over that are having issues.

I was actually thinking as well about lopping off everything after the T in order to send a more PlainDate-ish string like '2023-07-07' to the API, and agree the Z should definitely go. I just need to test my API (.NET Core) to make sure it supports this. There is a new-ish DateOnly type added in .NET 6 that I hope will receive a PlainDate.

I was dreaming even further that I might just write my own simple date picker that doesn't use Date at all in JS/TS and constructs the date from year-month-day parts and stores it as a string or individual numbers internally, which can then be parsed for display but preserved in this form for the API. I don't think I'll have the time for such a thing, but one can dream.

I'll check out the other writeup you posted. Thanks again for sharing your thoughts.

@Lokeshpatil7
Copy link

I have tried this its working for me

const tempDate = new Date(startOfMonth(new Date()))

const [vaccineMonthDateFrom, setVaccineMonthDateFrom] = useState(
new Date(tempDate.getTime() - (tempDate.getTimezoneOffset() * 60000))
);

const handleMonthChangeFrom = (date) => {
const offsetDate = new Date(date.getTime() - (date.getTimezoneOffset() * 60000));
setVaccineMonthDateFrom(new Date(offsetDate));
};

@Bohooslav
Copy link

6 YEARS YOU ARE AWARE OF THIS ISSUE AND STILL DIDN'T FIX IT? SHAME ON YOU! SHAME!

@martijnrusschen
Copy link
Member

@Bohooslav you're welcome to open a PR to fix this.

@drewjenkins
Copy link

@Bohooslav Whoa there cowboy take a chill pill

@dgreene1
Copy link

dgreene1 commented Aug 7, 2023

To remind everyone, this can’t be fixed in react-datepicker unless this library adopts types and validation that clarify the allowable inputs.

read more here: https://dev.to/dgreene1/making-your-datepicker-easier-to-work-with-21n4

Caveats:

  1. I’m not a member of the react-datepicker team
  2. I am NOT trying to promote myself by linking to my dev.to blog. I make no money off of blogging, and I don’t really enjoy blogging. I’m just pointing to a discussion on the root of the issue that explains that the real culprit is JS Date.

@Bohooslav
Copy link

@dgreene1
Copy link

I’ve proposed a solution to the maintainers and I’d be interested in the community’s input:

#4208

@KirillZhdanov
Copy link

KirillZhdanov commented Oct 19, 2023

Still issue in 4.20.0 and 4.21.0
Using date format dd.MM.yyyy

@dgreene1
Copy link

[KirillZhdanov (https://github.com/KirillZhdanov) commented 4 minutes ago
Still issue in 4.20.0 and 4.21.0
Using date format dd.MM.yyyy

It’s always going to be an issue since it’s a logical issue. I’ve proposed an alternative solution (#4208) that was approved as a concept by the maintainer; however, neither myself or my team have the capacity to implement the PR.

@KirillZhdanov
Copy link

[KirillZhdanov (https://github.com/KirillZhdanov) commented 4 minutes ago
Still issue in 4.20.0 and 4.21.0
Using date format dd.MM.yyyy

It’s always going to be an issue since it’s a logical issue. I’ve proposed an alternative solution (#4208) that was approved as a concept by the maintainer; however, neither myself or my team have the capacity to implement the PR.

Unfortunately, it's not really what you're expecting while using this library. I suspect that this issue has persisted for six years because it often goes unnoticed until it directly impacts project

@ryanbakker
Copy link

Decided to take the lazy approach and push the selected day forward by one and seems to work okay.

  const adjustedDate = new Date(values.dateTime);
  const utcDate = new Date(adjustedDate.toISOString());

  utcDate.setUTCDate(utcDate.getUTCDate() + 1);
  utcDate.setUTCHours(1, 0, 0, 0);

  const newEvent = await createEvent({
    event: { ...values, dateTime: utcDate },
    userId,
    path: "/events",
  });

@nailaiftikhar
Copy link

In case if it helps someone:
I only needed the date field. In my case, converting the date using toLocaleDateString() worked.

@Maeiro
Copy link

Maeiro commented May 20, 2024

Workaround

Still having the issue in 2024, but this workaround works.

@jcbabo
Copy link

jcbabo commented Jun 19, 2024

This works: const offsetDate = new Date(selected.getTime() - (selected.getTimezoneOffset() * 60000));

Just put it in onChange() handler

It worked here

@BugalloF
Copy link

BugalloF commented Jul 4, 2024

@Lokeshpatil7 THANK YOU !
It worked here as well.
const offsetDate = new Date(date.getTime() - (date.getTimezoneOffset() * 60000));

@Prince-AppsTango
Copy link

The dates in my app's database were in UTC time with the ISOString format with a "Z" at the end (ie: 2011-10-05T14:48:00.000Z) and I was converting them to Moments as such: Moment("2011-10-05T14:48:00.000Z") but this was using my local UTC offset, causing them to appear one day off.

Creating my dates like this: Moment.utc("2011-10-05T14:48:00.000Z") fixed my issue

ref: https://maggiepint.com/2016/05/14/moment-js-shows-the-wrong-date/

thanks

@anki247
Copy link

anki247 commented Oct 7, 2024

How I fixed it for me...

import { DateTime } from "luxon";

<DatePicker
  dateFormat="yyyy-MM-dd"
  selected={value ? DateTime.fromISO(value.toString()).toJSDate() : null}
  onChange={(date) => {
    onChange(date ? DateTime.fromJSDate(date).toISODate() : null);
  }}
/>

@lunchin
Copy link

lunchin commented Nov 13, 2024

How I fixed it for me...

import { DateTime } from "luxon";

<DatePicker
  dateFormat="yyyy-MM-dd"
  selected={value ? DateTime.fromISO(value.toString()).toJSDate() : null}
  onChange={(date) => {
    onChange(date ? DateTime.fromJSDate(date).toISODate() : null);
  }}
/>

This worked for me, thanks alot, none of the other solutions worked for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests