Skip to content

Commit 0da4310

Browse files
Merge pull request #14099 from bbc/WS-NA-temporal-migrate-prep-tests
WS-NA: Temporal Migration (PR 1: Adds test coverage)
2 parents 823afa7 + 086c04b commit 0da4310

6 files changed

Lines changed: 281 additions & 3 deletions

File tree

src/app/legacy/containers/ArticleTimestamp/index.stories.jsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import WithTimeMachine from '../../../../testHelpers/withTimeMachine';
2+
import { ServiceContextProvider } from '#app/contexts/ServiceContext';
23
import ArticleTimestamp from '.';
34
import { timestampGenerator } from './testHelpers';
45

@@ -93,3 +94,46 @@ export const H = () => (
9394
);
9495
H.storyName =
9596
'lastPublished today and more than 10 hours ago and firstPublished before today';
97+
98+
// Timezone and locale migration checks for Moment → Temporal
99+
const timezoneLocaleServices = [
100+
{ service: 'news', label: 'English (Europe/London)' },
101+
{ service: 'azeri', label: 'Azeri (Asia/Baku)' },
102+
{ service: 'persian', label: 'Persian (GMT, Jalali calendar)' },
103+
{ service: 'arabic', label: 'Arabic (GMT, RTL)' },
104+
{ service: 'portuguese', label: 'Portuguese (America/Sao_Paulo)' },
105+
{ service: 'nepali', label: 'Nepali (Asia/Kathmandu)' },
106+
];
107+
108+
const dstBoundaryTimestamp = Date.UTC(2021, 2, 28, 1, 0, 0); // 28 March 2021 01:00 UTC (DST boundary)
109+
const fixedOlderTimestamp = Date.UTC(2021, 2, 27, 12, 0, 0); // 27 March 2021 12:00 UTC
110+
111+
export const TimezoneAndLocaleChecks = () => (
112+
<div>
113+
{timezoneLocaleServices.map(({ service, label }) => (
114+
<ServiceContextProvider key={service} service={service}>
115+
<div
116+
style={{
117+
marginBottom: '2rem',
118+
padding: '1rem',
119+
border: '1px solid #ccc',
120+
}}
121+
>
122+
<strong>{label}</strong>
123+
<div style={{ marginTop: '0.5rem' }}>
124+
<ArticleTimestamp
125+
firstPublished={fixedOlderTimestamp}
126+
lastPublished={dstBoundaryTimestamp}
127+
/>
128+
</div>
129+
</div>
130+
</ServiceContextProvider>
131+
))}
132+
</div>
133+
);
134+
TimezoneAndLocaleChecks.parameters = {
135+
chromatic: {
136+
disable: false,
137+
},
138+
};
139+
TimezoneAndLocaleChecks.tags = ['!dev'];

src/app/legacy/containers/ArticleTimestamp/timeFormatTests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ A couple of scenarios that we expect these tests would fail and need updating wo
1212

1313
- Change in the timeformat logic [here](../timeFormats)
1414

15-
- Changes in DST times across different timezones.
15+
- Changes in Daylight Saving Time (DST) times across different timezones.
1616

1717
- ... please add more if you find any
1818

src/app/legacy/containers/ArticleTimestamp/timeFormatTests/testUtils.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,23 @@ export const format = ({ datetimeLocale, timezone, fixture, altCalendar }) => {
5151

5252
return formatted;
5353
};
54+
55+
// allows precise checks for edge cases without fixture data
56+
export const formatTimestamp = ({
57+
datetimeLocale,
58+
timezone,
59+
time,
60+
altCalendar,
61+
dateTimeFormat = formatDateAndTime,
62+
}) => {
63+
const dateWithTimezone = moment.tz(time, timezone).locale(datetimeLocale);
64+
const formatted = dateWithTimezone.format(dateTimeFormat(datetimeLocale));
65+
66+
if (altCalendar) {
67+
const altCalendarFormatted = altCalendar.formatDate(dateWithTimezone);
68+
69+
return `${altCalendarFormatted} - ${formatted}`;
70+
}
71+
72+
return formatted;
73+
};

src/app/legacy/containers/ArticleTimestamp/timeFormatTests/timeFormat.test.js

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import services from '#utilities/serviceConfigs';
22
import expectedFormats from './expectedFormats.json';
3-
import { format, timestampsFixtures } from './testUtils';
3+
import { format, formatTimestamp, timestampsFixtures } from './testUtils';
44

