Skip to content

Commit

Permalink
feat(ui5-date-*): adapt date and time controls to timezone feature (#…
Browse files Browse the repository at this point in the history
…8610)

Adapting the date and time controls to the timezone feature, allowing the controls to present date in different timezones.

Adaptation is done by using the UI5Date object provided by ui5.

Can be tested either via html tag:

<script data-ui5-config type="application/json">
	{
		"language": "EN",
		"timezone" : "Pacific/Apia"
	}
</script>
or by adding a query parameter to the url: ?sap-ui-timezone=XXXX

The timezone identifiers format is the one used in IANA timezone database.

Related to: #8461
  • Loading branch information
tsanislavgatev committed Apr 12, 2024
1 parent b589486 commit 1acae01
Show file tree
Hide file tree
Showing 27 changed files with 298 additions and 18 deletions.
22 changes: 21 additions & 1 deletion docs/2-advanced/01-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ There are several configuration settings that affect all UI5 Web Components glob
| [noConflict](#noConflict) | `true`, `false` | `false` | When set to true, all events will be fired with a `ui5-` prefix only | Components that fire events (most do) |
| [formatSettings](#formatSettings) | See the [Format settings](#formatSettings) section below | `{}` | Allows to override locale-specific configuration | Date/time components (`ui5-date-picker`, etc.) |
| [fetchDefaultLanguage](#fetchDefaultLanguage) | `true`, `false` | `false` | Whether to fetch assets even for the default language | Framework |
| [timezone](#timezone) | `Asia/Tokyo`, `Pacific/Apia`, `Asia/Kolkata`, `Europe/Sofia` and etc. | Your local time zone. | Allows to override your local time zone. | Date/time components (`ui5-date-picker`, etc.) |

### theme
<a name="theme"></a>
Expand Down Expand Up @@ -218,6 +219,19 @@ Example:
}
</script>
```
### timezone
<a name="timezone"></a>

The time zone should be an IANA time zone ID, e.g. "America/New_York". It can be set to the `timezone` property in the configuration script.

Example:
```html
<script data-ui5-config type="application/json">
{
"timezone": "Europe/Sofia"
}
</script>
```

## Configuration Script
<a name="script"></a>
Expand All @@ -244,7 +258,8 @@ Example:
"noConflict": {
"events": ["selection-change", "header-click"]
},
"fetchDefaultLanguage": true
"fetchDefaultLanguage": true,
"timezone": "Europe/Sofia"
}
</script>
```
Expand Down Expand Up @@ -297,3 +312,8 @@ import { getFirstDayOfWeek } from "@ui5/webcomponents-base/dist/config/FormatSet
```js
import { getFetchDefaultLanguage, setFetchDefaultLanguage } from "@ui5/webcomponents-base/dist/config/Language.js";
```
- `timezone` - can only be set initially in the configuration script.

```js
import { getTimezone } from "@ui5/webcomponents-base/dist/config/Timezone.js";
```
7 changes: 4 additions & 3 deletions packages/main/src/Calendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import getCachedLocaleDataInstance from "@ui5/webcomponents-localization/dist/getCachedLocaleDataInstance.js";
import getLocale from "@ui5/webcomponents-base/dist/locale/getLocale.js";
import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import CalendarDate from "./CalendarDate.js";
import CalendarPart from "./CalendarPart.js";
import CalendarHeader from "./CalendarHeader.js";
Expand Down Expand Up @@ -305,7 +306,7 @@ class Calendar extends CalendarPart {
* @private
*/
_setSelectedDates(selectedDates: Array<number>) {
const selectedValues = selectedDates.map(timestamp => this.getFormat().format(new Date(timestamp * 1000), true)); // Format as UTC
const selectedValues = selectedDates.map(timestamp => this.getFormat().format(UI5Date.getInstance(timestamp * 1000), true)); // Format as UTC
const valuesInDOM = [...this.dates].map(dateElement => dateElement.value);

// Remove all elements for dates that are no longer selected
Expand Down Expand Up @@ -342,7 +343,7 @@ class Calendar extends CalendarPart {
const uniqueSpecialDates: Array<SpecialCalendarDateT> = [];

validSpecialDates.forEach(date => {
const dateFromValue = new Date(date.value);
const dateFromValue = UI5Date.getInstance(date.value);
const timestamp = dateFromValue.getTime();

if (!uniqueDates.has(timestamp)) {
Expand Down Expand Up @@ -484,7 +485,7 @@ class Calendar extends CalendarPart {
return;
}

const localDate = new Date(this._timestamp * 1000);
const localDate = UI5Date.getInstance(this._timestamp * 1000);
const secondYearFormat = DateFormat.getDateInstance({ format: "y", calendarType: this._secondaryCalendarType });
const dateInSecType = transformDateToSecondaryType(this._primaryCalendarType, this._secondaryCalendarType, this._timestamp);
const secondMonthInfo = convertMonthNumbersToMonthNames(dateInSecType.firstDate.getMonth(), dateInSecType.lastDate.getMonth(), this._secondaryCalendarType);
Expand Down
3 changes: 2 additions & 1 deletion packages/main/src/CalendarPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
import modifyDateBy from "@ui5/webcomponents-localization/dist/dates/modifyDateBy.js";
import getTodayUTCTimestamp from "@ui5/webcomponents-localization/dist/dates/getTodayUTCTimestamp.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import DateComponentBase from "./DateComponentBase.js";

/**
Expand Down Expand Up @@ -51,7 +52,7 @@ class CalendarPart extends DateComponentBase {
}

get _localDate() {
return new Date(this._timestamp * 1000);
return UI5Date.getInstance(this._timestamp * 1000);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion packages/main/src/DateComponentBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import CalendarType from "@ui5/webcomponents-base/dist/types/CalendarType.js";
import getLocale from "@ui5/webcomponents-base/dist/locale/getLocale.js";
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
import { getMaxCalendarDate, getMinCalendarDate } from "@ui5/webcomponents-localization/dist/dates/ExtremeDates.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";

/**
* @class
Expand Down Expand Up @@ -160,7 +161,7 @@ class DateComponentBase extends UI5Element {
}

_getStringFromTimestamp(timestamp: number) {
const localDate = new Date(timestamp);
const localDate = UI5Date.getInstance(timestamp);
return this.getFormat().format(localDate, true);
}

Expand Down
3 changes: 2 additions & 1 deletion packages/main/src/DateTimePicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import getCachedLocaleDataInstance from "@ui5/webcomponents-localization/dist/ge
import modifyDateBy from "@ui5/webcomponents-localization/dist/dates/modifyDateBy.js";
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
import "@ui5/webcomponents-icons/dist/date-time.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import Button from "./Button.js";
import type ResponsivePopover from "./ResponsivePopover.js";
import ToggleButton from "./ToggleButton.js";
Expand Down Expand Up @@ -195,7 +196,7 @@ class DateTimePicker extends DatePicker {
await super.openPicker();
this._previewValues = {
...this._previewValues,
timeSelectionValue: this.value || this.getFormat().format(new Date()),
timeSelectionValue: this.value || this.getFormat().format(UI5Date.getInstance()),
};
}

Expand Down
3 changes: 2 additions & 1 deletion packages/main/src/DayPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
import CalendarDate from "@ui5/webcomponents-localization/dist/dates/CalendarDate.js";
import calculateWeekNumber from "@ui5/webcomponents-localization/dist/dates/calculateWeekNumber.js";
import CalendarType from "@ui5/webcomponents-base/dist/types/CalendarType.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import CalendarSelectionMode from "./types/CalendarSelectionMode.js";
import CalendarPart from "./CalendarPart.js";
import type {
Expand Down Expand Up @@ -220,7 +221,7 @@ class DayPicker extends CalendarPart implements ICalendarPicker {
const nonWorkingDayLabel = DayPicker.i18nBundle.getText(DAY_PICKER_NON_WORKING_DAY);
const todayLabel = DayPicker.i18nBundle.getText(DAY_PICKER_TODAY);
const tempDate = this._getFirstDay(); // date that will be changed by 1 day 42 times
const todayDate = CalendarDate.fromLocalJSDate(new Date(), this._primaryCalendarType); // current day date - calculate once
const todayDate = CalendarDate.fromLocalJSDate(UI5Date.getInstance(), this._primaryCalendarType); // current day date - calculate once
const calendarDate = this._calendarDate; // store the _calendarDate value as this getter is expensive and degrades IE11 perf
const minDate = this._minDate; // store the _minDate (expensive getter)
const maxDate = this._maxDate; // store the _maxDate (expensive getter)
Expand Down
5 changes: 3 additions & 2 deletions packages/main/src/TimePickerBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
isF6Previous,
} from "@ui5/webcomponents-base/dist/Keys.js";
import "@ui5/webcomponents-icons/dist/time-entry-request.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import Icon from "./Icon.js";
import Popover from "./Popover.js";
import ResponsivePopover from "./ResponsivePopover.js";
Expand Down Expand Up @@ -233,7 +234,7 @@ class TimePickerBase extends UI5Element {
* @returns Resolves when the picker is open
*/
openPicker(): void {
this.tempValue = this.value && this.isValid(this.value) ? this.value : this.getFormat().format(new Date());
this.tempValue = this.value && this.isValid(this.value) ? this.value : this.getFormat().format(UI5Date.getInstance());
const responsivePopover = this._getPopover();
responsivePopover.showAt(this);
}
Expand Down Expand Up @@ -286,7 +287,7 @@ class TimePickerBase extends UI5Element {
* @returns Resolves when the Inputs popover is open
*/
openInputsPopover() {
this.tempValue = this.value && this.isValid(this.value) ? this.value : this.getFormat().format(new Date());
this.tempValue = this.value && this.isValid(this.value) ? this.value : this.getFormat().format(UI5Date.getInstance());
const popover = this._getInputsPopover();
popover.showAt(this);
this._isInputsPopoverOpen = true;
Expand Down
5 changes: 3 additions & 2 deletions packages/main/src/TimePickerInternals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "@ui5/webcomponents-localization/dist/features/calendar/Gregorian.js"; //
import CalendarType from "@ui5/webcomponents-base/dist/types/CalendarType.js";
import { fetchCldr } from "@ui5/webcomponents-base/dist/asset-registries/LocaleData.js";
import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import SegmentedButton from "./SegmentedButton.js";
import {
getHoursConfigByFormat,
Expand Down Expand Up @@ -220,11 +221,11 @@ class TimePickerInternals extends UI5Element {
}

get dateValue() {
return this.value ? this.getFormat().parse(this.value, undefined as unknown as boolean, undefined as unknown as boolean) as Date : new Date();
return this.value ? this.getFormat().parse(this.value, undefined as unknown as boolean, undefined as unknown as boolean) as Date : UI5Date.getInstance();
}

get validDateValue() {
return this.value !== undefined && this.isValid(this.value) ? this.dateValue : new Date();
return this.value !== undefined && this.isValid(this.value) ? this.dateValue : UI5Date.getInstance();
}

get periodsArray() {
Expand Down
5 changes: 3 additions & 2 deletions packages/main/src/TimeSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
isRight,
} from "@ui5/webcomponents-base/dist/Keys.js";
import "@ui5/webcomponents-icons/dist/time-entry-request.js";
import UI5Date from "@ui5/webcomponents-localization/dist/dates/UI5Date.js";
import timeSelectionTemplate from "./generated/templates/TimeSelectionTemplate.lit.js";
import WheelSlider from "./WheelSlider.js";
import {
Expand Down Expand Up @@ -455,11 +456,11 @@ class TimeSelection extends UI5Element {
}

get dateValue() {
return this.value ? this.getFormat().parse(this.value) as Date : new Date();
return this.value ? this.getFormat().parse(this.value) as Date : UI5Date.getInstance();
}

get validDateValue() {
return this.value !== undefined && this.isValid(this.value) ? this.dateValue : new Date();
return this.value !== undefined && this.isValid(this.value) ? this.dateValue : UI5Date.getInstance();
}

get hoursSliderTitle() {
Expand Down
44 changes: 44 additions & 0 deletions packages/main/test/pages/DateControlsWithTimezone.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta charset="utf-8">

<script>
// delete Document.prototype.adoptedStyleSheets;
</script>
<link rel="stylesheet" type="text/css" href="./styles/TimePicker.css">
<script data-ui5-config type="application/json">
{
"language": "EN",
"timezone" : "Pacific/Apia"
}
</script>

<script src="%VITE_BUNDLE_PATH%" type="module"></script>


</head>
<body class="timepicker1auto">

<div class="samples">
<div class="sample-box-1">
<ui5-time-picker id="timePickerNow" value="now" format-pattern="HH:mm:ss"></ui5-time-picker>
<br>
</div>
<div class="sample-box-1">
<ui5-date-picker id="datePickerNow" value="now" format-pattern="dd/MM/yyyy"></ui5-date-picker>
<br>
</div>
<div class="sample-box-1">
<ui5-datetime-picker id="dateTimePickerNow" value="now" format-pattern="dd/MM/yyyy HH:mm:ss"></ui5-datetime-picker>
<br>
</div>
<div class="sample-box-1">
<ui5-calendar id="calendar1"></ui5-calendar>
<br>
</div>
</div>

</body>
</html>
55 changes: 55 additions & 0 deletions packages/main/test/specs/DateControlsWithTimezone.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { assert } from "chai";

describe("Calendar general interaction", () => {
before(async () => {
await browser.url(`test/pages/DateControlsWithTimezone.html`);
});

it("The date is with the correct offset in date picker", async () => {

const datePicker = await browser.$("#datePickerNow");
const datePickerValue = await datePicker.shadow$("ui5-input").getValue();
const now = new Date();
const offset = now.getTimezoneOffset();
now.setMinutes(now.getMinutes() + offset);
now.setHours(now.getHours() + 13);
const datePickerValues = datePickerValue.split("/");

assert.strictEqual(now.getDate(), Number(datePickerValues[0]), "Date is correct");
assert.strictEqual(now.getMonth(), Number(datePickerValues[1] - 1), "Month is correct"); // Month is -1 because JSDate starts months count from 0
assert.strictEqual(now.getFullYear(), Number(datePickerValues[2]), "Year is correct");
});

it("The time is with the correct offset in time picker", async () => {

const timePicker = await browser.$("#timePickerNow");
const timePickerValue = await timePicker.shadow$("ui5-input").getValue();
const now = new Date();
const offset = now.getTimezoneOffset();
now.setMinutes(now.getMinutes() + offset);
now.setHours(now.getHours() + 13);
const timePickerValues = timePickerValue.split(":");

assert.strictEqual(now.getHours(), Number(timePickerValues[0]), "Hour is correct");
assert.strictEqual(now.getMinutes(), Number(timePickerValues[1]), "Minute is correct");
});

it("The date and time is with the correct offset in datetime picker", async () => {

const dateTimePicker = await browser.$("#dateTimePickerNow");
const dateTimePickerValue = await dateTimePicker.shadow$("ui5-input").getValue();
const now = new Date();
const offset = now.getTimezoneOffset();
now.setMinutes(now.getMinutes() + offset);
now.setHours(now.getHours() + 13);
const dateTimePickerValues = dateTimePickerValue.split(" ");
const datePickerValues = dateTimePickerValues[0].split("/");
const timePickerValues = dateTimePickerValues[1].split(":");

assert.strictEqual(now.getDate(), Number(datePickerValues[0]), "Date is correct");
assert.strictEqual(now.getMonth(), Number(datePickerValues[1] - 1), "Month is correct"); // Month is -1 because JSDate starts months count from 0
assert.strictEqual(now.getFullYear(), Number(datePickerValues[2]), "Year is correct");
assert.strictEqual(now.getHours(), Number(timePickerValues[0]), "Hour is correct");
assert.strictEqual(now.getMinutes(), Number(timePickerValues[1]), "Minute is correct");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Bounds from "../../../_samples/main/Calendar/Bounds/Bounds.md";
import CalendarTypes from "../../../_samples/main/Calendar/CalendarTypes/CalendarTypes.md";
import CalendarWithLegend from "../../../_samples/main/Calendar/CalendarWithLegend/CalendarWithLegend.md"
import SelectionModes from "../../../_samples/main/Calendar/SelectionModes/SelectionModes.md";
import CalendarInDifferentTimezone from "../../../_samples/main/Calendar/CalendarInDifferentTimezone/CalendarInDifferentTimezone.md";
import useBaseUrl from "@docusaurus/useBaseUrl";
import Link from '@docusaurus/Link';

Expand Down Expand Up @@ -39,4 +40,9 @@ Several calendars are supported: Gregorian, Islamic, Persian and Japanese.
You can use the [CalendarLegend](../CalendarLegend/CalendarLegend.mdx) component in addition to the Calendar to highlight specific days.
Discover all the available <Link to={useBaseUrl("/components/CalendarLegend#calendarlegenditem-types")}>CalendarLegendItem Types</Link>.

<CalendarWithLegend />
<CalendarWithLegend />


### Timezones
You can set to the configuration the preferred time zone, such as: Asia/Tokyo, Pacific/Apia, Asia/Kolkata, Europe/Sofia and etc.
<CalendarInDifferentTimezone />
7 changes: 6 additions & 1 deletion packages/website/docs/_components_pages/main/DatePicker.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Basic from "../../_samples/main/DatePicker/Basic/Basic.md";
import State from "../../_samples/main/DatePicker/State/State.md";
import MinMax from "../../_samples/main/DatePicker/MinMax/MinMax.md";
import CalendarTypes from "../../_samples/main/DatePicker/CalendarTypes/CalendarTypes.md";
import DatePickerInDifferentTimezone from "../../_samples/main/DatePicker/DatePickerInDifferentTimezone/DatePickerInDifferentTimezone.md";

<%COMPONENT_OVERVIEW%>

Expand All @@ -26,4 +27,8 @@ Define min and max date boundaries to contrain user choice.

### Calendar Types
You can set the preferred calendar, such as: Gregorian, Islamic, Japanese and Persian.
<CalendarTypes />
<CalendarTypes />

### Timezones
You can set to the configuration the preferred time zone, such as: Asia/Tokyo, Pacific/Apia, Asia/Kolkata, Europe/Sofia and etc.
<DatePickerInDifferentTimezone />
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ slug: ../DateTimePicker
import Basic from "../../_samples/main/DateTimePicker/Basic/Basic.md";
import FormatPattern from "../../_samples/main/DateTimePicker/FormatPattern/FormatPattern.md";
import MinMax from "../../_samples/main/DateTimePicker/MinMax/MinMax.md";
import DateTimePickerInDifferentTimezone from "../../_samples/main/DateTimePicker/DateTimePickerInDifferentTimezone/DateTimePickerInDifferentTimezone.md";

<%COMPONENT_OVERVIEW%>

Expand All @@ -23,4 +24,8 @@ For more information, see <a target="_blank" href="http://unicode.org/reports/tr
### Min and Max Dates
Define min and max date-time boundaries to contrain user choice.

<MinMax />
<MinMax />

### Timezones
You can set to the configuration the preferred time zone, such as: Asia/Tokyo, Pacific/Apia, Asia/Kolkata, Europe/Sofia and etc.
<DateTimePickerInDifferentTimezone />
Loading

0 comments on commit 1acae01

Please sign in to comment.