From efc8053e756156cca5e239f1886469c6b92c2cf7 Mon Sep 17 00:00:00 2001 From: Tsanislav Gatev Date: Fri, 23 Aug 2024 16:38:09 +0300 Subject: [PATCH] feat(ui5-calendar): implement calendar week numbering (#9694) With this feature you can select one of four options to sete to the Calendar, DatePicker, DateTimePicker, DateRangePicker components. Default -Determined by active format locale ISO_8601 - Official calendar week numbering in most of Europe (ISO 8601standard) WesternTraditional -Official calendar week numbering in the United States, Canada, Brazil, Israel, Japan, and other countries MiddleEastern - Official calendar week numbering in much of the Middle East It can be used as: This property will control the week numbering and the first day of the week in a way explained in the enumeration documentation. --- packages/localization/src/CalendarUtils.ts | 8 +++ .../src/dates/calculateWeekNumber.ts | 55 ------------------- packages/main/src/Calendar.hbs | 1 + packages/main/src/DateComponentBase.ts | 11 ++++ packages/main/src/DatePickerPopover.hbs | 1 + packages/main/src/DateRangePicker.hbs | 1 + packages/main/src/DateTimePickerPopover.hbs | 1 + packages/main/src/DayPicker.ts | 20 +++++-- .../main/src/types/CalendarWeekNumbering.ts | 50 +++++++++++++++++ packages/main/test/pages/Calendar.html | 17 ++++++ packages/main/test/pages/DatePicker.html | 14 +++++ packages/main/test/pages/DateRangePicker.html | 12 ++++ packages/main/test/pages/DateTimePicker.html | 14 +++++ packages/main/test/specs/Calendar.spec.js | 25 +++++++++ packages/main/test/specs/DatePicker.spec.js | 2 +- .../main/Calendar/Calendar.mdx | 7 ++- .../_components_pages/main/DatePicker.mdx | 7 ++- .../CalendarWeekNumbering.md | 4 ++ .../Calendar/CalendarWeekNumbering/main.js | 7 +++ .../CalendarWeekNumbering/sample.html | 31 +++++++++++ .../CalendarWeekNumbering.md | 4 ++ .../DatePicker/CalendarWeekNumbering/main.js | 7 +++ .../CalendarWeekNumbering/sample.html | 31 +++++++++++ 23 files changed, 267 insertions(+), 63 deletions(-) create mode 100644 packages/localization/src/CalendarUtils.ts delete mode 100644 packages/localization/src/dates/calculateWeekNumber.ts create mode 100644 packages/main/src/types/CalendarWeekNumbering.ts create mode 100644 packages/website/docs/_samples/main/Calendar/CalendarWeekNumbering/CalendarWeekNumbering.md create mode 100644 packages/website/docs/_samples/main/Calendar/CalendarWeekNumbering/main.js create mode 100644 packages/website/docs/_samples/main/Calendar/CalendarWeekNumbering/sample.html create mode 100644 packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/CalendarWeekNumbering.md create mode 100644 packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/main.js create mode 100644 packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/sample.html diff --git a/packages/localization/src/CalendarUtils.ts b/packages/localization/src/CalendarUtils.ts new file mode 100644 index 000000000000..455a75abaecc --- /dev/null +++ b/packages/localization/src/CalendarUtils.ts @@ -0,0 +1,8 @@ +import type CaledndarUtilsOpenui5T from "sap/ui/core/date/CalendarUtils"; +// @ts-ignore +import CalendarUtilsNative from "./sap/ui/core/date/CalendarUtils.js"; + +const CalendarUtilsWrapped = CalendarUtilsNative as typeof CaledndarUtilsOpenui5T; +const CalendarUtils = CalendarUtilsWrapped; + +export default CalendarUtils; diff --git a/packages/localization/src/dates/calculateWeekNumber.ts b/packages/localization/src/dates/calculateWeekNumber.ts deleted file mode 100644 index 6d98e89d8a61..000000000000 --- a/packages/localization/src/dates/calculateWeekNumber.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type Locale from "@ui5/webcomponents-base/dist/locale/Locale.js"; -import type CalendarType from "@ui5/webcomponents-base/dist/types/CalendarType.js"; -import UniversalDate from "./UniversalDate.js"; -import type LocaleData from "../LocaleData.js"; -import type UI5Date from "./UI5Date.js"; - -const calculateWeekNumber = (confFirstDayOfWeek: number | undefined, oDate: Date | UI5Date, iYear: number, oLocale: Locale, oLocaleData: LocaleData, calendarType: CalendarType) => { - let iWeekNum = 0; - let iWeekDay = 0; - const iFirstDayOfWeek = Number.isInteger(confFirstDayOfWeek) ? confFirstDayOfWeek! : oLocaleData.getFirstDayOfWeek(); - - // search Locale for containing "en-US", since sometimes - // when any user settings have been defined, subtag "sapufmt" is added to the locale name - // this is described inside sap.ui.core.Configuration file - if (oLocale && (oLocale.getLanguage() === "en" && oLocale.getRegion() === "US")) { - /* - * in US the week starts with Sunday - * The first week of the year starts with January 1st. But Dec. 31 is still in the last year - * So the week beginning in December and ending in January has 2 week numbers - */ - const oJanFirst = UniversalDate.getInstance(oDate, calendarType); - oJanFirst.setUTCFullYear(iYear, 0, 1); - iWeekDay = oJanFirst.getUTCDay(); - - // get the date for the same weekday like jan 1. - const oCheckDate = UniversalDate.getInstance(oDate, calendarType); - oCheckDate.setUTCDate(oCheckDate.getUTCDate() - oCheckDate.getUTCDay() + iWeekDay); - - iWeekNum = Math.round((oCheckDate.getTime() - oJanFirst.getTime()) / 86400000 / 7) + 1; - } else { - // normally the first week of the year is the one where the first Thursday of the year is - // find Thursday of this week - // if the checked day is before the 1. day of the week use a day of the previous week to check - const oThursday = UniversalDate.getInstance(oDate, calendarType); - oThursday.setUTCDate(oThursday.getUTCDate() - iFirstDayOfWeek); - iWeekDay = oThursday.getUTCDay(); - oThursday.setUTCDate(oThursday.getUTCDate() - iWeekDay + 4); - - const oFirstDayOfYear = UniversalDate.getInstance(new Date(oThursday.getTime()), calendarType); - oFirstDayOfYear.setUTCMonth(0, 1); - iWeekDay = oFirstDayOfYear.getUTCDay(); - let iAddDays = 0; - if (iWeekDay > 4) { - iAddDays = 7; // first day of year is after Thursday, so first Thursday is in the next week - } - const oFirstThursday = UniversalDate.getInstance(new Date(oFirstDayOfYear.getTime()), calendarType); - oFirstThursday.setUTCDate(1 - iWeekDay + 4 + iAddDays); - - iWeekNum = Math.round((oThursday.getTime() - oFirstThursday.getTime()) / 86400000 / 7) + 1; - } - - return iWeekNum; -}; - -export default calculateWeekNumber; diff --git a/packages/main/src/Calendar.hbs b/packages/main/src/Calendar.hbs index 2e370b3f3b9b..e7ab7af3b6c8 100644 --- a/packages/main/src/Calendar.hbs +++ b/packages/main/src/Calendar.hbs @@ -15,6 +15,7 @@ .selectionMode="{{selectionMode}}" .minDate="{{minDate}}" .maxDate="{{maxDate}}" + .calendarWeekNumbering="{{calendarWeekNumbering}}" timestamp="{{_timestamp}}" ?hide-week-numbers="{{hideWeekNumbers}}" @ui5-change="{{onSelectedDatesChange}}" diff --git a/packages/main/src/DateComponentBase.ts b/packages/main/src/DateComponentBase.ts index ef16ecfc0d12..627486e399bf 100644 --- a/packages/main/src/DateComponentBase.ts +++ b/packages/main/src/DateComponentBase.ts @@ -13,6 +13,7 @@ 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"; +import type CalendarWeekNumbering from "./types/CalendarWeekNumbering.js"; /** * @class @@ -81,6 +82,16 @@ class DateComponentBase extends UI5Element { @property() maxDate = ""; + /** + * Defines how to calculate calendar weeks and first day of the week. + * If not set, the calendar will be displayed according to the currently set global configuration. + * @default "Default" + * @since 2.2.0 + * @public + */ + @property() + calendarWeekNumbering: `${CalendarWeekNumbering}` = "Default"; + static i18nBundle?: I18nBundle; /** diff --git a/packages/main/src/DatePickerPopover.hbs b/packages/main/src/DatePickerPopover.hbs index f4b6fe9e76be..0f9b624168c8 100644 --- a/packages/main/src/DatePickerPopover.hbs +++ b/packages/main/src/DatePickerPopover.hbs @@ -47,6 +47,7 @@ .selectionMode="{{_calendarSelectionMode}}" .minDate="{{minDate}}" .maxDate="{{maxDate}}" + .calendarWeekNumbering="{{calendarWeekNumbering}}" @ui5-selection-change="{{onSelectedDatesChange}}" @ui5-show-month-view="{{onHeaderShowMonthPress}}" @ui5-show-year-view="{{onHeaderShowYearPress}}" diff --git a/packages/main/src/DateRangePicker.hbs b/packages/main/src/DateRangePicker.hbs index 46562a1fcf89..d1b22957b158 100644 --- a/packages/main/src/DateRangePicker.hbs +++ b/packages/main/src/DateRangePicker.hbs @@ -10,6 +10,7 @@ .selectionMode="{{_calendarSelectionMode}}" .minDate="{{minDate}}" .maxDate="{{maxDate}}" + .calendarWeekNumbering="{{calendarWeekNumbering}}" @ui5-selection-change="{{onSelectedDatesChange}}" @ui5-show-month-view="{{onHeaderShowMonthPress}}" @ui5-show-year-view="{{onHeaderShowYearPress}}" diff --git a/packages/main/src/DateTimePickerPopover.hbs b/packages/main/src/DateTimePickerPopover.hbs index c3a63d7b17a7..b78b6a9837b4 100644 --- a/packages/main/src/DateTimePickerPopover.hbs +++ b/packages/main/src/DateTimePickerPopover.hbs @@ -23,6 +23,7 @@ .selectionMode="{{_calendarSelectionMode}}" .minDate="{{minDate}}" .maxDate="{{maxDate}}" + .calendarWeekNumbering="{{calendarWeekNumbering}}" @ui5-selection-change="{{onSelectedDatesChange}}" @ui5-show-month-view="{{onHeaderShowMonthPress}}" @ui5-show-year-view="{{onHeaderShowYearPress}}" diff --git a/packages/main/src/DayPicker.ts b/packages/main/src/DayPicker.ts index f532e69ade7d..aa36aab519f1 100644 --- a/packages/main/src/DayPicker.ts +++ b/packages/main/src/DayPicker.ts @@ -3,7 +3,6 @@ import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import event from "@ui5/webcomponents-base/dist/decorators/event.js"; import getLocale from "@ui5/webcomponents-base/dist/locale/getLocale.js"; import type LocaleData from "@ui5/webcomponents-localization/dist/LocaleData.js"; -import { getFirstDayOfWeek } from "@ui5/webcomponents-base/dist/config/FormatSettings.js"; import getCachedLocaleDataInstance from "@ui5/webcomponents-localization/dist/getCachedLocaleDataInstance.js"; import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; import { @@ -29,9 +28,10 @@ import { isPageDownShiftCtrl, } from "@ui5/webcomponents-base/dist/Keys.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 CalendarUtils from "@ui5/webcomponents-localization/dist/CalendarUtils.js"; +import DateFormat from "@ui5/webcomponents-localization/dist/DateFormat.js"; import CalendarSelectionMode from "./types/CalendarSelectionMode.js"; import CalendarPart from "./CalendarPart.js"; import type { @@ -304,8 +304,10 @@ class DayPicker extends CalendarPart implements ICalendarPicker { week.push(day); if (dayOfTheWeek === DAYS_IN_WEEK - 1) { // 0-indexed so 6 is the last day of the week + const weekNumber = this._calculateWeekNumber(tempDate.toLocalJSDate()); + week.unshift({ - weekNum: calculateWeekNumber(getFirstDayOfWeek(), tempDate.toUTCJSDate(), tempDate.getYear(), getLocale(), localeData, this._primaryCalendarType as CalendarType), + weekNum: weekNumber, isHidden: this.shouldHideWeekNumbers, }); } @@ -322,6 +324,13 @@ class DayPicker extends CalendarPart implements ICalendarPicker { } } + _calculateWeekNumber(date: Date): number { + const oDateFormat = DateFormat.getDateInstance({ pattern: "w", calendarType: this.primaryCalendarType, calendarWeekNumbering: this.calendarWeekNumbering }); + const weekNumber = oDateFormat.format(date); + + return Number(weekNumber); + } + /** * Builds the dayNames object (header of the month). * @param localeData @@ -793,9 +802,10 @@ class DayPicker extends CalendarPart implements ICalendarPicker { } _getFirstDayOfWeek(): number { + const result = CalendarUtils.getWeekConfigurationValues(this.calendarWeekNumbering); + const localeData = getCachedLocaleDataInstance(getLocale()); - const confFirstDayOfWeek = getFirstDayOfWeek(); - return Number.isInteger(confFirstDayOfWeek) ? confFirstDayOfWeek! : localeData.getFirstDayOfWeek(); + return result?.firstDayOfWeek ? result.firstDayOfWeek : localeData.getFirstDayOfWeek(); } get styles() { diff --git a/packages/main/src/types/CalendarWeekNumbering.ts b/packages/main/src/types/CalendarWeekNumbering.ts new file mode 100644 index 000000000000..7b8ea696811c --- /dev/null +++ b/packages/main/src/types/CalendarWeekNumbering.ts @@ -0,0 +1,50 @@ +/** + * The CalendarWeekNumbering enum defines how to calculate calendar weeks. Each + * value defines: + * - The first day of the week, + * - The first week of the year. + * + * @public + * @since 2.2.0 + */ + +enum CalendarWeekNumbering { + + /** + * The default calendar week numbering: + * + * The framework determines the week numbering scheme; currently it is derived from the + * active format locale. Future versions of ui5-webcomponents might select a different week numbering + * scheme. + * + * @public + */ + Default = "Default", + + /** + * Official calendar week numbering in most of Europe (ISO 8601 standard): + * Monday is first day of the week, the week containing January 4th is first week of the year. + * + * @public + */ + ISO_8601 = "ISO_8601", + + /** + * Official calendar week numbering in much of the Middle East (Middle Eastern calendar): + * Saturday is first day of the week, the week containing January 1st is first week of the year. + * + * @public + */ + MiddleEastern = "MiddleEastern", + + /** + * Official calendar week numbering in the United States, Canada, Brazil, Israel, Japan, and + * other countries (Western traditional calendar): + * Sunday is first day of the week, the week containing January 1st is first week of the year. + * + * @public + */ + WesternTraditional = "WesternTraditional", +} + +export default CalendarWeekNumbering; diff --git a/packages/main/test/pages/Calendar.html b/packages/main/test/pages/Calendar.html index d76332b2c57d..3849394025ed 100644 --- a/packages/main/test/pages/Calendar.html +++ b/packages/main/test/pages/Calendar.html @@ -86,6 +86,23 @@ +
+ Calendar with CalendarWeekNumbering ISO_8601 + + + + + Calendar with CalendarWeekNumbering MiddleEastern + + + + + Calendar with CalendarWeekNumbering WesternTraditional + + + +
+ + + + + + + + ISO_8601 + MiddleEastern + WesternTraditional + + + + + + + + diff --git a/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/CalendarWeekNumbering.md b/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/CalendarWeekNumbering.md new file mode 100644 index 000000000000..17798ecc59ab --- /dev/null +++ b/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/CalendarWeekNumbering.md @@ -0,0 +1,4 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; + + diff --git a/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/main.js b/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/main.js new file mode 100644 index 000000000000..82c635b63a14 --- /dev/null +++ b/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/main.js @@ -0,0 +1,7 @@ +import "@ui5/webcomponents/dist/DatePicker.js"; +import "@ui5/webcomponents/dist/Select.js"; +import "@ui5/webcomponents/dist/Option.js"; + +sel.addEventListener("change", async (e) => { + dp.calendarWeekNumbering = e.detail.selectedOption.getAttribute("data-calendar-week-numbering"); +}); \ No newline at end of file diff --git a/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/sample.html b/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/sample.html new file mode 100644 index 000000000000..ecaae6440e2d --- /dev/null +++ b/packages/website/docs/_samples/main/DatePicker/CalendarWeekNumbering/sample.html @@ -0,0 +1,31 @@ + + + + + + + + DatePicker with a selection of calendarWeekNumbering + + + + + + + + + ISO_8601 + MiddleEastern + WesternTraditional + + + + + + + +