mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-10 00:27:41 +08:00
feat(datetime): add custom timezone display property (#19519)
resolves #19401
This commit is contained in:
committed by
Liam DeBeasi
parent
39d12629db
commit
7b032c5e9b
8
core/src/components.d.ts
vendored
8
core/src/components.d.ts
vendored
@ -680,6 +680,10 @@ export namespace Components {
|
||||
*/
|
||||
'displayFormat': string;
|
||||
/**
|
||||
* The timezone to use for display purposes only. See [Date.prototype.toLocaleString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString) for a list of supported timezones. If no value is provided, the component will default to displaying times in the user's local timezone.
|
||||
*/
|
||||
'displayTimezone'?: string;
|
||||
/**
|
||||
* The text to display on the picker's "Done" button.
|
||||
*/
|
||||
'doneText': string;
|
||||
@ -4116,6 +4120,10 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
'displayFormat'?: string;
|
||||
/**
|
||||
* The timezone to use for display purposes only. See [Date.prototype.toLocaleString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString) for a list of supported timezones. If no value is provided, the component will default to displaying times in the user's local timezone.
|
||||
*/
|
||||
'displayTimezone'?: string;
|
||||
/**
|
||||
* The text to display on the picker's "Done" button.
|
||||
*/
|
||||
'doneText'?: string;
|
||||
|
||||
@ -242,12 +242,13 @@ export const parseDate = (val: string | undefined | null): DatetimeData | undefi
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a valid UTC datetime string
|
||||
* To the user's local timezone
|
||||
* Converts a valid UTC datetime string to JS Date time object.
|
||||
* By default uses the users local timezone, but an optional
|
||||
* timezone can be provided.
|
||||
* Note: This is not meant for time strings
|
||||
* such as "01:47"
|
||||
*/
|
||||
export const getLocalDateTime = (dateString: any = ''): Date => {
|
||||
export const getDateTime = (dateString: any = '', timeZone: any = ''): Date => {
|
||||
/**
|
||||
* If user passed in undefined
|
||||
* or null, convert it to the
|
||||
@ -273,7 +274,7 @@ export const getLocalDateTime = (dateString: any = ''): Date => {
|
||||
}
|
||||
|
||||
const date = (typeof dateString === 'string' && dateString.length > 0) ? new Date(dateString) : new Date();
|
||||
return new Date(
|
||||
const localDateTime = new Date(
|
||||
Date.UTC(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
@ -284,14 +285,26 @@ export const getLocalDateTime = (dateString: any = ''): Date => {
|
||||
date.getMilliseconds()
|
||||
)
|
||||
);
|
||||
|
||||
if (timeZone && timeZone.length > 0) {
|
||||
return new Date(date.getTime() - getTimezoneOffset(localDateTime, timeZone));
|
||||
}
|
||||
|
||||
return localDateTime;
|
||||
};
|
||||
|
||||
export const getTimezoneOffset = (localDate: Date, timeZone: string) => {
|
||||
const utcDateTime = new Date(localDate.toLocaleString('en-US', { timeZone: 'utc' }));
|
||||
const tzDateTime = new Date(localDate.toLocaleString('en-US', { timeZone }));
|
||||
return utcDateTime.getTime() - tzDateTime.getTime();
|
||||
};
|
||||
|
||||
export const updateDate = (existingData: DatetimeData, newData: any): boolean => {
|
||||
export const updateDate = (existingData: DatetimeData, newData: any, displayTimezone?: string): boolean => {
|
||||
|
||||
if (!newData || typeof newData === 'string') {
|
||||
const localDateTime = getLocalDateTime(newData);
|
||||
if (!Number.isNaN(localDateTime.getTime())) {
|
||||
newData = localDateTime.toISOString();
|
||||
const dateTime = getDateTime(newData, displayTimezone);
|
||||
if (!Number.isNaN(dateTime.getTime())) {
|
||||
newData = dateTime.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { clamp, findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { pickerController } from '../../utils/overlays';
|
||||
import { hostContext } from '../../utils/theme';
|
||||
|
||||
import { DatetimeData, LocaleData, convertDataToISO, convertFormatToKey, convertToArrayOfNumbers, convertToArrayOfStrings, dateDataSortValue, dateSortValue, dateValueRange, daysInMonth, getDateValue, parseDate, parseTemplate, renderDatetime, renderTextFormat, updateDate } from './datetime-util';
|
||||
import { DatetimeData, LocaleData, convertDataToISO, convertFormatToKey, convertToArrayOfNumbers, convertToArrayOfStrings, dateDataSortValue, dateSortValue, dateValueRange, daysInMonth, getDateValue, getTimezoneOffset, parseDate, parseTemplate, renderDatetime, renderTextFormat, updateDate } from './datetime-util';
|
||||
|
||||
/**
|
||||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use.
|
||||
@ -81,6 +81,14 @@ export class Datetime implements ComponentInterface {
|
||||
*/
|
||||
@Prop() displayFormat = 'MMM D, YYYY';
|
||||
|
||||
/**
|
||||
* The timezone to use for display purposes only. See
|
||||
* [Date.prototype.toLocaleString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString)
|
||||
* for a list of supported timezones. If no value is provided, the
|
||||
* component will default to displaying times in the user's local timezone.
|
||||
*/
|
||||
@Prop() displayTimezone?: string;
|
||||
|
||||
/**
|
||||
* The format of the date and time picker columns the user selects.
|
||||
* A datetime input can have one or many datetime parts, each getting their
|
||||
@ -287,7 +295,7 @@ export class Datetime implements ComponentInterface {
|
||||
}
|
||||
|
||||
private updateDatetimeValue(value: any) {
|
||||
updateDate(this.datetimeValue, value);
|
||||
updateDate(this.datetimeValue, value, this.displayTimezone);
|
||||
}
|
||||
|
||||
private generatePickerOptions(): PickerOptions {
|
||||
@ -326,7 +334,11 @@ export class Datetime implements ComponentInterface {
|
||||
* there can be 1 hr difference when dealing w/ DST
|
||||
*/
|
||||
const date = new Date(convertDataToISO(this.datetimeValue));
|
||||
this.datetimeValue.tzOffset = date.getTimezoneOffset() * -1;
|
||||
|
||||
// If a custom display timezone is provided, use that tzOffset value instead
|
||||
this.datetimeValue.tzOffset = (this.displayTimezone !== undefined && this.displayTimezone.length > 0)
|
||||
? ((getTimezoneOffset(date, this.displayTimezone)) / 1000 / 60) * -1
|
||||
: date.getTimezoneOffset() * -1;
|
||||
|
||||
this.value = convertDataToISO(this.datetimeValue);
|
||||
}
|
||||
|
||||
@ -58,9 +58,24 @@ above can be passed in to the display format in any combination.
|
||||
| `YYYY, MMMM` | `2005, June` |
|
||||
| `MMM DD, YYYY HH:mm` | `Jun 17, 2005 11:06` |
|
||||
|
||||
**Important**: `ion-datetime` will always display values relative to the user's timezone.
|
||||
**Important**: `ion-datetime` will by default display values relative to the user's timezone.
|
||||
Given a value of `09:00:00+01:00`, the datetime component will
|
||||
display it as `04:00:00-04:00` for users in a `-04:00` timezone offset.
|
||||
To change the display to use a different timezone, use the displayTimezone property described below.
|
||||
|
||||
### Display Timezone
|
||||
|
||||
The `displayTimezone` property allows you to change the default behavior
|
||||
of displaying values relative to the user's local timezone. In addition to "UTC" valid
|
||||
time zone values are determined by the browser, and in most cases follow the time zone names
|
||||
of the [IANA time zone database](https://www.iana.org/time-zones), such as "Asia/Shanghai",
|
||||
"Asia/Kolkata", "America/New_York". In the following example:
|
||||
|
||||
```html
|
||||
<ion-datetime value="2019-10-01T15:43:40.394Z" display-timezone="utc"></ion-datetime>
|
||||
```
|
||||
|
||||
The displayed value will not be converted and will be displayed as provided (UTC).
|
||||
|
||||
|
||||
### Picker Format
|
||||
@ -650,6 +665,7 @@ export const DateTimeExample: React.FC = () => (
|
||||
| `dayValues` | `day-values` | Values used to create the list of selectable days. By default every day is shown for the given month. However, to control exactly which days of the month to display, the `dayValues` input can take a number, an array of numbers, or a string of comma separated numbers. Note that even if the array days have an invalid number for the selected month, like `31` in February, it will correctly not show days which are not valid for the selected month. | `number \| number[] \| string \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the datetime. | `boolean` | `false` |
|
||||
| `displayFormat` | `display-format` | The display format of the date and time as text that shows within the item. When the `pickerFormat` input is not used, then the `displayFormat` is used for both display the formatted text, and determining the datetime picker's columns. See the `pickerFormat` input description for more info. Defaults to `MMM D, YYYY`. | `string` | `'MMM D, YYYY'` |
|
||||
| `displayTimezone` | `display-timezone` | The timezone to use for display purposes only. See [Date.prototype.toLocaleString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleString) for a list of supported timezones. If no value is provided, the component will default to displaying times in the user's local timezone. | `string \| undefined` | `undefined` |
|
||||
| `doneText` | `done-text` | The text to display on the picker's "Done" button. | `string` | `'Done'` |
|
||||
| `hourValues` | `hour-values` | Values used to create the list of selectable hours. By default the hour values range from `0` to `23` for 24-hour, or `1` to `12` for 12-hour. However, to control exactly which hours to display, the `hourValues` input can take a number, an array of numbers, or a string of comma separated numbers. | `number \| number[] \| string \| undefined` | `undefined` |
|
||||
| `max` | `max` | The maximum datetime allowed. Value must be a date string following the [ISO 8601 datetime format standard](https://www.w3.org/TR/NOTE-datetime), `1996-12-19`. The format does not have to be specific to an exact datetime. For example, the maximum could just be the year, such as `1994`. Defaults to the end of this year. | `string \| undefined` | `undefined` |
|
||||
|
||||
@ -31,12 +31,12 @@
|
||||
<ion-label>Default</ion-label>
|
||||
<ion-datetime></ion-datetime>
|
||||
</ion-item>
|
||||
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">Default with floating label</ion-label>
|
||||
<ion-datetime></ion-datetime>
|
||||
</ion-item>
|
||||
|
||||
|
||||
<ion-item>
|
||||
<ion-label position="floating">Placeholder with floating label</ion-label>
|
||||
<ion-datetime placeholder="Select a date"></ion-datetime>
|
||||
@ -142,6 +142,23 @@
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>Display UTC 00:00 in Local Timezone (default behavior)</ion-label>
|
||||
<ion-datetime display-format="MMM DD, YYYY HH:mm" value="2020-01-01T00:00:00Z"></ion-datetime>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Display UTC 00:00 in UTC (display-timezone = 'utc')</ion-label>
|
||||
<ion-datetime display-format="MMM DD, YYYY HH:mm" value="2020-01-01T00:00:00Z" display-timezone="utc"></ion-datetime>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Display UTC 00:00 in US Pacific Time (display-timezone = 'America/Los_Angeles')</ion-label>
|
||||
<ion-datetime display-format="MMM DD, YYYY HH:mm" value="2020-01-01T00:00:00Z" display-timezone="America/Los_Angeles"></ion-datetime>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-item>
|
||||
<ion-label>HH:mm:ss</ion-label>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { DatetimeData, daysInMonth, getDateValue, getLocalDateTime, renderDatetime } from '../datetime-util';
|
||||
import { DatetimeData, daysInMonth, getDateValue, getDateTime, renderDatetime } from '../datetime-util';
|
||||
|
||||
describe('Datetime', () => {
|
||||
describe('getDateValue()', () => {
|
||||
@ -32,7 +32,7 @@ describe('Datetime', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLocalDateTime()', () => {
|
||||
describe('getDateTime()', () => {
|
||||
it('should format a datetime string according to the local timezone', () => {
|
||||
|
||||
const dateStringTests = [
|
||||
@ -44,7 +44,7 @@ describe('Datetime', () => {
|
||||
];
|
||||
|
||||
dateStringTests.forEach(test => {
|
||||
const convertToLocal = getLocalDateTime(test.input);
|
||||
const convertToLocal = getDateTime(test.input);
|
||||
|
||||
const timeZoneOffset = convertToLocal.getTimezoneOffset() / 60;
|
||||
const expectedDateString = test.expectedOutput.replace('%HOUR%', padNumber(test.expectedHourUTC - timeZoneOffset));
|
||||
@ -65,19 +65,32 @@ describe('Datetime', () => {
|
||||
];
|
||||
|
||||
dateStringTests.forEach(test => {
|
||||
const convertToLocal = getLocalDateTime(test.input);
|
||||
const convertToLocal = getDateTime(test.input);
|
||||
expect(convertToLocal.toISOString()).toContain(test.expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
it('should format a datetime string using provided timezone', () => {
|
||||
const dateStringTests = [
|
||||
{ displayTimezone: 'utc', input: `2019-03-02T12:00:00.000Z`, expectedOutput: `2019-03-02T12:00:00.000Z` },
|
||||
{ displayTimezone: 'America/New_York', input: `2019-03-02T12:00:00.000Z`, expectedOutput: `2019-03-02T07:00:00.000Z` },
|
||||
{ displayTimezone: 'Asia/Tokyo', input: `2019-03-02T12:00:00.000Z`, expectedOutput: `2019-03-02T21:00:00.000Z` },
|
||||
];
|
||||
|
||||
dateStringTests.forEach(test => {
|
||||
const convertToLocal = getDateTime(test.input, test.displayTimezone);
|
||||
expect(convertToLocal.toISOString()).toEqual(test.expectedOutput);
|
||||
});
|
||||
});
|
||||
|
||||
it('should default to today for null and undefined cases', () => {
|
||||
const today = new Date();
|
||||
const todayString = renderDatetime('YYYY-MM-DD', { year: today.getFullYear(), month: today.getMonth() + 1, day: today.getDate() } )
|
||||
|
||||
const convertToLocalUndefined = getLocalDateTime(undefined);
|
||||
const convertToLocalUndefined = getDateTime(undefined);
|
||||
expect(convertToLocalUndefined.toISOString()).toContain(todayString);
|
||||
|
||||
const convertToLocalNull = getLocalDateTime(null);
|
||||
const convertToLocalNull = getDateTime(null);
|
||||
expect(convertToLocalNull.toISOString()).toContain(todayString);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user