Skip to content

Commit

Permalink
feat(ui5-calendar): implement calendar week numbering (#9694)
Browse files Browse the repository at this point in the history
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:

<ui5-calendar id="calendar10" calendar-week-numbering="WesternTraditional">
  <ui5-date value="Jan 1, 2023"></ui5-date-range>
</ui5-calendar>
This property will control the week numbering and the first day of the week in a way explained in the enumeration documentation.
  • Loading branch information
tsanislavgatev authored Aug 23, 2024
1 parent d8a1196 commit efc8053
Show file tree
Hide file tree
Showing 23 changed files with 267 additions and 63 deletions.
8 changes: 8 additions & 0 deletions packages/localization/src/CalendarUtils.ts
Original file line number Diff line number Diff line change
@@ -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;
55 changes: 0 additions & 55 deletions packages/localization/src/dates/calculateWeekNumber.ts

This file was deleted.

1 change: 1 addition & 0 deletions packages/main/src/Calendar.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
.selectionMode="{{selectionMode}}"
.minDate="{{minDate}}"
.maxDate="{{maxDate}}"
.calendarWeekNumbering="{{calendarWeekNumbering}}"
timestamp="{{_timestamp}}"
?hide-week-numbers="{{hideWeekNumbers}}"
@ui5-change="{{onSelectedDatesChange}}"
Expand Down
11 changes: 11 additions & 0 deletions packages/main/src/DateComponentBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;

/**
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/DatePickerPopover.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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}}"
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/DateRangePicker.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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}}"
Expand Down
1 change: 1 addition & 0 deletions packages/main/src/DateTimePickerPopover.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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}}"
Expand Down
20 changes: 15 additions & 5 deletions packages/main/src/DayPicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
});
}
Expand All @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
50 changes: 50 additions & 0 deletions packages/main/src/types/CalendarWeekNumbering.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* The <code>CalendarWeekNumbering</code> 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;
17 changes: 17 additions & 0 deletions packages/main/test/pages/Calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@
</ui5-calendar>
</section>

<section>
<ui5-title>Calendar with CalendarWeekNumbering ISO_8601</ui5-title>
<ui5-calendar id="calendar8" calendar-week-numbering="ISO_8601">
<ui5-date value="Jan 1, 2023"></ui5-date-range>
</ui5-calendar>

<ui5-title>Calendar with CalendarWeekNumbering MiddleEastern</ui5-title>
<ui5-calendar id="calendar9" calendar-week-numbering="MiddleEastern">
<ui5-date value="Jan 1, 2023"></ui5-date-range>
</ui5-calendar>

<ui5-title>Calendar with CalendarWeekNumbering WesternTraditional</ui5-title>
<ui5-calendar id="calendar10" calendar-week-numbering="WesternTraditional">
<ui5-date value="Jan 1, 2023"></ui5-date-range>
</ui5-calendar>
</section>

</body>

<script>
Expand Down
14 changes: 14 additions & 0 deletions packages/main/test/pages/DatePicker.html
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,20 @@ <h3>DatePicker with format `MMM y` should open picker on months</h3>
<h3>DatePicker with format `yyyy` should open picker on years</h3>
<ui5-date-picker id="dpCalendarModeYears" format-pattern="yyyy"></ui5-date-picker>

<section>
<ui5-title>DatePicker with CalendarWeekNumbering ISO_8601</ui5-title>
<ui5-date-picker id="datepicker8" calendar-week-numbering="ISO_8601">
</ui5-date-picker>

<ui5-title>DatePicker with CalendarWeekNumbering MiddleEastern</ui5-title>
<ui5-date-picker id="datepicker9" calendar-week-numbering="MiddleEastern">
</ui5-date-picker>

<ui5-title>DatePicker with CalendarWeekNumbering WesternTraditional</ui5-title>
<ui5-date-picker id="datepicker10" calendar-week-numbering="WesternTraditional">
</ui5-date-picker>
</section>

</div>
<script>
var dp = document.getElementById('dp5');
Expand Down
12 changes: 12 additions & 0 deletions packages/main/test/pages/DateRangePicker.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,19 @@ <h3>DateRange Picker with one date selected as first & last</h3>
<h3>DateRange Picker with no format pattern & min-max dates in ISO format</h3>
<ui5-daterange-picker id="daterange-picker8" min-date="2023-02-10" max-date="2023-07-22"></ui5-daterange-picker>

<section>
<ui5-title>DateRangePicker with CalendarWeekNumbering ISO_8601</ui5-title>
<ui5-daterange-picker id="daterangepicker8" calendar-week-numbering="ISO_8601">
</ui5-daterange-picker>

<ui5-title>DateRangePicker with CalendarWeekNumbering MiddleEastern</ui5-title>
<ui5-daterange-picker id="daterangepicker9" calendar-week-numbering="MiddleEastern">
</ui5-daterange-picker>

<ui5-title>DateRangePicker with CalendarWeekNumbering WesternTraditional</ui5-title>
<ui5-daterange-picker id="daterangepicker10" calendar-week-numbering="WesternTraditional">
</ui5-daterange-picker>
</section>
</div>
<script>
document.getElementById('daterange-picker1').addEventListener('ui5-change', function(e) {
Expand Down
14 changes: 14 additions & 0 deletions packages/main/test/pages/DateTimePicker.html
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,20 @@
></ui5-datetime-picker>
</section>

<section>
<ui5-title>DateTimePicker with CalendarWeekNumbering ISO_8601</ui5-title>
<ui5-datetime-picker id="datetimepicker8" calendar-week-numbering="ISO_8601">
</ui5-datetime-picker>

<ui5-title>DateTimePicker with CalendarWeekNumbering MiddleEastern</ui5-title>
<ui5-datetime-picker id="datetimepicker9" calendar-week-numbering="MiddleEastern">
</ui5-datetime-picker>

<ui5-title>DateTimePicker with CalendarWeekNumbering WesternTraditional</ui5-title>
<ui5-datetime-picker id="datetimepicker10" calendar-week-numbering="WesternTraditional">
</ui5-datetime-picker>
</section>

<script>
var counter = 0;
dt1.addEventListener("ui5-change", function(event) {
Expand Down
25 changes: 25 additions & 0 deletions packages/main/test/specs/Calendar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,12 @@ describe("Calendar general interaction", () => {
await browser.url(`test/pages/Calendar.html`);

const calendar = await browser.$("#calendar7");
const calendarHeader = await browser.$("#calendar7").shadow$(".ui5-calheader");
const dayPicker = await calendar.shadow$("ui5-daypicker");
const monthButton = await calendarHeader.shadow$(`[data-ui5-cal-header-btn-month]`);
monthButton.click();
const januaryCell = await calendar.shadow$("ui5-monthpicker").shadow$(`[data-sap-timestamp="1611100800"]`);
januaryCell.click();
const currentDayItem = await dayPicker.shadow$(`div[data-sap-timestamp="1611100800"]`);

assert.ok(await currentDayItem.isFocusedDeep(), "Current calendar day item is focused");
Expand All @@ -443,4 +448,24 @@ describe("Calendar general interaction", () => {
await yearButton.click();
assert.ok(yearButton.hasAttribute("hidden"), "The year button is hidden");
});

it("Check calendar week numbers with specific CalendarWeekNumbering configuration", async () => {
const calendarIsoFirstWeekNumber = await browser.$("#calendar8").shadow$("[ui5-daypicker]").shadow$(".ui5-dp-weekname");
const calendarHeaderMiddleEasternFirstWeekNumber = await browser.$("#calendar9").shadow$("[ui5-daypicker]").shadow$(".ui5-dp-weekname");
const calendarHeaderWesternTraditionalFirstWeekNumber = await browser.$("#calendar10").shadow$("[ui5-daypicker]").shadow$(".ui5-dp-weekname");

assert.strictEqual(await calendarIsoFirstWeekNumber.getText(), "52", "first week in ISO_8601 is correct");
assert.strictEqual(await calendarHeaderMiddleEasternFirstWeekNumber.getText(), "1", "first week in MiddleEastern is correct");
assert.strictEqual(await calendarHeaderWesternTraditionalFirstWeekNumber.getText(), "1", "first week in WesternTraditional is correct");
});

it("Check calendar week day names with specific CalendarWeekNumbering configuration", async () => {
const calendarIsoFirstWeekDayName = await browser.$("#calendar8").shadow$("[ui5-daypicker]").shadow$(".ui5-dp-firstday");
const calendarHeaderMiddleEasternFirstWeekDayName = await browser.$("#calendar9").shadow$("[ui5-daypicker]").shadow$(".ui5-dp-firstday");
const calendarHeaderWesternTraditionalFirstWeekDayName = await browser.$("#calendar10").shadow$("[ui5-daypicker]").shadow$(".ui5-dp-firstday");

assert.strictEqual(await calendarIsoFirstWeekDayName.getText(), "Mon", "first week in ISO_8601 is correct");
assert.strictEqual(await calendarHeaderMiddleEasternFirstWeekDayName.getText(), "Sat", "first week in MiddleEastern is correct");
assert.strictEqual(await calendarHeaderWesternTraditionalFirstWeekDayName.getText(), "Sun", "first week in WesternTraditional is correct");
});
});
2 changes: 1 addition & 1 deletion packages/main/test/specs/DatePicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -991,7 +991,7 @@ describe("Date Picker Tests", () => {
await datepicker.openPicker();

const data = Array.from(await datepicker.getDayPickerDatesRow(2));
assert.strictEqual(await data[0].getAttribute("aria-label"), "Calendar Week 18", "First columnheader have Week number aria-label");
assert.strictEqual(await data[0].getAttribute("aria-label"), "Calendar Week 19", "First columnheader have Week number aria-label");
assert.strictEqual(await data[1].getAttribute("aria-label"), "Non-Working Day May 2, 2100", "Each date have the full date's info in Month Date, Year in aria-label");
assert.strictEqual(await data[2].getAttribute("aria-label"), "May 3, 2100", "Each date have the full date's info in Month Date, Year in aria-label");
assert.strictEqual(await data[3].getAttribute("aria-label"), "May 4, 2100", "Each date have the full date's info in Month Date, Year in aria-label");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import CalendarTypes from "../../../_samples/main/Calendar/CalendarTypes/Calenda
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 CalendarWeekNumbering from "../../../_samples/main/Calendar/CalendarWeekNumbering/CalendarWeekNumbering.md";
import useBaseUrl from "@docusaurus/useBaseUrl";
import Link from '@docusaurus/Link';

Expand Down Expand Up @@ -45,4 +46,8 @@ Discover all the available <Link to={useBaseUrl("/components/CalendarLegend#cale

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

### CalendarWeekNumbering
You can use the component, the preferred week numbering and first day of week.
<CalendarWeekNumbering />
Loading

0 comments on commit efc8053

Please sign in to comment.