Skip to content

Commit 18851fb

Browse files
wip refactoring manual timezone offset calculations in date.pipe from native Date methods to momentjs
1 parent 995752b commit 18851fb

File tree

2 files changed

+476
-32
lines changed

2 files changed

+476
-32
lines changed

projects/ccd-case-ui-toolkit/src/lib/shared/components/palette/utils/date.pipe.spec.ts

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,280 @@ describe('DatePipe', () => {
162162
(two hours behind or in front) when moving through the various steps.
163163
*/
164164

165+
describe('British Summer Time (BST) Transitions - EXUI-3066', () => {
166+
// BST starts: Last Sunday in March at 1:00 AM GMT → 2:00 AM BST (UTC+1)
167+
// BST ends: Last Sunday in October at 2:00 AM BST → 1:00 AM GMT (UTC+0)
168+
169+
describe('Spring Forward - Clocks go forward (GMT to BST)', () => {
170+
it('should correctly display UTC time just before spring forward transition', () => {
171+
// March 26, 2023 at 00:30 UTC (30 mins before clocks spring forward)
172+
const UTC_BEFORE_SPRING = '2023-03-26T00:30:00.000';
173+
const result = datePipe.transform(UTC_BEFORE_SPRING, 'local', null);
174+
175+
// In local time (GMT), this would still be 00:30 AM on March 26
176+
expect(result).toContain('26 Mar 2023');
177+
expect(result).toContain('12:30:00 AM');
178+
});
179+
180+
it('should correctly display UTC time just after spring forward transition', () => {
181+
// March 26, 2023 at 01:30 UTC (30 mins after clocks spring forward)
182+
// At 1:00 AM GMT, clocks jumped to 2:00 AM BST
183+
const UTC_AFTER_SPRING = '2023-03-26T01:30:00.000';
184+
const result = datePipe.transform(UTC_AFTER_SPRING, 'local', null);
185+
186+
// In local time (BST = UTC+1), 01:30 UTC should be 02:30 BST
187+
expect(result).toContain('26 Mar 2023');
188+
expect(result).toContain('2:30:00 AM');
189+
});
190+
191+
it('should handle UTC time during the missing hour (1:00-2:00 AM GMT)', () => {
192+
// March 26, 2023 at 01:15 UTC (during the non-existent hour in local time)
193+
const UTC_MISSING_HOUR = '2023-03-26T01:15:00.000';
194+
const result = datePipe.transform(UTC_MISSING_HOUR, 'local', null);
195+
196+
// This time doesn't exist in local time, but moment converts it to BST
197+
// 01:15 UTC = 02:15 BST
198+
expect(result).toContain('26 Mar 2023');
199+
expect(result).toContain('2:15:00 AM');
200+
});
201+
202+
it('should handle date-only value during spring forward day', () => {
203+
// Date without time during spring forward should not shift
204+
const SPRING_FORWARD_DATE = '2023-03-26';
205+
const result = datePipe.transform(SPRING_FORWARD_DATE, null, null);
206+
207+
expect(result).toBe('26 Mar 2023');
208+
});
209+
210+
it('should correctly handle noon UTC on spring forward day', () => {
211+
// March 26, 2023 at 12:00 UTC (well after transition)
212+
const UTC_NOON = '2023-03-26T12:00:00.000';
213+
const result = datePipe.transform(UTC_NOON, 'local', null);
214+
215+
// 12:00 UTC = 13:00 BST (1:00 PM)
216+
expect(result).toContain('26 Mar 2023');
217+
expect(result).toContain('1:00:00 PM');
218+
});
219+
});
220+
221+
describe('Fall Back - Clocks go back (BST to GMT)', () => {
222+
it('should correctly display UTC time just before fall back transition', () => {
223+
// October 29, 2023 at 00:30 UTC (30 mins before clocks fall back)
224+
const UTC_BEFORE_FALL = '2023-10-29T00:30:00.000';
225+
const result = datePipe.transform(UTC_BEFORE_FALL, 'local', null);
226+
227+
// Still in BST, so 00:30 UTC = 01:30 BST
228+
expect(result).toContain('29 Oct 2023');
229+
expect(result).toContain('1:30:00 AM');
230+
});
231+
232+
it('should correctly display UTC time just after fall back transition', () => {
233+
// October 29, 2023 at 01:30 UTC (after clocks fell back)
234+
// At 2:00 AM BST, clocks went back to 1:00 AM GMT
235+
const UTC_AFTER_FALL = '2023-10-29T01:30:00.000';
236+
const result = datePipe.transform(UTC_AFTER_FALL, 'local', null);
237+
238+
// Now in GMT, so 01:30 UTC = 01:30 GMT
239+
expect(result).toContain('29 Oct 2023');
240+
expect(result).toContain('1:30:00 AM');
241+
});
242+
243+
it('should handle UTC time during the repeated hour (1:00-2:00 AM)', () => {
244+
// October 29, 2023 at 01:15 UTC (during the hour that occurs twice)
245+
const UTC_REPEATED_HOUR = '2023-10-29T01:15:00.000';
246+
const result = datePipe.transform(UTC_REPEATED_HOUR, 'local', null);
247+
248+
// Moment will interpret this as GMT (after the transition)
249+
expect(result).toContain('29 Oct 2023');
250+
expect(result).toContain('1:15:00 AM');
251+
});
252+
253+
it('should handle date-only value during fall back day', () => {
254+
// Date without time during fall back should not shift
255+
const FALL_BACK_DATE = '2023-10-29';
256+
const result = datePipe.transform(FALL_BACK_DATE, null, null);
257+
258+
expect(result).toBe('29 Oct 2023');
259+
});
260+
261+
it('should correctly handle noon UTC on fall back day', () => {
262+
// October 29, 2023 at 12:00 UTC (well after transition)
263+
const UTC_NOON = '2023-10-29T12:00:00.000';
264+
const result = datePipe.transform(UTC_NOON, 'local', null);
265+
266+
// 12:00 UTC = 12:00 GMT (after fall back)
267+
expect(result).toContain('29 Oct 2023');
268+
expect(result).toContain('12:00:00 PM');
269+
});
270+
});
271+
272+
describe('Regular BST and GMT periods', () => {
273+
it('should correctly display UTC time during summer (BST period)', () => {
274+
// July 15, 2023 at 14:30 UTC (middle of summer, BST in effect)
275+
const UTC_SUMMER = '2023-07-15T14:30:00.000';
276+
const result = datePipe.transform(UTC_SUMMER, 'local', null);
277+
278+
// 14:30 UTC = 15:30 BST (3:30 PM)
279+
expect(result).toContain('15 Jul 2023');
280+
expect(result).toContain('3:30:00 PM');
281+
});
282+
283+
it('should correctly display UTC time during winter (GMT period)', () => {
284+
// January 15, 2023 at 14:30 UTC (middle of winter, GMT in effect)
285+
const UTC_WINTER = '2023-01-15T14:30:00.000';
286+
const result = datePipe.transform(UTC_WINTER, 'local', null);
287+
288+
// 14:30 UTC = 14:30 GMT (2:30 PM)
289+
expect(result).toContain('15 Jan 2023');
290+
expect(result).toContain('2:30:00 PM');
291+
});
292+
293+
it('should correctly display UTC midnight during BST', () => {
294+
// August 1, 2023 at 00:00 UTC (midnight during BST)
295+
const UTC_MIDNIGHT_BST = '2023-08-01T00:00:00.000';
296+
const result = datePipe.transform(UTC_MIDNIGHT_BST, 'local', null);
297+
298+
// 00:00 UTC = 01:00 BST (1:00 AM)
299+
expect(result).toContain('1 Aug 2023');
300+
expect(result).toContain('1:00:00 AM');
301+
});
302+
303+
it('should correctly display UTC midnight during GMT', () => {
304+
// December 1, 2023 at 00:00 UTC (midnight during GMT)
305+
const UTC_MIDNIGHT_GMT = '2023-12-01T00:00:00.000';
306+
const result = datePipe.transform(UTC_MIDNIGHT_GMT, 'local', null);
307+
308+
// 00:00 UTC = 00:00 GMT (midnight)
309+
expect(result).toContain('1 Dec 2023');
310+
expect(result).toContain('12:00:00 AM');
311+
});
312+
313+
it('should correctly display UTC 23:00 during BST (crosses day boundary)', () => {
314+
// July 15, 2023 at 23:30 UTC (late evening during BST)
315+
const UTC_LATE_BST = '2023-07-15T23:30:00.000';
316+
const result = datePipe.transform(UTC_LATE_BST, 'local', null);
317+
318+
// 23:30 UTC = 00:30 next day BST (crosses midnight)
319+
expect(result).toContain('16 Jul 2023');
320+
expect(result).toContain('12:30:00 AM');
321+
});
322+
});
323+
324+
describe('Edge cases with timezone offset notation', () => {
325+
it('should handle UTC datetime with Z suffix during BST period', () => {
326+
const UTC_WITH_Z = '2023-06-15T10:30:00.000Z';
327+
const result = datePipe.transform(UTC_WITH_Z, 'local', null);
328+
329+
// 10:30 UTC = 11:30 BST
330+
expect(result).toContain('15 Jun 2023');
331+
expect(result).toContain('11:30:00 AM');
332+
});
333+
334+
it('should handle UTC datetime with +00:00 offset during GMT period', () => {
335+
const UTC_WITH_OFFSET = '2023-01-15T10:30:00.000+00:00';
336+
const result = datePipe.transform(UTC_WITH_OFFSET, 'local', null);
337+
338+
// 10:30 UTC = 10:30 GMT
339+
expect(result).toContain('15 Jan 2023');
340+
expect(result).toContain('10:30:00 AM');
341+
});
342+
});
343+
344+
describe('UTC mode (should not convert to local time)', () => {
345+
it('should keep UTC time as-is when zone is utc during BST period', () => {
346+
const UTC_TIME = '2023-07-15T14:30:00.000';
347+
const result = datePipe.transform(UTC_TIME, 'utc', null);
348+
349+
// Should remain 14:30, not converted to BST
350+
expect(result).toContain('15 Jul 2023');
351+
expect(result).toContain('2:30:00 PM');
352+
});
353+
354+
it('should keep UTC time as-is when zone is utc during GMT period', () => {
355+
const UTC_TIME = '2023-01-15T14:30:00.000';
356+
const result = datePipe.transform(UTC_TIME, 'utc', null);
357+
358+
// Should remain 14:30
359+
expect(result).toContain('15 Jan 2023');
360+
expect(result).toContain('2:30:00 PM');
361+
});
362+
});
363+
});
364+
365+
describe('Edge case tests - EXUI-3066', () => {
366+
it('should correctly handle timezone offset notation (+01:00)', () => {
367+
const DATE_WITH_OFFSET = '2023-07-15T14:30:00.000+01:00';
368+
const result = datePipe.transform(DATE_WITH_OFFSET, 'local', null);
369+
370+
// 14:30 in +01:00 timezone = 13:30 UTC = 14:30 BST (in UK summer)
371+
expect(result).toContain('15 Jul 2023');
372+
expect(result).toContain('2:30:00 PM');
373+
});
374+
375+
it('should correctly handle negative timezone offsets (-05:00)', () => {
376+
const DATE_WITH_NEG_OFFSET = '2023-01-15T10:00:00.000-05:00';
377+
const result = datePipe.transform(DATE_WITH_NEG_OFFSET, 'local', null);
378+
379+
// 10:00 in -05:00 (EST) = 15:00 UTC = 15:00 GMT (in UK winter)
380+
expect(result).toContain('15 Jan 2023');
381+
expect(result).toContain('3:00:00 PM');
382+
});
383+
384+
it('should handle datetime values crossing day boundary when converting to local', () => {
385+
const LATE_UTC = '2023-08-15T23:30:00.000';
386+
const result = datePipe.transform(LATE_UTC, 'local', null);
387+
388+
// 23:30 UTC in summer = 00:30 next day BST
389+
expect(result).toContain('16 Aug 2023');
390+
expect(result).toContain('12:30:00 AM');
391+
});
392+
393+
it('should handle early morning UTC times during winter', () => {
394+
const EARLY_UTC_WINTER = '2023-01-15T00:30:00.000';
395+
const result = datePipe.transform(EARLY_UTC_WINTER, 'local', null);
396+
397+
// 00:30 UTC in winter = 00:30 GMT (no change)
398+
expect(result).toContain('15 Jan 2023');
399+
expect(result).toContain('12:30:00 AM');
400+
});
401+
402+
it('should handle early morning UTC times during summer (stays on same day)', () => {
403+
const EARLY_UTC_SUMMER = '2023-07-15T00:30:00.000';
404+
const result = datePipe.transform(EARLY_UTC_SUMMER, 'local', null);
405+
406+
// 00:30 UTC in summer = 01:30 BST (same day)
407+
expect(result).toContain('15 Jul 2023');
408+
expect(result).toContain('1:30:00 AM');
409+
});
410+
411+
it('should correctly handle DST transition with explicit UTC offset', () => {
412+
const DST_TRANSITION_WITH_OFFSET = '2023-03-26T01:00:00.000+00:00';
413+
const result = datePipe.transform(DST_TRANSITION_WITH_OFFSET, 'local', null);
414+
415+
// 01:00 UTC with +00:00 = 02:00 BST (after transition)
416+
expect(result).toContain('26 Mar 2023');
417+
expect(result).toContain('2:00:00 AM');
418+
});
419+
420+
it('should handle timezone-aware string when converting to UTC mode', () => {
421+
const WITH_OFFSET = '2023-06-15T14:30:00.000+02:00';
422+
const result = datePipe.transform(WITH_OFFSET, 'utc', null);
423+
424+
// 14:30 in +02:00 = 12:30 UTC
425+
expect(result).toContain('15 Jun 2023');
426+
expect(result).toContain('12:30:00 PM');
427+
});
428+
429+
it('should handle datetime at exact DST transition moment', () => {
430+
const EXACT_TRANSITION = '2023-03-26T01:00:00.000';
431+
const result = datePipe.transform(EXACT_TRANSITION, 'local', null);
432+
433+
// At 1:00 AM GMT, clocks jump to 2:00 AM BST
434+
expect(result).toContain('26 Mar 2023');
435+
expect(result).toContain('2:00:00 AM');
436+
});
437+
});
438+
165439
function getExpectedHour(hour): number {
166440
let expectedHour = hour + EXPECTED_OFFSET;
167441
if (expectedHour > 12) {

0 commit comments

Comments
 (0)