Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions src/tests/totext.i18n.extra.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ function zdt(y: number, m: number, d: number, h: number, tz = 'UTC') {
const expected = {
de: [
'jede/n/s Tag',
'jede/n/s Tag um 10 AM, 12 PM und 5 PM UTC',
'jede/n/s Woche am Sonntag um 10 AM, 12 PM und 5 PM UTC',
'jede/n/s Tag um 5:30 PM CST',
'jede/n/s Tag um 6:15:45 AM UTC',
'jede/n/s Tag um 10 Uhr, 12 Uhr und 17 Uhr UTC',
'jede/n/s Woche am Sonntag um 10 Uhr, 12 Uhr und 17 Uhr UTC',
'jede/n/s Tag um 17:30 GMT-6',
'jede/n/s Tag um 6:15:45 UTC',
'jede/n/s Werktag',
'jede/n/s 2 Minuten',
'jede/n/s Woche am Sonntag bis 10. November 2012',
Expand All @@ -23,10 +23,10 @@ const expected = {
],
es: [
'cada día',
'cada día a las 10 AM, 12 PM y 5 PM UTC',
'cada semana en domingo a las 10 AM, 12 PM y 5 PM UTC',
'cada día a las 5:30 PM CST',
'cada día a las 6:15:45 AM UTC',
'cada día a las 10, 12 y 17 UTC',
'cada semana en domingo a las 10, 12 y 17 UTC',
'cada día a las 17:30 GMT-6',
'cada día a las 6:15:45 UTC',
'cada día de la semana',
'cada 2 minutos',
'cada semana en domingo hasta 10 de noviembre de 2012',
Expand All @@ -39,7 +39,7 @@ const expected = {
'हर दिन',
'हर दिन पर 10 AM, 12 PM और 5 PM UTC',
'हर सप्ताह को रविवार पर 10 AM, 12 PM और 5 PM UTC',
'हर दिन पर 5:30 PM CST',
'हर दिन पर 5:30 pm GMT-6',
'हर दिन पर 6:15:45 AM UTC',
'हर सप्ताह का दिन',
'हर 2 मिनट',
Expand All @@ -51,10 +51,10 @@ const expected = {
],
yue: [
'每 日',
'每 日 在 10 AM, 12 PM 和 5 PM UTC',
'每 週 在 星期日 在 10 AM, 12 PM 和 5 PM UTC',
'每 日 在 5:30 PM CST',
'每 日 在 6:15:45 AM UTC',
'每 日 在 上午10時, 下午12時 和 下午5時 utc',
'每 週 在 星期日 在 上午10時, 下午12時 和 下午5時 utc',
'每 日 在 下午5:30 GMT-6',
'每 日 在 上午6:15:45 utc',
'每 平日',
'每 2 分鐘',
'每 週 在 星期日 直到 2012年11月10日',
Expand All @@ -65,10 +65,10 @@ const expected = {
],
ar: [
'كل يوم',
'كل يوم عند 10 AM, 12 PM و 5 PM UTC',
'كل أسبوع في الأحد عند 10 AM, 12 PM و 5 PM UTC',
'كل يوم عند 5:30 PM CST',
'كل يوم عند 6:15:45 AM UTC',
'كل يوم عند 10 ص, 12 م و 5 م utc',
'كل أسبوع في الأحد عند 10 ص, 12 م و 5 م utc',
'كل يوم عند 5:30 م غرينتش-6',
'كل يوم عند 6:15:45 ص utc',
'كل يوم من أيام الأسبوع',
'كل 2 دقائق',
'كل أسبوع في الأحد حتى 10 نوفمبر 2012',
Expand All @@ -79,10 +79,10 @@ const expected = {
],
he: [
'כל יום',
'כל יום בשעה 10 AM, 12 PM ו 5 PM UTC',
'כל שבוע ב יום ראשון בשעה 10 AM, 12 PM ו 5 PM UTC',
'כל יום בשעה 5:30 PM CST',
'כל יום בשעה 6:15:45 AM UTC',
'כל יום בשעה 10, 12 ו 17 utc',
'כל שבוע ב יום ראשון בשעה 10, 12 ו 17 utc',
'כל יום בשעה 17:30 GMT-6‎',
'כל יום בשעה 6:15:45 utc',
'כל יום חול',
'כל 2 דקות',
'כל שבוע ב יום ראשון עד 10 בנובמבר 2012',
Expand All @@ -91,12 +91,12 @@ const expected = {
'כל חודש ב יום שני ו יום רביעי ב שני פעם',
'כל חודש במשך 1 פעם עם 1 תאריך נוסף למעט 2 תאריכים',
],
zh: [
'zh-Hans': [
'每 日',
'每 日 在 10 AM, 12 PM 和 5 PM UTC',
'每 周 在 星期日 在 10 AM, 12 PM 和 5 PM UTC',
'每 日 在 5:30 PM CST',
'每 日 在 6:15:45 AM UTC',
'每 日 在 10时, 12时 和 17时 utc',
'每 周 在 星期日 在 10时, 12时 和 17时 UTC',
'每 日 在 17:30 GMT-6',
'每 日 在 6:15:45 utc',
'每 工作日',
'每 2 分钟',
'每 周 在 星期日 直到 2012年11月10日',
Expand All @@ -107,10 +107,10 @@ const expected = {
],
fr: [
'chaque jour',
'chaque jour à 10 AM, 12 PM et 5 PM UTC',
'chaque semaine le dimanche à 10 AM, 12 PM et 5 PM UTC',
'chaque jour à 5:30 PM CST',
'chaque jour à 6:15:45 AM UTC',
'chaque jour à 10 h, 12 h et 17 h UTC',
'chaque semaine le dimanche à 10 h, 12 h et 17 h UTC',
'chaque jour à 17:30 UTC−6',
'chaque jour à 6:15:45 UTC',
'chaque jour de semaine',
'chaque 2 minutes',
"chaque semaine le dimanche jusqu'au 10 novembre 2012",
Expand Down
82 changes: 41 additions & 41 deletions src/tests/totext.i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ const cases = [
const expected = {
de: [
'jede/n/s Tag',
'jede/n/s Tag um 10 AM, 12 PM und 5 PM EDT',
'jede/n/s Woche am Sonntag um 10 AM, 12 PM und 5 PM EDT',
'jede/n/s Tag um 10 Uhr, 12 Uhr und 17 Uhr GMT-4',
'jede/n/s Woche am Sonntag um 10 Uhr, 12 Uhr und 17 Uhr GMT-4',
'jede/n/s Woche am Sonntag',
'jede/n/s Stunde',
'jede/n/s 4 Stunden',
Expand All @@ -54,14 +54,14 @@ const expected = {
'jede/n/s Jahr',
'jede/n/s Jahr am 1. Freitag',
'jede/n/s Jahr am 13. Freitag',
'jede/n/s Tag um 5:30 PM EDT',
'jede/n/s Woche am Montag und Mittwoch um 10 AM und 4 PM EDT',
'jede/n/s Woche am Dienstag und Donnerstag um 9:30 AM und 3:30 PM EDT',
'jede/n/s Tag um 17:30 GMT-4',
'jede/n/s Woche am Montag und Mittwoch um 10 Uhr und 16 Uhr GMT-4',
'jede/n/s Woche am Dienstag und Donnerstag um 9:30 und 15:30 GMT-4',
],
es: [
'cada día',
'cada día a las 10 AM, 12 PM y 5 PM EDT',
'cada semana en domingo a las 10 AM, 12 PM y 5 PM EDT',
'cada día a las 10, 12 y 17 GMT-4',
'cada semana en domingo a las 10, 12 y 17 GMT-4',
'cada semana en domingo',
'cada hora',
'cada 4 horas',
Expand All @@ -74,14 +74,14 @@ const expected = {
'cada año',
'cada año en 1º viernes',
'cada año en 13º viernes',
'cada día a las 5:30 PM EDT',
'cada semana en lunes y miércoles a las 10 AM y 4 PM EDT',
'cada semana en martes y jueves a las 9:30 AM y 3:30 PM EDT',
'cada día a las 17:30 GMT-4',
'cada semana en lunes y miércoles a las 10 y 16 GMT-4',
'cada semana en martes y jueves a las 9:30 y 15:30 GMT-4',
],
hi: [
'हर दिन',
'हर दिन पर 10 AM, 12 PM और 5 PM EDT',
'हर सप्ताह को रविवार पर 10 AM, 12 PM और 5 PM EDT',
'हर दिन पर 10 AM, 12 PM और 5 PM GMT-4',
'हर सप्ताह को रविवार पर 10 AM, 12 PM और 5 PM GMT-4',
'हर सप्ताह को रविवार',
'हर घंटा',
'हर 4 घंटे',
Expand All @@ -94,14 +94,14 @@ const expected = {
'हर साल',
'हर साल को 1वां शुक्रवार',
'हर साल को 13वां शुक्रवार',
'हर दिन पर 5:30 PM EDT',
'हर सप्ताह को सोमवार और बुधवार पर 10 AM और 4 PM EDT',
'हर सप्ताह को मंगलवार और गुरुवार पर 9:30 AM और 3:30 PM EDT',
'हर दिन पर 5:30 pm GMT-4',
'हर सप्ताह को सोमवार और बुधवार पर 10 am और 4 pm GMT-4',
'हर सप्ताह को मंगलवार और गुरुवार पर 9:30 am और 3:30 pm GMT-4',
],
yue: [
'每 日',
'每 日 在 10 AM, 12 PM 和 5 PM EDT',
'每 週 在 星期日 在 10 AM, 12 PM 和 5 PM EDT',
'每 日 在 上午10時, 下午12時 和 下午5時 GMT-4',
'每 週 在 星期日 在 上午10時, 下午12時 和 下午5時 GMT-4',
'每 週 在 星期日',
'每 小時',
'每 4 小時',
Expand All @@ -114,14 +114,14 @@ const expected = {
'每 年',
'每 年 在 第1 星期五',
'每 年 在 第13 星期五',
'每 日 在 5:30 PM EDT',
'每 週 在 星期一 和 星期三 在 10 AM 和 4 PM EDT',
'每 週 在 星期二 和 星期四 在 9:30 AM 和 3:30 PM EDT',
'每 日 在 下午5:30 GMT-4',
'每 週 在 星期一 和 星期三 在 上午10時 和 下午4時 GMT-4',
'每 週 在 星期二 和 星期四 在 上午9:30 和 下午3:30 GMT-4',
],
ar: [
'كل يوم',
'كل يوم عند 10 AM, 12 PM و 5 PM EDT',
'كل أسبوع في الأحد عند 10 AM, 12 PM و 5 PM EDT',
'كل يوم عند 10 ص, 12 م و 5 م غرينتش-4',
'كل أسبوع في الأحد عند 10 ص, 12 م و 5 م غرينتش-4',
'كل أسبوع في الأحد',
'كل ساعة',
'كل 4 ساعات',
Expand All @@ -134,14 +134,14 @@ const expected = {
'كل سنة',
'كل سنة في الأول الجمعة',
'كل سنة في الثالث عشر الجمعة',
'كل يوم عند 5:30 PM EDT',
'كل أسبوع في الاثنين و الأربعاء عند 10 AM و 4 PM EDT',
'كل أسبوع في الثلاثاء و الخميس عند 9:30 AM و 3:30 PM EDT',
'كل يوم عند 5:30 م غرينتش-4',
'كل أسبوع في الاثنين و الأربعاء عند 10 ص و 4 م غرينتش-4',
'كل أسبوع في الثلاثاء و الخميس عند 9:30 ص و 3:30 م غرينتش-4',
],
he: [
'כל יום',
'כל יום בשעה 10 AM, 12 PM ו 5 PM EDT',
'כל שבוע ב יום ראשון בשעה 10 AM, 12 PM ו 5 PM EDT',
'כל יום בשעה 10, 12 ו 17 GMT-4‎',
'כל שבוע ב יום ראשון בשעה 10, 12 ו 17 GMT-4‎',
'כל שבוע ב יום ראשון',
'כל שעה',
'כל 4 שעות',
Expand All @@ -154,14 +154,14 @@ const expected = {
'כל שנה',
'כל שנה ב ראשון יום שישי',
'כל שנה ב שלושה עשר יום שישי',
'כל יום בשעה 5:30 PM EDT',
'כל שבוע ב יום שני ו יום רביעי בשעה 10 AM ו 4 PM EDT',
'כל שבוע ב יום שלישי ו יום חמישי בשעה 9:30 AM ו 3:30 PM EDT',
'כל יום בשעה 17:30 GMT-4‎',
'כל שבוע ב יום שני ו יום רביעי בשעה 10 ו 16 GMT-4‎',
'כל שבוע ב יום שלישי ו יום חמישי בשעה 9:30 ו 15:30 GMT-4‎',
],
zh: [
'zh-Hans': [
'每 日',
'每 日 在 10 AM, 12 PM 和 5 PM EDT',
'每 周 在 星期日 在 10 AM, 12 PM 和 5 PM EDT',
'每 日 在 10时, 12时 和 17时 GMT-4',
'每 周 在 星期日 在 10时, 12时 和 17时 GMT-4',
'每 周 在 星期日',
'每 小时',
'每 4 小时',
Expand All @@ -174,14 +174,14 @@ const expected = {
'每 年',
'每 年 在 第1 星期五',
'每 年 在 第13 星期五',
'每 日 在 5:30 PM EDT',
'每 周 在 星期一 和 星期三 在 10 AM 和 4 PM EDT',
'每 周 在 星期二 和 星期四 在 9:30 AM 和 3:30 PM EDT',
'每 日 在 17:30 GMT-4',
'每 周 在 星期一 和 星期三 在 10时 和 16时 GMT-4',
'每 周 在 星期二 和 星期四 在 9:30 和 15:30 GMT-4',
],
fr: [
'chaque jour',
'chaque jour à 10 AM, 12 PM et 5 PM EDT',
'chaque semaine le dimanche à 10 AM, 12 PM et 5 PM EDT',
'chaque jour à 10 h, 12 h et 17 h UTC−4',
'chaque semaine le dimanche à 10 h, 12 h et 17 h UTC−4',
'chaque semaine le dimanche',
'chaque heure',
'chaque 4 heures',
Expand All @@ -194,9 +194,9 @@ const expected = {
'chaque année',
'chaque année le 1er vendredi',
'chaque année le 13e vendredi',
'chaque jour à 5:30 PM EDT',
'chaque semaine le lundi et mercredi à 10 AM et 4 PM EDT',
'chaque semaine le mardi et jeudi à 9:30 AM et 3:30 PM EDT',
'chaque jour à 17:30 UTC−4',
'chaque semaine le lundi et mercredi à 10 h et 16 h UTC−4',
'chaque semaine le mardi et jeudi à 9:30 et 15:30 UTC−4',
],
};

Expand Down
11 changes: 11 additions & 0 deletions src/tests/totext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,15 @@ describe('RRuleTemporal.toText', () => {
});
expect(toText(rule)).toBe('every week on Thursday at 10:30 AM UTC');
});

test('should display the requested wall-clock time for DST spring-forward (nonexistent local time)', () => {
const rule = new RRuleTemporal({
dtstart: Temporal.ZonedDateTime.from('2025-03-09T02:30:00[America/New_York]'),
freq: 'YEARLY',
byHour: [2],
byMinute: [30],
});
const text = toText(rule, 'en');
expect(text).toContain('2:30'); // Should not display 3:30
});
});
50 changes: 34 additions & 16 deletions src/totext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,30 +638,46 @@ function formatByDayToken(tok: string | number, locale: LocaleData): string {
return `${ordinal(ord, locale)} ${name}`;
}

function formatTime(hour: number, minute = 0, second = 0): string {
const hr12 = ((hour + 11) % 12) + 1;
const ampm = hour < 12 ? 'AM' : 'PM';
const mm = String(minute).padStart(2, '0');
const ss = String(second).padStart(2, '0');
if (second) {
return `${hr12}:${mm}:${ss} ${ampm}`;
}
if (minute) {
return `${hr12}:${mm} ${ampm}`;
function formatTime(zdt: Temporal.ZonedDateTime, locale: string, hour: number, minute: number, second: number): string {
const options: Intl.DateTimeFormatOptions = {
hour: 'numeric',
timeZone: zdt.timeZoneId,
};

if (second) options.second = '2-digit';
if (second || minute) options.minute = '2-digit';

const ruleTime = Temporal.PlainTime.from({hour, minute, second});

let result;
try {
result = ruleTime.toLocaleString(locale, options);
} catch {
result = ruleTime.toLocaleString('en', options);
}
return `${hr12} ${ampm}`;

return result;
}

function weekdayTokenFromZdt(zdt: Temporal.ZonedDateTime): string {
return allowedWeekdays[zdt.dayOfWeek - 1]!;
}

function tzAbbreviation(zdt: Temporal.ZonedDateTime): string {
const parts = new Intl.DateTimeFormat('en-US', {
function tzAbbreviation(zdt: Temporal.ZonedDateTime, locale: string): string {
const options: Intl.DateTimeFormatOptions = {
timeZone: zdt.timeZoneId,
timeZoneName: 'short',
hour: 'numeric',
}).formatToParts(new Date(zdt.epochMilliseconds));
};

let formatter
try {
formatter = new Intl.DateTimeFormat(locale, options);
} catch {
formatter = Intl.DateTimeFormat('en', options);
}

const parts = formatter.formatToParts(new Date(zdt.epochMilliseconds));
const tzPart = parts.find((p) => p.type === 'timeZoneName');
return tzPart?.value || zdt.timeZoneId;
}
Expand Down Expand Up @@ -794,9 +810,11 @@ export function toText(input: RRuleTemporal | string, locale?: string, options:
if (textByHour) {
const minutes = textByMinute ?? [0];
const seconds = textBySecond ?? [0];
const times = textByHour.flatMap((h) => minutes.flatMap((m) => seconds.map((s) => formatTime(h, m, s))));
const times = textByHour.flatMap((h) =>
minutes.flatMap((m) => seconds.map((s) => formatTime(opts.dtstart, dateLocale, h, m, s))),
);
parts.push(data.words.at, list(times, undefined, data.words.and));
parts.push(tzAbbreviation(opts.dtstart));
parts.push(tzAbbreviation(opts.dtstart, dateLocale));
}

if (!textByHour && textByMinute) {
Expand Down