Skip to content

Commit 294a97f

Browse files
committed
Event Timing Summaries: Redesign to support date/time locales and 24-hour time.
1 parent ff97285 commit 294a97f

1 file changed

Lines changed: 98 additions & 33 deletions

File tree

htdocs/js/utils.js

Lines changed: 98 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ function get_pretty_int_list(arr, ranges) {
3939
return arr.slice(0, arr.length - 1).join(', ') + ' and ' + arr[ arr.length - 1 ];
4040
}
4141

42+
function get_pretty_str_list(arr) {
43+
// compose string array to string using commas + spaces, and
44+
// the english "and" to group the final two elements
45+
if (!arr || !arr.length) return '';
46+
if (arr.length == 1) return arr[0].toString();
47+
return arr.slice(0, arr.length - 1).join(', ') + ' and ' + arr[ arr.length - 1 ];
48+
}
49+
4250
function summarize_event_timings(event) {
4351
// summarize all event triggers from event into human-readable string
4452
// separate schedule items and options
@@ -87,63 +95,122 @@ function summarize_event_timing(trigger, idx) {
8795

8896
// years
8997
var year_str = '';
98+
var date_opts = app.getDateOptions({ timeZone: 'UTC' });
99+
var locale = date_opts.locale;
100+
var lang = (locale || '').split(/\-/)[0];
101+
var is_english = (lang == 'en');
102+
var numbering = date_opts.numberingSystem;
103+
104+
function build_formatter(extra) {
105+
var opts = Object.assign({}, date_opts, extra);
106+
var loc = opts.locale;
107+
delete opts.locale;
108+
return new Intl.DateTimeFormat(loc, opts);
109+
}
110+
var month_fmt = build_formatter({ month: 'long' });
111+
var wday_fmt = build_formatter({ weekday: 'long' });
112+
var hour_fmt = build_formatter({ hour: 'numeric' });
113+
var hm_fmt = build_formatter({ hour: 'numeric', minute: '2-digit' });
114+
var num_fmt = new Intl.NumberFormat(locale, { useGrouping: false, numberingSystem: numbering });
115+
var num2_fmt = new Intl.NumberFormat(locale, { useGrouping: false, numberingSystem: numbering, minimumIntegerDigits: 2 });
116+
117+
function format_number(num, min_digits) {
118+
return (min_digits ? num2_fmt : num_fmt).format(num);
119+
}
120+
function format_ordinal(num) {
121+
// keep English ordinal suffixes, but localize digits
122+
var suffix = 'th';
123+
if ((num % 100 < 11) || (num % 100 > 13)) {
124+
switch (num % 10) {
125+
case 1: suffix = 'st'; break;
126+
case 2: suffix = 'nd'; break;
127+
case 3: suffix = 'rd'; break;
128+
}
129+
}
130+
return format_number(num) + suffix;
131+
}
132+
function format_month_name(num) {
133+
return month_fmt.format( new Date(Date.UTC(2020, num - 1, 1)) );
134+
}
135+
function format_weekday_name(num) {
136+
return wday_fmt.format( new Date(Date.UTC(2020, 7, 2 + num)) );
137+
}
138+
function normalize_time_label(text) {
139+
if (!is_english) return text;
140+
return text.replace(/\s+/g, '').replace(/am|pm/i, function(m_all) { return m_all.toLowerCase(); });
141+
}
142+
function format_hour_name(num) {
143+
return normalize_time_label( hour_fmt.format( new Date(Date.UTC(2020, 0, 1, num, 0)) ) );
144+
}
145+
function format_time_name(hour, minute) {
146+
return normalize_time_label( hm_fmt.format( new Date(Date.UTC(2020, 0, 1, hour, minute)) ) );
147+
}
148+
function format_weekday_label(num) {
149+
var name = format_weekday_name(num);
150+
return is_english ? (name + 's') : name;
151+
}
152+
function format_month_day(num) {
153+
if (num < 0) {
154+
if (num == -1) return 'last day';
155+
return format_ordinal(Math.abs(num)) + ' last day';
156+
}
157+
return format_ordinal(num);
158+
}
159+
function format_minute_label(num) {
160+
if (num == 0) return 'hour';
161+
if (num == 30) return 'half-hour';
162+
return ':' + format_number(num, 2);
163+
}
164+
90165
if (trigger.years && trigger.years.length) {
91-
year_str = get_pretty_int_list(trigger.years, true);
166+
year_str = get_pretty_int_list(trigger.years, true).replace(/(\d+)/g, function(m_all, m_g1) {
167+
return format_number( parseInt(m_g1) );
168+
});
92169
}
93170

94171
// months
95172
var mon_str = '';
96173
if (trigger.months && trigger.months.length) {
97174
mon_str = get_pretty_int_list(trigger.months, true).replace(/(\d+)/g, function(m_all, m_g1) {
98-
return _months[ parseInt(m_g1) - 1 ][1];
175+
return format_month_name( parseInt(m_g1) );
99176
});
100177
}
101178

102179
// days
103180
var mday_str = '';
104181
if (trigger.days && trigger.days.length) {
105182
mday_str = get_pretty_int_list(trigger.days, true).replace(/(\-?\d+)/g, function(m_all, m_g1) {
106-
var result = '';
107-
switch (m_g1) {
108-
case '-1': result = 'last day'; break;
109-
case '-2': result = '2nd last day'; break;
110-
case '-3': result = '3rd last day'; break;
111-
case '-4': result = '4th last day'; break;
112-
case '-5': result = '5th last day'; break;
113-
case '-6': result = '6th last day'; break;
114-
case '-7': result = '7th last day'; break;
115-
default:
116-
if (m_g1.match(/^1[1-9]$/)) result = m_g1 + 'th'; // teens break the rule (11th, 12th, 13th, etc.)
117-
else result = m_g1 + _number_suffixes[ parseInt( m_g1.substring(m_g1.length - 1) ) ];
118-
break;
119-
}
120-
return result;
183+
return format_month_day( parseInt(m_g1) );
121184
});
122185
}
123186

124187
// weekdays
125188
var wday_str = '';
126189
if (trigger.weekdays && trigger.weekdays.length) {
127-
wday_str = get_pretty_int_list(trigger.weekdays, true).replace(/(\d+)/g, function(m_all, m_g1) {
128-
return _day_names[ parseInt(m_g1) ] + 's';
129-
});
130-
wday_str = wday_str.replace(/Mondays\s+\-\s+Fridays/, 'weekdays');
190+
var wdays = deep_copy_object(trigger.weekdays).sort( function(a, b) { return a - b; } );
191+
if ((wdays.length == 5) && (wdays[0] == 1) && (wdays[4] == 5)) {
192+
wday_str = 'weekdays';
193+
}
194+
else {
195+
wday_str = get_pretty_int_list(wdays, true).replace(/(\d+)/g, function(m_all, m_g1) {
196+
return format_weekday_label( parseInt(m_g1) );
197+
});
198+
}
131199
}
132200

133201
// hours
134202
var hour_str = '';
135203
if (trigger.hours && trigger.hours.length) {
136204
hour_str = get_pretty_int_list(trigger.hours, true).replace(/(\d+)/g, function(m_all, m_g1) {
137-
return _hour_names[ parseInt(m_g1) ];
205+
return format_hour_name( parseInt(m_g1) );
138206
});
139207
}
140208

141209
// minutes
142210
var min_str = '';
143211
if (trigger.minutes && trigger.minutes.length) {
144-
min_str = get_pretty_int_list(trigger.minutes, false).replace(/(\d+)/g, function(m_all, m_g1) {
145-
return ':' + ((m_g1.length == 1) ? ('0'+m_g1) : m_g1);
146-
});
212+
var mins = deep_copy_object(trigger.minutes).sort( function(a, b) { return a - b; } );
213+
min_str = get_pretty_str_list( mins.map( function(min) { return format_minute_label(min); } ) );
147214
}
148215

149216
// construct final string
@@ -173,10 +240,7 @@ function summarize_event_timing(trigger, idx) {
173240

174241
// compress single hour + single minute
175242
if (trigger.hours && trigger.hours.length == 1 && trigger.minutes && trigger.minutes.length == 1) {
176-
hour_str.match(/^(\d+)(\w+)$/);
177-
var hr = RegExp.$1;
178-
var ampm = RegExp.$2;
179-
var new_str = hr + min_str + ampm;
243+
var new_str = format_time_name(trigger.hours[0], trigger.minutes[0]);
180244

181245
if (mday_str || wday_str) groups.push( 'at ' + new_str );
182246
else groups.push( 'daily at ' + new_str );
@@ -194,8 +258,7 @@ function summarize_event_timing(trigger, idx) {
194258
if (interval) {
195259
var new_str = 'every ' + interval + ' minutes';
196260
if (trigger.minutes[0] > 0) {
197-
var m_g1 = trigger.minutes[0].toString();
198-
new_str += ' starting on the :' + ((m_g1.length == 1) ? ('0'+m_g1) : m_g1);
261+
new_str += ' starting on the :' + format_number(trigger.minutes[0], 2);
199262
}
200263
groups.push( new_str );
201264
min_added = true;
@@ -208,15 +271,17 @@ function summarize_event_timing(trigger, idx) {
208271
}
209272

210273
if (!min_added) {
211-
if (min_str) groups.push( 'on the ' + min_str.replace(/\:00/, 'hour').replace(/\:30/, 'half-hour') );
274+
if (min_str) groups.push( 'on the ' + min_str );
212275
else groups.push( 'every minute' );
213276
}
214277
}
215278

216279
var text = (typeof(idx) != 'undefined') ? groups.join(' ') : groups.join(', ');
217280
var output = text;
218281
if (!idx) output = text.substring(0, 1).toUpperCase() + text.substring(1, text.length);
219-
if (trigger.timezone) output += ' (' + trigger.timezone + ')';
282+
var timing_tz = trigger.timezone || app.config.tz;
283+
var user_tz = (app.user && app.user.timezone) ? app.user.timezone : Intl.DateTimeFormat().resolvedOptions().timeZone;
284+
if (timing_tz && user_tz && (timing_tz != user_tz)) output += ' (' + timing_tz + ')';
220285

221286
return output;
222287
};

0 commit comments

Comments
 (0)