fix(calendar): mark today and past days from the Europe/Paris date
CI / policy (pull_request) Successful in 8s
CI / validate (pull_request) Successful in 31s
CI / build (pull_request) Successful in 29s
CI / benchmark (pull_request) Successful in 1m34s
CI / security (pull_request) Successful in 1m3s
CI / test (pull_request) Successful in 2m9s
CI / images (pull_request) Successful in 1m10s
CI / required (pull_request) Successful in 1s
CI / policy (push) Successful in 8s
CI / validate (push) Successful in 36s
CI / build (push) Successful in 34s
CI / benchmark (push) Successful in 1m39s
CI / security (push) Successful in 1m8s
CI / test (push) Successful in 2m20s
CI / images (push) Successful in 1m1s
CI / required (push) Successful in 1s
CI / policy (pull_request) Successful in 8s
CI / validate (pull_request) Successful in 31s
CI / build (pull_request) Successful in 29s
CI / benchmark (pull_request) Successful in 1m34s
CI / security (pull_request) Successful in 1m3s
CI / test (pull_request) Successful in 2m9s
CI / images (pull_request) Successful in 1m10s
CI / required (pull_request) Successful in 1s
CI / policy (push) Successful in 8s
CI / validate (push) Successful in 36s
CI / build (push) Successful in 34s
CI / benchmark (push) Successful in 1m39s
CI / security (push) Successful in 1m8s
CI / test (push) Successful in 2m20s
CI / images (push) Successful in 1m1s
CI / required (push) Successful in 1s
The today marker and past styling used the device's local date while the default planning horizon starts on the Paris date, so outside Paris time near midnight the horizon's first day was not marked as today (CI in UTC failed nightly). project() now takes an injectable now, derives today once with planningDateInParis and returns it; calendar-month styles a day as past when its date key is before that today. Delete DateCacheService, monthsInAYearFromNow() and the default year of getHolidays(), whose only remaining callers were their own specs. Refs #301 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #312.
This commit is contained in:
1 parent
d089c4670b
commit
ff6c4d8122
8 files changed
+61
-217
No files matched your search
@@ -34,7 +34,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
[attr.data-date]="dateKey"
|
[attr.data-date]="dateKey"
|
||||||
[class.past]="day < getNow()"
|
[class.past]="dateKey < today"
|
||||||
[class.start]="selectedDates.isStart(day)"
|
[class.start]="selectedDates.isStart(day)"
|
||||||
[class.end]="selectedDates.isEnd(day)"
|
[class.end]="selectedDates.isEnd(day)"
|
||||||
[class.selected]="selectedDates.isSelected(day)"
|
[class.selected]="selectedDates.isSelected(day)"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { provideZonelessChangeDetection } from '@angular/core';
|
import { provideZonelessChangeDetection } from '@angular/core';
|
||||||
|
import { SelectedDates } from '../../services/calendar-service';
|
||||||
import { CalendarMonth } from './calendar-month';
|
import { CalendarMonth } from './calendar-month';
|
||||||
|
|
||||||
describe('CalendarMonth', () => {
|
describe('CalendarMonth', () => {
|
||||||
@@ -20,4 +21,20 @@ describe('CalendarMonth', () => {
|
|||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('styles only the dates before the given today as past', () => {
|
||||||
|
const monthFixture = TestBed.createComponent(CalendarMonth);
|
||||||
|
monthFixture.componentRef.setInput('monthIndex', 9);
|
||||||
|
monthFixture.componentRef.setInput('year', 2026);
|
||||||
|
monthFixture.componentRef.setInput('selectedDates', new SelectedDates());
|
||||||
|
monthFixture.componentRef.setInput('today', '2026-10-18');
|
||||||
|
monthFixture.detectChanges();
|
||||||
|
const button = (date: string) =>
|
||||||
|
(monthFixture.nativeElement as HTMLElement).querySelector(
|
||||||
|
`button[data-date="${date}"]`,
|
||||||
|
)!;
|
||||||
|
expect(button('2026-10-17').classList).toContain('past');
|
||||||
|
expect(button('2026-10-18').classList).not.toContain('past');
|
||||||
|
expect(button('2026-10-19').classList).not.toContain('past');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
import { CalendarService, Month, SelectedDates } from '../../services/calendar-service';
|
import { CalendarService, Month, SelectedDates } from '../../services/calendar-service';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { CalendarSettingsService } from '../../services/calendar-settings-service';
|
import { CalendarSettingsService } from '../../services/calendar-settings-service';
|
||||||
import { DateCacheService } from '../../services/date-cache.service';
|
|
||||||
import { TranslationService } from '../../services/translation-service';
|
import { TranslationService } from '../../services/translation-service';
|
||||||
import { isWorkingPlanningDate } from '../../services/declared-non-working-days';
|
import { isWorkingPlanningDate } from '../../services/declared-non-working-days';
|
||||||
import type { LeaveType } from '../../services/leave-planner.service';
|
import type { LeaveType } from '../../services/leave-planner.service';
|
||||||
@@ -29,7 +28,6 @@ export interface DeclarationPreview {
|
|||||||
})
|
})
|
||||||
export class CalendarMonth implements OnInit {
|
export class CalendarMonth implements OnInit {
|
||||||
protected calendarSettingsService = inject(CalendarSettingsService);
|
protected calendarSettingsService = inject(CalendarSettingsService);
|
||||||
private dateCache = inject(DateCacheService);
|
|
||||||
protected translationService = inject(TranslationService);
|
protected translationService = inject(TranslationService);
|
||||||
|
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
@@ -42,11 +40,6 @@ export class CalendarMonth implements OnInit {
|
|||||||
return this.translationService.tr;
|
return this.translationService.tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use DateCacheService instead of creating Date objects
|
|
||||||
getNow(): Date {
|
|
||||||
return this.dateCache.getNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentMonth: Date = new Date();
|
currentMonth: Date = new Date();
|
||||||
|
|
||||||
weeksInAMonth: Month = [];
|
weeksInAMonth: Month = [];
|
||||||
@@ -56,6 +49,9 @@ export class CalendarMonth implements OnInit {
|
|||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
selectedDates!: SelectedDates;
|
selectedDates!: SelectedDates;
|
||||||
|
|
||||||
|
@Input({ required: true })
|
||||||
|
today!: string;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
horizonStart?: string;
|
horizonStart?: string;
|
||||||
|
|
||||||
|
|||||||
@@ -608,6 +608,7 @@
|
|||||||
[monthIndex]="month.monthIndex"
|
[monthIndex]="month.monthIndex"
|
||||||
[year]="month.year"
|
[year]="month.year"
|
||||||
[selectedDates]="calendarSelectedDates()"
|
[selectedDates]="calendarSelectedDates()"
|
||||||
|
[today]="calendarProjection()?.today ?? ''"
|
||||||
[horizonStart]="calendarHorizon()?.start"
|
[horizonStart]="calendarHorizon()?.start"
|
||||||
[horizonEnd]="calendarHorizon()?.end"
|
[horizonEnd]="calendarHorizon()?.end"
|
||||||
[declarationEntry]="declarationEntry()"
|
[declarationEntry]="declarationEntry()"
|
||||||
|
|||||||
@@ -150,14 +150,14 @@ describe('CalendarService', () => {
|
|||||||
|
|
||||||
describe('getHolidays', () => {
|
describe('getHolidays', () => {
|
||||||
it('should return an array of holidays', () => {
|
it('should return an array of holidays', () => {
|
||||||
const holidays = service.getHolidays();
|
const holidays = service.getHolidays(2026);
|
||||||
expect(Array.isArray(holidays)).toBe(true);
|
expect(Array.isArray(holidays)).toBe(true);
|
||||||
expect(holidays.length).toBeGreaterThan(0);
|
expect(holidays.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cache holidays for the same year', () => {
|
it('should cache holidays for the same year', () => {
|
||||||
const holidays1 = service.getHolidays();
|
const holidays1 = service.getHolidays(2026);
|
||||||
const holidays2 = service.getHolidays();
|
const holidays2 = service.getHolidays(2026);
|
||||||
expect(holidays1).toEqual(holidays2);
|
expect(holidays1).toEqual(holidays2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -179,11 +179,6 @@ describe('CalendarService', () => {
|
|||||||
expect(months.length).toBe(12);
|
expect(months.length).toBe(12);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should calculate months from now', () => {
|
|
||||||
const months = service.monthsInAYearFromNow();
|
|
||||||
expect(months.length).toBe(12);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return week number for a date', () => {
|
it('should return week number for a date', () => {
|
||||||
const date = new Date(2024, 0, 1);
|
const date = new Date(2024, 0, 1);
|
||||||
const weekNumber = CalendarService.getWeekNumber(date);
|
const weekNumber = CalendarService.getWeekNumber(date);
|
||||||
@@ -193,20 +188,44 @@ describe('CalendarService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('project', () => {
|
describe('project', () => {
|
||||||
const project = (horizon: { start: string; end: string }) =>
|
const project = (
|
||||||
service.project({ placements: [], horizon });
|
horizon: { start: string; end: string },
|
||||||
|
options?: Parameters<typeof service.project>[1],
|
||||||
|
) => service.project({ placements: [], horizon }, options);
|
||||||
const day = (key: string) => new Date(`${key}T00:00:00`);
|
const day = (key: string) => new Date(`${key}T00:00:00`);
|
||||||
|
|
||||||
it('projects weekends, holidays and today for the default planning horizon', () => {
|
it('projects weekends, holidays and today for the default planning horizon', () => {
|
||||||
const { selections } = project({ start: '2026-01-01', end: '2027-01-01' });
|
const { selections } = project({ start: '2026-01-01', end: '2027-01-01' });
|
||||||
expect(selections.dayTypes(day('2026-05-09'))).toContain(DayType.WEEKEND);
|
expect(selections.dayTypes(day('2026-05-09'))).toContain(DayType.WEEKEND);
|
||||||
expect(selections.dayTypes(day('2026-05-01'))).toContain(DayType.CLOSED_DAY);
|
expect(selections.dayTypes(day('2026-05-01'))).toContain(DayType.CLOSED_DAY);
|
||||||
const horizon = service.getDefaultPlanningHorizon();
|
const now = new Date();
|
||||||
expect(project(horizon).selections.getClasses(day(horizon.start))).toContain(
|
const horizon = service.getDefaultPlanningHorizon(now);
|
||||||
|
expect(project(horizon, { now }).selections.getClasses(day(horizon.start))).toContain(
|
||||||
DayType.TODAY,
|
DayType.TODAY,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('marks the Europe/Paris date as today when the device is in UTC', () => {
|
||||||
|
const previousTimeZone = process.env['TZ'];
|
||||||
|
process.env['TZ'] = 'UTC';
|
||||||
|
try {
|
||||||
|
const now = new Date('2026-10-17T22:16:00Z');
|
||||||
|
const horizon = service.getDefaultPlanningHorizon(now);
|
||||||
|
expect(horizon.start).toBe('2026-10-18');
|
||||||
|
const projection = project(horizon, { now });
|
||||||
|
expect(projection.today).toBe('2026-10-18');
|
||||||
|
expect(projection.selections.getClasses(day('2026-10-18'))).toContain(
|
||||||
|
DayType.TODAY,
|
||||||
|
);
|
||||||
|
expect(projection.selections.getClasses(day('2026-10-17'))).not.toContain(
|
||||||
|
DayType.TODAY,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (previousTimeZone === undefined) delete process.env['TZ'];
|
||||||
|
else process.env['TZ'] = previousTimeZone;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('lists the covered months, including partial boundary months and year changes', () => {
|
it('lists the covered months, including partial boundary months and year changes', () => {
|
||||||
expect(project({ start: '2024-11-20', end: '2025-02-02' }).months).toEqual([
|
expect(project({ start: '2024-11-20', end: '2025-02-02' }).months).toEqual([
|
||||||
{ monthIndex: 10, year: 2024 },
|
{ monthIndex: 10, year: 2024 },
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { DateRange } from '@angular/material/datepicker';
|
import { DateRange } from '@angular/material/datepicker';
|
||||||
import { frenchPublicHolidaysForYear, isWorkingPlanningDate } from './declared-non-working-days';
|
import { frenchPublicHolidaysForYear, isWorkingPlanningDate } from './declared-non-working-days';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
import weekOfYear from 'dayjs/plugin/weekOfYear';
|
||||||
import isoWeek from 'dayjs/plugin/isoWeek';
|
import isoWeek from 'dayjs/plugin/isoWeek';
|
||||||
import { DateCacheService } from './date-cache.service';
|
|
||||||
import {
|
import {
|
||||||
createPlanningCalculation as createPlanningCalculationOperation,
|
createPlanningCalculation as createPlanningCalculationOperation,
|
||||||
planLeave as planLeaveOperation,
|
planLeave as planLeaveOperation,
|
||||||
@@ -55,14 +54,13 @@ export function calendarProjectionHorizon(plan: CalendarPlan) {
|
|||||||
export interface CalendarProjection {
|
export interface CalendarProjection {
|
||||||
months: { monthIndex: number; year: number }[];
|
months: { monthIndex: number; year: number }[];
|
||||||
selections: SelectedDates;
|
selections: SelectedDates;
|
||||||
|
today: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class CalendarService {
|
export class CalendarService {
|
||||||
private dateCache = inject(DateCacheService);
|
|
||||||
|
|
||||||
readonly planLeave = planLeaveOperation;
|
readonly planLeave = planLeaveOperation;
|
||||||
readonly createPlanningCalculation = createPlanningCalculationOperation;
|
readonly createPlanningCalculation = createPlanningCalculationOperation;
|
||||||
|
|
||||||
@@ -74,7 +72,7 @@ export class CalendarService {
|
|||||||
/** `fixedMonths` lists only the plan horizon's months, so editing declarations never adds a month. */
|
/** `fixedMonths` lists only the plan horizon's months, so editing declarations never adds a month. */
|
||||||
project(
|
project(
|
||||||
plan: CalendarPlan & { horizon: PlanningStateSnapshot['horizon'] },
|
plan: CalendarPlan & { horizon: PlanningStateSnapshot['horizon'] },
|
||||||
{ fixedMonths = false } = {},
|
{ fixedMonths = false, now = new Date() } = {},
|
||||||
): CalendarProjection {
|
): CalendarProjection {
|
||||||
const horizon = calendarProjectionHorizon(plan)!;
|
const horizon = calendarProjectionHorizon(plan)!;
|
||||||
const firstMonth = plan.horizon.start.slice(0, 7);
|
const firstMonth = plan.horizon.start.slice(0, 7);
|
||||||
@@ -85,11 +83,8 @@ export class CalendarService {
|
|||||||
const holidays = new Set(
|
const holidays = new Set(
|
||||||
this.getHolidays(first.getFullYear(), last.getFullYear()).map((h) => h.getTime()),
|
this.getHolidays(first.getFullYear(), last.getFullYear()).map((h) => h.getTime()),
|
||||||
);
|
);
|
||||||
const {
|
const today = planningDateInParis(now);
|
||||||
date: todayDate,
|
const todayTime = new Date(`${today}T00:00:00`).getTime();
|
||||||
month: todayMonth,
|
|
||||||
year: todayYear,
|
|
||||||
} = this.dateCache.getTodayComponents();
|
|
||||||
const months: CalendarProjection['months'] = [];
|
const months: CalendarProjection['months'] = [];
|
||||||
const ranges: SelectedDate[] = [];
|
const ranges: SelectedDate[] = [];
|
||||||
const cursor = new Date(first);
|
const cursor = new Date(first);
|
||||||
@@ -122,11 +117,7 @@ export class CalendarService {
|
|||||||
ranges.push(
|
ranges.push(
|
||||||
new SelectedDate(DayType.CLOSED_DAY, new DateRange<Date>(day, day)),
|
new SelectedDate(DayType.CLOSED_DAY, new DateRange<Date>(day, day)),
|
||||||
);
|
);
|
||||||
if (
|
if (day.getTime() === todayTime)
|
||||||
day.getDate() === todayDate &&
|
|
||||||
day.getMonth() === todayMonth &&
|
|
||||||
day.getFullYear() === todayYear
|
|
||||||
)
|
|
||||||
ranges.push(new SelectedDate(DayType.TODAY, new DateRange<Date>(day, day)));
|
ranges.push(new SelectedDate(DayType.TODAY, new DateRange<Date>(day, day)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,10 +137,10 @@ export class CalendarService {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { months, selections: new SelectedDates(ranges) };
|
return { months, selections: new SelectedDates(ranges), today };
|
||||||
}
|
}
|
||||||
|
|
||||||
getHolidays(startYear = this.dateCache.getCurrentYear(), lastYear = startYear + 1): Date[] {
|
getHolidays(startYear: number, lastYear = startYear + 1): Date[] {
|
||||||
const dates: Date[] = [];
|
const dates: Date[] = [];
|
||||||
for (let year = startYear; year <= lastYear; year++) {
|
for (let year = startYear; year <= lastYear; year++) {
|
||||||
for (const date of frenchPublicHolidaysForYear(year))
|
for (const date of frenchPublicHolidaysForYear(year))
|
||||||
@@ -204,18 +195,6 @@ export class CalendarService {
|
|||||||
return months;
|
return months;
|
||||||
}
|
}
|
||||||
|
|
||||||
monthsInAYearFromNow(): Year {
|
|
||||||
const months: Year = [];
|
|
||||||
const currentMonthIndex = this.dateCache.getCurrentMonth();
|
|
||||||
const currentYear = this.dateCache.getCurrentYear();
|
|
||||||
for (let i = 0; i < 12; i++) {
|
|
||||||
const monthIndex = (currentMonthIndex + i) % 12;
|
|
||||||
const year = currentYear + Math.floor((currentMonthIndex + i) / 12);
|
|
||||||
months.push(CalendarService.weeksInMonth(monthIndex, year));
|
|
||||||
}
|
|
||||||
return months;
|
|
||||||
}
|
|
||||||
|
|
||||||
static getWeekNumber(date: Date): number {
|
static getWeekNumber(date: Date): number {
|
||||||
return dayjs(date).isoWeek();
|
return dayjs(date).isoWeek();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
import { provideZonelessChangeDetection } from '@angular/core';
|
|
||||||
import { DateCacheService } from './date-cache.service';
|
|
||||||
|
|
||||||
describe('DateCacheService', () => {
|
|
||||||
let service: DateCacheService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
providers: [provideZonelessChangeDetection()],
|
|
||||||
});
|
|
||||||
service = TestBed.inject(DateCacheService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should cache today date', () => {
|
|
||||||
const today1 = service.getToday();
|
|
||||||
const today2 = service.getToday();
|
|
||||||
expect(today1).toBe(today2); // Same reference
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should cache today components', () => {
|
|
||||||
const components1 = service.getTodayComponents();
|
|
||||||
const components2 = service.getTodayComponents();
|
|
||||||
expect(components1).toBe(components2); // Same reference
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should cache current month', () => {
|
|
||||||
const month1 = service.getCurrentMonth();
|
|
||||||
const month2 = service.getCurrentMonth();
|
|
||||||
expect(month1).toBe(month2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should cache current year', () => {
|
|
||||||
const year1 = service.getCurrentYear();
|
|
||||||
const year2 = service.getCurrentYear();
|
|
||||||
expect(year1).toBe(year2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear cache', () => {
|
|
||||||
const year1 = service.getCurrentYear();
|
|
||||||
service.clearCache();
|
|
||||||
const year2 = service.getCurrentYear();
|
|
||||||
expect(year1).toBe(year2); // Values should be the same even after clearing
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return today with time normalized to 00:00:00.000', () => {
|
|
||||||
const today = service.getToday();
|
|
||||||
expect(today.getHours()).toBe(0);
|
|
||||||
expect(today.getMinutes()).toBe(0);
|
|
||||||
expect(today.getSeconds()).toBe(0);
|
|
||||||
expect(today.getMilliseconds()).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return getNow() same as getToday()', () => {
|
|
||||||
const now = service.getNow();
|
|
||||||
const today = service.getToday();
|
|
||||||
expect(now).toBe(today);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service to cache and provide commonly used date values.
|
|
||||||
* This reduces redundant Date object creation across the application.
|
|
||||||
*/
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root',
|
|
||||||
})
|
|
||||||
export class DateCacheService {
|
|
||||||
private _today: Date | null = null;
|
|
||||||
private _todayComponents: { date: number; month: number; year: number } | null = null;
|
|
||||||
private _currentMonth: number | null = null;
|
|
||||||
private _currentYear: number | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get today's date (time set to 00:00:00.000)
|
|
||||||
*/
|
|
||||||
getToday(): Date {
|
|
||||||
if (!this._today || this.isNewDay(this._today)) {
|
|
||||||
this._today = new Date();
|
|
||||||
this._today.setHours(0, 0, 0, 0);
|
|
||||||
this._todayComponents = null; // Invalidate components cache
|
|
||||||
}
|
|
||||||
return this._today;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get today's date components (date, month, year) for efficient comparison
|
|
||||||
*/
|
|
||||||
getTodayComponents(): { date: number; month: number; year: number } {
|
|
||||||
if (!this._todayComponents || (this._today && this.isNewDay(this._today))) {
|
|
||||||
const today = this.getToday();
|
|
||||||
this._todayComponents = {
|
|
||||||
date: today.getDate(),
|
|
||||||
month: today.getMonth(),
|
|
||||||
year: today.getFullYear(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return this._todayComponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current month (0-11)
|
|
||||||
*/
|
|
||||||
getCurrentMonth(): number {
|
|
||||||
if (this._currentMonth === null || this.isNewMonth()) {
|
|
||||||
this._currentMonth = new Date().getMonth();
|
|
||||||
}
|
|
||||||
return this._currentMonth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current year
|
|
||||||
*/
|
|
||||||
getCurrentYear(): number {
|
|
||||||
if (this._currentYear === null || this.isNewYear()) {
|
|
||||||
this._currentYear = new Date().getFullYear();
|
|
||||||
}
|
|
||||||
return this._currentYear;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current date with time set to 00:00:00.000 (alias for getToday)
|
|
||||||
*/
|
|
||||||
getNow(): Date {
|
|
||||||
return this.getToday();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a cached date is from a previous day
|
|
||||||
*/
|
|
||||||
private isNewDay(cachedDate: Date): boolean {
|
|
||||||
const now = new Date();
|
|
||||||
return (
|
|
||||||
cachedDate.getDate() !== now.getDate() ||
|
|
||||||
cachedDate.getMonth() !== now.getMonth() ||
|
|
||||||
cachedDate.getFullYear() !== now.getFullYear()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if we're in a new month
|
|
||||||
*/
|
|
||||||
private isNewMonth(): boolean {
|
|
||||||
return this._currentMonth !== null && this._currentMonth !== new Date().getMonth();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if we're in a new year
|
|
||||||
*/
|
|
||||||
private isNewYear(): boolean {
|
|
||||||
return this._currentYear !== null && this._currentYear !== new Date().getFullYear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all caches (useful for testing)
|
|
||||||
*/
|
|
||||||
clearCache(): void {
|
|
||||||
this._today = null;
|
|
||||||
this._todayComponents = null;
|
|
||||||
this._currentMonth = null;
|
|
||||||
this._currentYear = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user