55
describe('Timestamp Formats', () => {
66
Object.keys(services).forEach(service => {
@@ -22,4 +22,81 @@ describe('Timestamp Formats', () => {
2222
});
2323
});
2424
});
25+
26+
describe('Timestamp Edge Cases', () => {
27+
it('should preserve timezone output around DST start for Europe/London', () => {
28+
const beforeDSTStart = Date.UTC(2021, 2, 28, 0, 30, 0); // 00:30 GMT
29+
const afterDSTStart = Date.UTC(2021, 2, 28, 1, 30, 0); // 02:30 BST
30+
31+
expect(
32+
formatTimestamp({
33+
datetimeLocale: services.news.default.datetimeLocale,
34+
timezone: services.news.default.timezone,
35+
time: beforeDSTStart,
36+
}),
37+
).toEqual('28 March 2021, 00:30 GMT');
38+
39+
expect(
40+
formatTimestamp({
41+
datetimeLocale: services.news.default.datetimeLocale,
42+
timezone: services.news.default.timezone,
43+
time: afterDSTStart,
44+
}),
45+
).toEqual('28 March 2021, 02:30 BST');
46+
});
47+
48+
it('should preserve timezone output around DST end for Europe/London', () => {
49+
const beforeDSTEnd = Date.UTC(2021, 9, 31, 0, 30, 0); // 01:30 BST
50+
const afterDSTEnd = Date.UTC(2021, 9, 31, 1, 30, 0); // 01:30 GMT
51+
52+
expect(
53+
formatTimestamp({
54+
datetimeLocale: services.news.default.datetimeLocale,
55+
timezone: services.news.default.timezone,
56+
time: beforeDSTEnd,
57+
}),
58+
).toEqual('31 October 2021, 01:30 BST');
59+
60+
expect(
61+
formatTimestamp({
62+
datetimeLocale: services.news.default.datetimeLocale,
63+
timezone: services.news.default.timezone,
64+
time: afterDSTEnd,
65+
}),
66+
).toEqual('31 October 2021, 01:30 GMT');
67+
});
68+
69+
it('should preserve half-hour offset output for Asia/Kabul', () => {
70+
const utcTimestamp = Date.UTC(2021, 5, 15, 12, 0, 0); // 15 June 2021 12:00 UTC
71+
72+
expect(
73+
formatTimestamp({
74+
datetimeLocale: services.dari.default.datetimeLocale,
75+
timezone: services.dari.default.timezone,
76+
time: utcTimestamp,
77+
}),
78+
).toEqual('۱۵ جون ۲۰۲۱ ۱۶:۳۰');
79+
});
80+
81+
it('should preserve timezone output around DST start for Europe/Bucharest', () => {
82+
const beforeDSTStart = Date.UTC(2021, 2, 28, 0, 30, 0); // 02:30 EET
83+
const afterDSTStart = Date.UTC(2021, 2, 28, 1, 30, 0); // 04:30 EEST
84+
85+
expect(
86+
formatTimestamp({
87+
datetimeLocale: services.romania.default.datetimeLocale,
88+
timezone: services.romania.default.timezone,
89+
time: beforeDSTStart,
90+
}),
91+
).toEqual('28 martie 2021, 02:30 EET');
92+
93+
expect(
94+
formatTimestamp({
95+
datetimeLocale: services.romania.default.datetimeLocale,
96+
timezone: services.romania.default.timezone,
97+
time: afterDSTStart,
98+
}),
99+
).toEqual('28 martie 2021, 04:30 EEST');
100+
});
101+
});
25102
});

