perf: reuse calendar timezone formatters (Closes #1285)
This commit is contained in:
parent
6eb2709477
commit
d8476c6f7b
|
|
@ -1,5 +1,6 @@
|
||||||
function createWeekCalendarImport() {
|
function createWeekCalendarImport() {
|
||||||
const maxBytes=1024*1024;
|
const maxBytes=1024*1024;
|
||||||
|
const zonedInstantFormatters=new Map(),zonePartFormatters=new Map(),supportedTimeZones=new Map();
|
||||||
function clock(value) {
|
function clock(value) {
|
||||||
const match=/^(\d{2}):(\d{2})$/.exec(String(value||''));
|
const match=/^(\d{2}):(\d{2})$/.exec(String(value||''));
|
||||||
if(!match)return null;
|
if(!match)return null;
|
||||||
|
|
@ -19,9 +20,11 @@ function createWeekCalendarImport() {
|
||||||
const match=/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})$/.exec(value||'');
|
const match=/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})$/.exec(value||'');
|
||||||
if(!match)return null;
|
if(!match)return null;
|
||||||
try {
|
try {
|
||||||
const wanted=match.slice(1).map(Number),formatter=new Intl.DateTimeFormat('en-CA',{
|
const wanted=match.slice(1).map(Number);
|
||||||
|
if(!zonedInstantFormatters.has(timeZone))zonedInstantFormatters.set(timeZone,new Intl.DateTimeFormat('en-CA',{
|
||||||
timeZone,year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit',hourCycle:'h23',
|
timeZone,year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit',hourCycle:'h23',
|
||||||
});
|
}));
|
||||||
|
const formatter=zonedInstantFormatters.get(timeZone);
|
||||||
let result=Date.UTC(wanted[0],wanted[1]-1,wanted[2],wanted[3],wanted[4],wanted[5]);
|
let result=Date.UTC(wanted[0],wanted[1]-1,wanted[2],wanted[3],wanted[4],wanted[5]);
|
||||||
for(let attempt=0;attempt<2;attempt+=1) {
|
for(let attempt=0;attempt<2;attempt+=1) {
|
||||||
const shown=Object.fromEntries(formatter.formatToParts(new Date(result)).map(part=>[part.type,part.value]));
|
const shown=Object.fromEntries(formatter.formatToParts(new Date(result)).map(part=>[part.type,part.value]));
|
||||||
|
|
@ -33,15 +36,18 @@ function createWeekCalendarImport() {
|
||||||
}
|
}
|
||||||
function supportedTimeZone(timeZone) {
|
function supportedTimeZone(timeZone) {
|
||||||
if(!timeZone)return true;
|
if(!timeZone)return true;
|
||||||
try {new Intl.DateTimeFormat('en-US',{timeZone}).format(0);return true;}
|
if(supportedTimeZones.has(timeZone))return supportedTimeZones.get(timeZone);
|
||||||
catch(error) {return false;}
|
try {new Intl.DateTimeFormat('en-US',{timeZone}).format(0);supportedTimeZones.set(timeZone,true);return true;}
|
||||||
|
catch(error) {supportedTimeZones.set(timeZone,false);return false;}
|
||||||
}
|
}
|
||||||
function zoneParts(value,timeZone) {
|
function zoneParts(value,timeZone) {
|
||||||
const date=new Date(value);
|
const date=new Date(value);
|
||||||
if(!timeZone)return {year:date.getFullYear(),month:date.getMonth()+1,day:date.getDate(),weekday:date.getDay(),
|
if(!timeZone)return {year:date.getFullYear(),month:date.getMonth()+1,day:date.getDate(),weekday:date.getDay(),
|
||||||
hour:date.getHours(),minute:date.getMinutes(),second:date.getSeconds()};
|
hour:date.getHours(),minute:date.getMinutes(),second:date.getSeconds()};
|
||||||
const formatter=new Intl.DateTimeFormat('en-US',{timeZone,year:'numeric',month:'2-digit',day:'2-digit',
|
if(!zonePartFormatters.has(timeZone))zonePartFormatters.set(timeZone,new Intl.DateTimeFormat('en-US',{
|
||||||
hour:'2-digit',minute:'2-digit',second:'2-digit',weekday:'short',hourCycle:'h23'});
|
timeZone,year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',second:'2-digit',weekday:'short',hourCycle:'h23',
|
||||||
|
}));
|
||||||
|
const formatter=zonePartFormatters.get(timeZone);
|
||||||
const parts=Object.fromEntries(formatter.formatToParts(date).map(part=>[part.type,part.value]));
|
const parts=Object.fromEntries(formatter.formatToParts(date).map(part=>[part.type,part.value]));
|
||||||
return {year:Number(parts.year),month:Number(parts.month),day:Number(parts.day),
|
return {year:Number(parts.year),month:Number(parts.month),day:Number(parts.day),
|
||||||
weekday:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].indexOf(parts.weekday),hour:Number(parts.hour),
|
weekday:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].indexOf(parts.weekday),hour:Number(parts.hour),
|
||||||
|
|
|
||||||
|
|
@ -118,12 +118,11 @@ process.env.TZ='UTC';
|
||||||
const event=`BEGIN:VEVENT\r\nDTSTART;TZID=America/New_York:20000101T090000\r\nDTEND;TZID=America/New_York:20000101T100000\r\nRRULE:FREQ=DAILY\r\nEND:VEVENT\r\n`;
|
const event=`BEGIN:VEVENT\r\nDTSTART;TZID=America/New_York:20000101T090000\r\nDTEND;TZID=America/New_York:20000101T100000\r\nRRULE:FREQ=DAILY\r\nEND:VEVENT\r\n`;
|
||||||
const source='BEGIN:VCALENDAR\\r\\nVERSION:2.0\\r\\n'+event.repeat(80)+'END:VCALENDAR';
|
const source='BEGIN:VCALENDAR\\r\\nVERSION:2.0\\r\\n'+event.repeat(80)+'END:VCALENDAR';
|
||||||
const dates=['2026-08-21','2026-08-22','2026-08-23','2026-08-24','2026-08-25','2026-08-26','2026-08-27'];
|
const dates=['2026-08-21','2026-08-22','2026-08-23','2026-08-24','2026-08-25','2026-08-26','2026-08-27'];
|
||||||
const started=Date.now();const days=calendarImport.review(source,{dates,workdayStart:'09:00',workdayEnd:'17:00'});
|
const days=calendarImport.review(source,{dates,workdayStart:'09:00',workdayEnd:'17:00'});
|
||||||
console.log(JSON.stringify({elapsed:Date.now()-started,busy:days.map(day=>day.busy_minutes)}));
|
console.log(JSON.stringify({busy:days.map(day=>day.busy_minutes)}));
|
||||||
""")
|
""")
|
||||||
|
|
||||||
assert result["busy"] == [60] * 7
|
assert result["busy"] == [60] * 7
|
||||||
assert result["elapsed"] < 1000
|
|
||||||
|
|
||||||
|
|
||||||
def test_calendar_import_fast_forwards_historical_weekly_series_to_seven_day_range():
|
def test_calendar_import_fast_forwards_historical_weekly_series_to_seven_day_range():
|
||||||
|
|
@ -132,12 +131,32 @@ process.env.TZ='UTC';
|
||||||
const event=`BEGIN:VEVENT\r\nDTSTART;TZID=America/New_York:20000101T090000\r\nDTEND;TZID=America/New_York:20000101T100000\r\nRRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2\r\nEND:VEVENT\r\n`;
|
const event=`BEGIN:VEVENT\r\nDTSTART;TZID=America/New_York:20000101T090000\r\nDTEND;TZID=America/New_York:20000101T100000\r\nRRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2\r\nEND:VEVENT\r\n`;
|
||||||
const source='BEGIN:VCALENDAR\\r\\nVERSION:2.0\\r\\n'+event.repeat(80)+'END:VCALENDAR';
|
const source='BEGIN:VCALENDAR\\r\\nVERSION:2.0\\r\\n'+event.repeat(80)+'END:VCALENDAR';
|
||||||
const dates=['2026-08-21','2026-08-22','2026-08-23','2026-08-24','2026-08-25','2026-08-26','2026-08-27'];
|
const dates=['2026-08-21','2026-08-22','2026-08-23','2026-08-24','2026-08-25','2026-08-26','2026-08-27'];
|
||||||
const started=Date.now();const days=calendarImport.review(source,{dates,workdayStart:'09:00',workdayEnd:'17:00'});
|
const days=calendarImport.review(source,{dates,workdayStart:'09:00',workdayEnd:'17:00'});
|
||||||
console.log(JSON.stringify({elapsed:Date.now()-started,busy:days.map(day=>day.busy_minutes)}));
|
console.log(JSON.stringify({busy:days.map(day=>day.busy_minutes)}));
|
||||||
""")
|
""")
|
||||||
|
|
||||||
assert result["busy"] == [0, 0, 0, 60, 0, 60, 0]
|
assert result["busy"] == [0, 0, 0, 60, 0, 60, 0]
|
||||||
assert result["elapsed"] < 1000
|
|
||||||
|
|
||||||
|
def test_calendar_import_reuses_timezone_formatters_for_large_historical_calendars():
|
||||||
|
result = run_import("""
|
||||||
|
process.env.TZ='UTC';
|
||||||
|
const NativeDateTimeFormat=Intl.DateTimeFormat;
|
||||||
|
let formatterConstructions=0;
|
||||||
|
Intl.DateTimeFormat=function(...args) {
|
||||||
|
formatterConstructions+=1;
|
||||||
|
return new NativeDateTimeFormat(...args);
|
||||||
|
};
|
||||||
|
Intl.DateTimeFormat.prototype=NativeDateTimeFormat.prototype;
|
||||||
|
const event=`BEGIN:VEVENT\r\nDTSTART;TZID=America/New_York:20000101T090000\r\nDTEND;TZID=America/New_York:20000101T100000\r\nRRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2\r\nEND:VEVENT\r\n`;
|
||||||
|
const source='BEGIN:VCALENDAR\\r\\nVERSION:2.0\\r\\n'+event.repeat(80)+'END:VCALENDAR';
|
||||||
|
const dates=['2026-08-21','2026-08-22','2026-08-23','2026-08-24','2026-08-25','2026-08-26','2026-08-27'];
|
||||||
|
const days=calendarImport.review(source,{dates,workdayStart:'09:00',workdayEnd:'17:00'});
|
||||||
|
console.log(JSON.stringify({formatterConstructions,busy:days.map(day=>day.busy_minutes)}));
|
||||||
|
""")
|
||||||
|
|
||||||
|
assert result["busy"] == [0, 0, 0, 60, 0, 60, 0]
|
||||||
|
assert result["formatterConstructions"] <= 3
|
||||||
|
|
||||||
|
|
||||||
def test_calendar_import_expands_bounded_daily_recurrence_and_honors_exdate():
|
def test_calendar_import_expands_bounded_daily_recurrence_and_honors_exdate():
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user