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

Fix DateInput ignores the timezone when given #10311

Merged
merged 1 commit into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading