[FIX] web, hr_holidays: homogenize week numbering

Versions
--------
- 17.0
- 17.1
- master

Steps
-----
1. Go to Settings / Manage Languages;
2. select your current language;
3. set First Day of Week to something other than Sunday;
4. go to Time Off app.

Issue
-----
Weeks in year overview still start on a Sunday.

Cause
-----
Commit 52dae7a2f0 hardcoded `firstDay` to
Sunday for the `hr_holidays` module. This was a workaround to some
issues with `fullcalendar`'s week number calculations.

Solution
--------
Remove the hardcoded `firstDay`, and add a custom week numbering
function to be used on week, month, and year calendar views for
consistent numbering that allows for different first days of the week.

The function returns the ISO week number of the Monday nearest to the
configured first day of the week, i.e. the following Monday when first
day is set to Friday, Saturday or Sunday, the previous Monday if first
day is set to Tuesday, Wednesday or Thursday.

There were 3 main considerations for deciding a week numbering method:
1. no exisiting setting for users to decide on a method;
2. the ability to pick a first day of the week independent of locale;
3. the version of `luxon` used being unable to factor in locale.

Addendum
--------
This commit doesn't fix the issue with group-by week numbering in list
view. These stem from `babel`'s inconsistent locale defaults and
inability to take user-configured first day of the week into account.

opw-3668175

closes odoo/odoo#148623

Signed-off-by: Bruno Boi (boi) <boi@odoo.com>
This commit is contained in:
Levi Siuzdak (sile)
2024-02-13 12:21:50 +00:00
parent 8d63e49114
commit 02ea85a12f
6 changed files with 39 additions and 7 deletions
@@ -44,7 +44,6 @@ export class TimeOffCalendarYearRenderer extends CalendarYearRenderer {
weekNumbers: true,
weekNumbersWithinDays: false,
weekLabel: _t("Week"),
firstDay: 0,
});
}
@@ -9,6 +9,7 @@ import { getColor } from "../colors";
import { useCalendarPopover, useClickHandler, useFullCalendar } from "../hooks";
import { CalendarCommonPopover } from "./calendar_common_popover";
import { browser } from "@web/core/browser/browser";
import { getWeekNumber } from "../utils";
import { Component, onMounted, useEffect } from "@odoo/owl";
@@ -112,7 +113,7 @@ export class CalendarCommonRenderer extends Component {
unselectAuto: false,
weekLabel: this.props.model.scale === "month" && this.env.isSmall ? "" : _t("Week"),
weekends: this.props.isWeekendVisible,
weekNumberCalculation: "ISO",
weekNumberCalculation: (date) => getWeekNumber(date, this.props.model.firstDayOfWeek),
weekNumbers: true,
weekNumbersWithinDays: !this.env.isSmall,
windowResize: this.onWindowResizeDebounced,
@@ -19,6 +19,7 @@ import { useSearchBarToggler } from "@web/search/search_bar/search_bar_toggler";
import { ViewScaleSelector } from "@web/views/view_components/view_scale_selector";
import { CogMenu } from "@web/search/cog_menu/cog_menu";
import { browser } from "@web/core/browser/browser";
import { getWeekNumber } from "./utils";
import { Component, useState } from "@odoo/owl";
@@ -137,7 +138,7 @@ export class CalendarController extends Component {
}
get currentWeek() {
return this.date.toFormat("W");
return getWeekNumber(this.model.rangeStart);
}
get rendererProps() {
@@ -5,6 +5,7 @@ import { useDebounced } from "@web/core/utils/timing";
import { getColor } from "../colors";
import { useCalendarPopover, useFullCalendar } from "../hooks";
import { CalendarYearPopover } from "./calendar_year_popover";
import { getWeekNumber } from "../utils";
import { Component, useEffect, useRef } from "@odoo/owl";
@@ -58,7 +59,7 @@ export class CalendarYearRenderer extends Component {
timeZone: luxon.Settings.defaultZone.name,
titleFormat: { month: "long", year: "numeric" },
unselectAuto: false,
weekNumberCalculation: "ISO",
weekNumberCalculation: (date) => getWeekNumber(date, this.props.model.firstDayOfWeek),
weekNumbers: false,
windowResize: this.onWindowResizeDebounced,
};
@@ -12,3 +12,33 @@ export function getFormattedDateSpan(start, end) {
: start.toFormat("DDD") + " - " + end.toFormat("DDD");
}
}
/**
* Get the week number of a given date.
* Returns the ISO week number of the Monday nearest to the first day of the week.
*
* @param {Date | luxon.DateTime} date
* @param {number} first day of the week (optional)
* @returns {number} week number
*/
export function getWeekNumber(date, firstDay) {
if (!date.isLuxonDateTime) {
date = luxon.DateTime.fromJSDate(date);
}
if (Number.isInteger(firstDay)) { // go to start of week
date = date.minus({ days: (date.weekday + 7 - firstDay) % 7 });
} else {
firstDay = date.weekday;
}
// go to nearest Monday, up to 3 days back- or forwards
date = firstDay > 1 && firstDay < 5 // if firstDay after Mon & before Fri
? date.minus({ days: (date.weekday + 6) % 7 }) // then go back 1-3 days
: date.plus({ days: (8 - date.weekday) % 7 }); // else go forwards 0-3 days
date = date.plus({ days: 6 }); // go to last weekday of ISO week
const jan4 = luxon.DateTime.local(date.year, 1, 4);
// count from previous year if week falls before Jan 4
const diffDays = date < jan4
? date.diff(jan4.minus({ years: 1 }), 'day').days
: date.diff(jan4, 'day').days;
return Math.trunc(diffDays / 7) + 1;
}
@@ -1241,8 +1241,8 @@ QUnit.module("Views", ({ beforeEach }) => {
});
QUnit.test(`week numbering`, async (assert) => {
// The week is now calculated by FullCalendar (ISO week). If it's start a sunday it
// returns the week of the monday.
// Using ISO week calculation, get the ISO week number of
// the Monday nearest to the start of the week.
patchWithCleanup(localization, { weekStart: 7 });
@@ -4214,7 +4214,7 @@ QUnit.module("Views", ({ beforeEach }) => {
);
assert.strictEqual(
target.querySelector(".fc-head .fc-week-number").textContent,
"Week 36",
"Week 37",
"The number of the week should be correct"
);
});