Skip to content

Commit c01572b

Browse files
authored
fix(calendar): Clamp date arithmetic rollovers (#1720)
1 parent 2febfa6 commit c01572b

File tree

4 files changed

+68
-17
lines changed

4 files changed

+68
-17
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## [Unreleased]
8+
### Fixed
9+
- #### Calendar & Date picker
10+
- Incorrect date rollover for in certain scenarios [#1710](https://github.com/IgniteUI/igniteui-webcomponents/issues/1710)
11+
712
## [6.0.1] - 2025-05-28
813
### Added
914
- #### Radio group

src/components/calendar/model.spec.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,20 +127,15 @@ describe('Calendar day model', () => {
127127
const nonLeapFebruary = leapFebruary.set({ year: 2023 });
128128
let { year, month, date } = nonLeapFebruary;
129129

130-
// Shift to first day of next month -> 2024/03/01
131-
expect([year, month, date]).to.eql([2023, 2, 1]);
132-
133-
const lastDayOfJuly = new CalendarDay({
134-
year: 2024,
135-
month: 6,
136-
date: 31,
137-
});
130+
// Shift to last day of the current month -> 2023-02-28
131+
expect([year, month, date]).to.eql([2023, 1, 28]);
138132

133+
const lastDayOfJuly = new CalendarDay({ year: 2024, month: 6, date: 31 });
139134
const lastDayOfApril = lastDayOfJuly.set({ month: 3 });
140135
({ year, month, date } = lastDayOfApril);
141136

142-
// April does not have 31 days so shift to first day of May
143-
expect([year, month, date]).to.eql([2024, 4, 1]);
137+
// April does not have 31 days so clamp to the last day of April
138+
expect([year, month, date]).to.eql([2024, 3, 30]);
144139
});
145140
});
146141

src/components/calendar/model.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function checkRollover(original: CalendarDay, modified: CalendarDay) {
2828

2929
/* blazorSuppress */
3030
export class CalendarDay {
31-
private _date!: Date;
31+
private readonly _date: Date;
3232

3333
/** Constructs and returns the current day. */
3434
public static get today() {
@@ -77,11 +77,21 @@ export class CalendarDay {
7777
* Returns a new instance with values replaced.
7878
*/
7979
public set(args: Partial<CalendarDayParams>) {
80-
return new CalendarDay({
81-
year: args.year ?? this.year,
82-
month: args.month ?? this.month,
83-
date: args.date ?? this.date,
84-
});
80+
const year = args.year ?? this.year;
81+
const month = args.month ?? this.month;
82+
const date = args.date ?? this.date;
83+
84+
const temp = new Date(year, month, date);
85+
86+
if (date > 0 && temp.getMonth() !== month) {
87+
return new CalendarDay({
88+
year,
89+
month,
90+
date: new Date(year, month + 1, 0).getDate(),
91+
});
92+
}
93+
94+
return new CalendarDay({ year, month, date });
8595
}
8696

8797
public add(unit: DayInterval, value: number) {

src/components/date-picker/date-picker.spec.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
22
import { spy } from 'sinon';
33
import IgcCalendarComponent from '../calendar/calendar.js';
4-
import { getCalendarDOM, getDayViewDOM } from '../calendar/helpers.spec.js';
4+
import {
5+
getCalendarDOM,
6+
getDOMDate,
7+
getDayViewDOM,
8+
} from '../calendar/helpers.spec.js';
59
import { CalendarDay, toCalendarDay } from '../calendar/model.js';
610
import { DateRangeType } from '../calendar/types.js';
711
import {
@@ -897,6 +901,43 @@ describe('Date picker', () => {
897901
expect(picker.open).to.be.true;
898902
});
899903

904+
it('issue 1710', async () => {
905+
const activeDate = new CalendarDay({ year: 2025, month: 4, date: 29 });
906+
const targetDate = activeDate.add('day', 2);
907+
908+
// Select the last date of May
909+
picker.activeDate = activeDate.native;
910+
await picker.show();
911+
912+
const calendarDOM = getCalendarDOM(calendar);
913+
const lastOfMay = getDOMDate(targetDate, calendarDOM.views.days);
914+
915+
simulateClick(lastOfMay);
916+
await elementUpdated(picker);
917+
918+
expect(checkDatesEqual(picker.value!, targetDate));
919+
920+
// Open the picker and switch to months view
921+
await picker.show();
922+
923+
simulateClick(calendarDOM.navigation.months);
924+
await elementUpdated(picker);
925+
926+
const monthElements = Array.from(
927+
calendarDOM.views.months.renderRoot.querySelectorAll<HTMLElement>(
928+
'[role="gridcell"]'
929+
)
930+
);
931+
932+
const monthNames = new Set(monthElements.map((each) => each.innerText));
933+
const selectedMonths = monthElements.filter((each) =>
934+
each.matches('[aria-selected="true"]')
935+
);
936+
937+
expect(monthNames).lengthOf(12);
938+
expect(selectedMonths).lengthOf(1);
939+
});
940+
900941
describe('Readonly state', () => {
901942
describe('Dropdown mode', () => {
902943
beforeEach(async () => {

0 commit comments

Comments
 (0)