Skip to content

Commit

Permalink
Merge pull request #10311 from marmelab/fix-dateinput-shows-wrong-tim…
Browse files Browse the repository at this point in the history
…ezone

Fix DateInput ignores the timezone when given
  • Loading branch information
slax57 authored Oct 28, 2024
2 parents b5aa5d4 + fd4f57c commit 60bae65
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 18 deletions.
13 changes: 12 additions & 1 deletion docs/DateInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,18 @@ import { DateInput } from 'react-admin';
<DateInput source="published_at" />
```

The field value must be a string with the pattern `YYYY-MM-DD` (ISO 8601), e.g. `'2022-04-30'`.
The field value must be a string using the pattern `YYYY-MM-DD` (ISO 8601), e.g. `'2022-04-30'`. The returned input value will also be in this format, regardless of the browser locale.

`<DateInput>` also accepts values that can be converted to a `Date` object, such as:

- a localized date string (e.g. `'30/04/2022'`),
- an ISO date string (e.g. `'2022-04-30T00:00:00.000Z'`),
- a `Date` object, or
- a Linux timestamp (e.g. `1648694400000`).

In these cases, `<DateInput>` will automatically convert the value to the `YYYY-MM-DD` format.

**Note**: This conversion may change the date because of timezones. For example, the date string `'2022-04-30T00:00:00.000Z'` in Europe may be displayed as `'2022-04-29'` in Honolulu. If this is not what you want, pass your own [`parse`](./Inputs.md#parse) function to `<DateInput>`.

## Props

Expand Down
4 changes: 2 additions & 2 deletions packages/ra-ui-materialui/src/input/DateInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ describe('<DateInput />', () => {
it.each([
'2021-09-11T20:46:20.000+02:00',
'2021-09-11 20:46:20.000+02:00',
'2021-09-11T20:46:20.000-04:00',
'2021-09-11 20:46:20.000-04:00',
'2021-09-10T20:46:20.000-04:00',
'2021-09-10 20:46:20.000-04:00',
'2021-09-11T20:46:20.000Z',
'2021-09-11 20:46:20.000Z',
])('should accept a value with timezone %s', async publishedAt => {
Expand Down
2 changes: 1 addition & 1 deletion packages/ra-ui-materialui/src/input/DateInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const DefaultValue = () => (
'2021-09-11 20:46:20.000Z',
new Date('2021-09-11T20:46:20.000+02:00'),
// although this one is 2021-09-10, its local timezone makes it 2021-09-11 in the test timezone
new Date('2021-09-10T20:46:20.000-04:00'),
new Date('2021-09-10T23:46:20.000-09:00'),
new Date('2021-09-11T20:46:20.000Z'),
1631385980000,
].map((defaultValue, index) => (
Expand Down
34 changes: 20 additions & 14 deletions packages/ra-ui-materialui/src/input/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,22 +205,22 @@ export type DateInputProps = CommonInputProps &
Omit<TextFieldProps, 'helperText' | 'label'>;

/**
* Convert Date object to String, ignoring the timezone.
* Convert Date object to String, using the local timezone
*
* @param {Date} value value to convert
* @returns {String} A standardized date (yyyy-MM-dd), to be passed to an <input type="date" />
*/
const convertDateToString = (value: Date) => {
if (!(value instanceof Date) || isNaN(value.getDate())) return '';
let UTCDate = new Date(value.getTime() + value.getTimezoneOffset() * 60000);
let localDate = new Date(value.getTime());
const pad = '00';
const yyyy = UTCDate.getFullYear().toString();
const MM = (UTCDate.getMonth() + 1).toString();
const dd = UTCDate.getDate().toString();
const yyyy = localDate.getFullYear().toString();
const MM = (localDate.getMonth() + 1).toString();
const dd = localDate.getDate().toString();
return `${yyyy}-${(pad + MM).slice(-2)}-${(pad + dd).slice(-2)}`;
};

const dateRegex = /^(\d{4}-\d{2}-\d{2}).*$/;
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
const defaultInputLabelProps = { shrink: true };

/**
Expand All @@ -234,14 +234,21 @@ const defaultInputLabelProps = { shrink: true };
* - a Linux timestamp
* - an empty string
*
* When it's not a bare date string (YYYY-MM-DD), the value is converted to
* this format using the JS Date object.
* THIS MAY CHANGE THE DATE VALUE depending on the browser locale.
* For example, the string "09/11/2021" may be converted to "2021-09-10"
* in Honolulu. This is expected behavior.
* If this is not what you want, you should provide your own parse method.
*
* The output is always a string in the "YYYY-MM-DD" format.
*
* @example
* defaultFormat('2021-09-11'); // '2021-09-11'
* defaultFormat('09/11/2021'); // '2021-09-11'
* defaultFormat('2021-09-11T20:46:20.000Z'); // '2021-09-11'
* defaultFormat(new Date('2021-09-11T20:46:20.000Z')); // '2021-09-11'
* defaultFormat(1631385980000); // '2021-09-11'
* defaultFormat('09/11/2021'); // '2021-09-11' (may change depending on the browser locale)
* defaultFormat('2021-09-11T20:46:20.000Z'); // '2021-09-11' (may change depending on the browser locale)
* defaultFormat(new Date('2021-09-11T20:46:20.000Z')); // '2021-09-11' (may change depending on the browser locale)
* defaultFormat(1631385980000); // '2021-09-11' (may change depending on the browser locale)
* defaultFormat(''); // null
*/
const defaultFormat = (value: string | Date | number) => {
Expand All @@ -256,11 +263,10 @@ const defaultFormat = (value: string | Date | number) => {
return convertDateToString(value);
}

// Valid date strings should be stripped of their time and timezone parts.
// Valid date strings (YYYY-MM-DD) should be considered as is
if (typeof value === 'string') {
const matches = dateRegex.exec(value);
if (matches) {
return matches[1];
if (dateRegex.test(value)) {
return value;
}
}

Expand Down

0 comments on commit 60bae65

Please sign in to comment.