From 0b8424503c182725eb369ea2fd42146c60076c87 Mon Sep 17 00:00:00 2001
From: Nessar
Date: Mon, 19 Oct 2026 15:01:06 +0200
Subject: [PATCH] fix(planning): plan TIME_WINDOW rules on the picked calendar
dates
Rule windows reached the planner as local-midnight Date objects, and the
planner read their Europe/Paris date, so east of Paris every window
started and ended one day early. Format them from local components as
YYYY-MM-DD before planning, as balances already are.
Closes #311
Co-Authored-By: Claude Opus 5.5
---
.../planning-calculation.service.spec.ts | 30 ++++++++++++++++++-
.../services/planning-calculation.service.ts | 12 ++++++--
2 files changed, 39 insertions(+), 3 deletions(-)
diff --git a/src/app/services/planning-calculation.service.spec.ts b/src/app/services/planning-calculation.service.spec.ts
index ec88d8d..d52bf0a 100644
--- a/src/app/services/planning-calculation.service.spec.ts
+++ b/src/app/services/planning-calculation.service.spec.ts
@@ -1,7 +1,7 @@
import { provideZonelessChangeDetection } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { vi } from '../../testing/vitest-compat';
-import { CalendarService } from './calendar-service';
+import { CalendarService, SelectableDayType } from './calendar-service';
import { CalendarSettingsService } from './calendar-settings-service';
import type {
PlanOutcome,
@@ -11,6 +11,7 @@ import type {
PlanningReason,
} from './leave-planner.service';
import { PlanningCalculationService } from './planning-calculation.service';
+import { RuleService, RuleType } from './rule-service';
import { UserInputService } from './user-input-service';
function deferred() {
@@ -119,6 +120,33 @@ describe('PlanningCalculationService', () => {
expect(submission.labels).toBeDefined();
});
+ it('hands TIME_WINDOW rules the picked calendar dates east of Paris', () => {
+ const previousTimeZone = process.env['TZ'];
+ process.env['TZ'] = 'Pacific/Auckland';
+ try {
+ TestBed.inject(RuleService).addRule({
+ id: 'christmas',
+ name: 'Christmas',
+ type: RuleType.TIME_WINDOW,
+ enabled: true,
+ force: 'obligation',
+ startDate: new Date(2026, 11, 20),
+ endDate: new Date(2026, 11, 31),
+ placeDays: 3,
+ placeType: SelectableDayType.CP,
+ });
+ queue(fakeCalculation('first'));
+ service.start();
+ const submitted = create.mock.calls[0][0].rules.find(
+ (rule: { id: string }) => rule.id === 'christmas',
+ );
+ expect(submitted).toMatchObject({ startDate: '2026-12-20', endDate: '2026-12-31' });
+ } finally {
+ // bun test defaults to UTC without setting TZ; deleting it would fall back to the host zone.
+ process.env['TZ'] = previousTimeZone ?? 'UTC';
+ }
+ });
+
it('reports the stage and submission of the latest calculation only, until Cancel', () => {
queue(fakeCalculation('old'), fakeCalculation('latest'));
service.start();
diff --git a/src/app/services/planning-calculation.service.ts b/src/app/services/planning-calculation.service.ts
index 84a615d..b66d7fc 100644
--- a/src/app/services/planning-calculation.service.ts
+++ b/src/app/services/planning-calculation.service.ts
@@ -11,7 +11,7 @@ import type {
PlanningRequest,
PlanningSnapshot,
} from './leave-planner.service';
-import { RuleService } from './rule-service';
+import { RuleService, RuleType } from './rule-service';
import { TranslationService } from './translation-service';
import { UserInputService } from './user-input-service';
@@ -85,7 +85,15 @@ export class PlanningCalculationService {
: undefined,
expiryDate: dayjs(balance.expiryDate).format('YYYY-MM-DD'),
})),
- rules: this.rules.rules,
+ rules: this.rules.rules.map((rule) =>
+ rule.type === RuleType.TIME_WINDOW
+ ? {
+ ...rule,
+ startDate: dayjs(rule.startDate).format('YYYY-MM-DD'),
+ endDate: dayjs(rule.endDate).format('YYYY-MM-DD'),
+ }
+ : rule,
+ ),
};
});