src/app/legacy/psammead/psammead-timestamp-container/src/utilities/index.test.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,143 @@ describe('Timestamp utility functions', () => {
132132
});
133133
expect(result).toBeUndefined();
134134
});
135+
136+
it('should correctly format timezone labels around DST start in Europe/London', () => {
137+
const beforeDSTStart = Date.UTC(2021, 2, 28, 0, 30, 0); // 00:30 GMT
138+
const afterDSTStart = Date.UTC(2021, 2, 28, 1, 30, 0); // 02:30 BST
139+
140+
expect(
141+
formatUnixTimestamp({
142+
timestamp: beforeDSTStart,
143+
format: 'D MMMM YYYY, HH:mm z',
144+
timezone,
145+
locale,
146+
}),
147+
).toEqual('28 March 2021, 00:30 GMT');
148+
149+
expect(
150+
formatUnixTimestamp({
151+
timestamp: afterDSTStart,
152+
format: 'D MMMM YYYY, HH:mm z',
153+
timezone,
154+
locale,
155+
}),
156+
).toEqual('28 March 2021, 02:30 BST');
157+
});
158+
159+
it('should correctly format timezone labels around DST end in Europe/London', () => {
160+
const beforeDSTEnd = Date.UTC(2021, 9, 31, 0, 30, 0); // 01:30 BST
161+
const afterDSTEnd = Date.UTC(2021, 9, 31, 1, 30, 0); // 01:30 GMT
162+
163+
expect(
164+
formatUnixTimestamp({
165+
timestamp: beforeDSTEnd,
166+
format: 'D MMMM YYYY, HH:mm z',
167+
timezone,
168+
locale,
169+
}),
170+
).toEqual('31 October 2021, 01:30 BST');
171+
172+
expect(
173+
formatUnixTimestamp({
174+
timestamp: afterDSTEnd,
175+
format: 'D MMMM YYYY, HH:mm z',
176+
timezone,
177+
locale,
178+
}),
179+
).toEqual('31 October 2021, 01:30 GMT');
180+
});
181+
182+
it('should apply a fixed UTC+1 offset for Africa/Lagos (no DST)', () => {
183+
const utcTimestamp = Date.UTC(2021, 5, 15, 12, 0, 0); // 15 June 2021 12:00 UTC
184+
185+
expect(
186+
formatUnixTimestamp({
187+
timestamp: utcTimestamp,
188+
format: 'D MMMM YYYY, HH:mm z',
189+
timezone: 'Africa/Lagos',
190+
locale,
191+
}),
192+
).toEqual('15 June 2021, 13:00 WAT');
193+
});
194+
195+
it('should apply a +05:45 offset for Asia/Kathmandu (no DST, non-whole-hour offset)', () => {
196+
const utcTimestamp = Date.UTC(2021, 5, 15, 12, 0, 0); // 15 June 2021 12:00 UTC
197+
198+
expect(
199+
formatUnixTimestamp({
200+
timestamp: utcTimestamp,
201+
format: 'D MMMM YYYY, HH:mm z',
202+
timezone: 'Asia/Kathmandu',
203+
locale,
204+
}),
205+
).toEqual('15 June 2021, 17:45 +0545');
206+
});
207+
208+
it('should apply a negative UTC-3 offset for America/Sao_Paulo', () => {
209+
const utcTimestamp = Date.UTC(2021, 5, 15, 12, 0, 0); // 15 June 2021 12:00 UTC
210+
211+
expect(
212+
formatUnixTimestamp({
213+
timestamp: utcTimestamp,
214+
format: 'D MMMM YYYY, HH:mm z',
215+
timezone: 'America/Sao_Paulo',
216+
locale,
217+
}),
218+
).toEqual('15 June 2021, 09:00 -03');
219+
});
220+
221+
it('should format correctly for GMT (non-region-style IANA identifier)', () => {
222+
const utcTimestamp = Date.UTC(2021, 5, 15, 12, 0, 0); // 15 June 2021 12:00 UTC
223+
224+
expect(
225+
formatUnixTimestamp({
226+
timestamp: utcTimestamp,
227+
format: 'D MMMM YYYY, HH:mm z',
228+
timezone: 'GMT',
229+
locale,
230+
}),
231+
).toEqual('15 June 2021, 12:00 GMT');
232+
});
233+
234+
it('should translate month names for a non-Latin locale', () => {
235+
expect(
236+
formatUnixTimestamp({
237+
timestamp,
238+
format: 'D MMMM YYYY',
239+
timezone: 'GMT',
240+
locale: 'ar',
241+
}),
242+
).toEqual('١٩ أكتوبر ٢٠١٨');
243+
});
244+
245+
it('should apply locale-sensitive format tokens (LL, LT) for a non-Latin locale', () => {
246+
expect(
247+
formatUnixTimestamp({
248+
timestamp,
249+
format: null,
250+
timezone: 'GMT',
251+
locale: 'ar',
252+
}),
253+
).toEqual('١٩ أكتوبر ٢٠١٨، ١٧:١٠ GMT');
254+
});
255+
256+
it('should return relative timestamp in the provided locale', () => {
257+
const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(1704110400000); // 1 January 2024 12:00:00 UTC
258+
try {
259+
const nineHoursAgo = timestampGenerator({ hours: 9 });
260+
const output = formatUnixTimestamp({
261+
timestamp: nineHoursAgo,
262+
format: 'D MMMM YYYY',
263+
timezone: 'GMT',
264+
locale: 'ar',
265+
isRelative: true,
266+
});
267+
expect(output).toEqual('منذ ٩ ساعات');
268+
} finally {
269+
nowSpy.mockRestore();
270+
}
271+
});
135272
});
136273
});
137274

src/app/lib/config/services/azeri.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ export const service: DefaultServiceConfig = {
478478
],
479479
copyrightText: 'BBC. BBC kənar saytların məzmununa məsul deyil.',
480480
},
481-
timezone: 'Asia/baku',
481+
timezone: 'Asia/Baku',
482482
navigation: [
483483
{
484484
title: 'Xəbərlər',

0 commit comments

Comments
 (0)