After reviewing @VincenzoC's comment, I have come up with a simple approach to calculate offsets
and untils
(as per the moment-timezone
library) for a given time range. This method assumes that offsets change only on the hour, allowing us to iterate through the time range in one-hour increments.
import { IANAZone } from 'luxon';
const getZoneInfo = function (id, startTime, endTime) {
startTime = Math.floor(startTime / 36e5) * 36e5; // adjust to the previous whole hour
endTime = Math.ceil(endTime / 36e5) * 36e5; // adjust to the next whole hour
const increment = 36e5; // one-hour increment
const zone = IANAZone.create(id);
const offsets = [];
const untils = [];
let offset, prevOffset;
for (let time = startTime; time <= endTime; time += increment) {
offset = zone.offset(time);
if (prevOffset != undefined && offset != prevOffset || time == endTime) {
offsets.push(-prevOffset);
untils.push(time);
}
prevOffset = offset;
}
const zoneInfo = {
id: id,
offsets: offsets,
untils: untils
};
console.log('zoneInfo', zoneInfo);
return zoneInfo;
};
const startTime = 1646931245000;
const endTime = 1650473645000;
getZoneInfo('Europe/London', startTime, endTime);
getZoneInfo('Europe/Stockholm', startTime, endTime);
getZoneInfo('Asia/Tokyo', startTime, endTime);
getZoneInfo('America/New_York', startTime, endTime);
Output:
zoneInfo {
id: 'Europe/London',
offsets: [ -0, -60 ],
untils: [ 1648342800000, 1650474000000 ]
}
zoneInfo {
id: 'Europe/Stockholm',
offsets: [ -60, -120 ],
untils: [ 1648342800000, 1650474000000 ]
}
zoneInfo {
id: 'Asia/Tokyo',
offsets: [ -540 ],
untils: [ 1650474000000 ]
}
zoneInfo {
id: 'America/New_York',
offsets: [ 300, 240 ],
untils: [ 1647154800000, 1650474000000 ]
}
The last until
value does not indicate an actual offset
change but ensures full coverage of the specified time range up to endTime
.
UPDATE
Here is an alternative version of the above code, which aims to be more efficient (especially for larger time ranges) by iterating in full days and switching to hourly increments only when necessary to identify offset changes accurately:
import { IANAZone } from 'luxon';
const getZoneInfo = function (id, startTime, endTime) {
const zone = IANAZone.create(id);
const offsets = [];
const untils = [];
startTime = Math.floor(startTime / 36e5) * 36e5; // move to the previous whole hour
endTime = Math.ceil(endTime / 36e5) * 36e5; // move to the next whole hour
const stepSmall = 36e5; // one-hour increment
const stepLarge = 24 * 36e5; // one-day increment
let step = stepLarge; // start with the larger increment for speed
let offset, prevOffset, offsetChanged;
let time = startTime;
offset = prevOffset = zone.offset(time);
do {
time = Math.min(time + step, endTime);
offset = zone.offset(time);
offsetChanged = (offset != prevOffset);
if (offsetChanged || time == endTime) {
if (offsetChanged && step == stepLarge) {
// go back and switch to the smaller increment for better transition accuracy...
time = time - stepLarge;
step = stepSmall;
continue;
} else {
offsets.push(-prevOffset);
untils.push(time);
// revert to the larger increment...
step = stepLarge;
}
}
prevOffset = offset;
} while (time < endTime);
const zoneInfo = {
id: id,
offsets: offsets,
untils: untils
};
console.log('zoneInfo', zoneInfo);
return zoneInfo;
};
const startTime = 1608020493000;
const endTime = 1734250893000;
getZoneInfo('Europe/London', startTime, endTime);
getZoneInfo('Europe/Stockholm', startTime, endTime);
getZoneInfo('Asia/Tokyo', startTime, endTime);
getZoneInfo('America/New_York', startTime, endTime);
Output:
zoneInfo {
id: 'Europe/London',
offsets: [
-0, -60, -0, -60,
-0, -60, -0, -60,
-0
],
untils: [
1616893200000,
1635642000000,
1648342800000,
1667091600000,
1679792400000,
1698541200000,
1711846800000,
1729990800000,
1734253200000
]
}
zoneInfo {
id: 'Europe/Stockholm',
offsets: [
-60, -120, -60,
-120, -60, -120,
-60, -120, -60
],
untils: [
1616893200000,
1635642000000,
1648342800000,
1667091600000,
1679792400000,
1698541200000,
1711846800000,
1729990800000,
1734253200000
]
}
zoneInfo {
id: 'Asia/Tokyo',
offsets: [ -540 ],
untils: [ 1734253200000 ]
}
zoneInfo {
id: 'America/New_York',
offsets: [
300, 240, 300,
240, 300, 240,
300, 240, 300
],
untils: [
1615705200000,
1636264800000,
1647154800000,
1667714400000,
1678604400000,
1699164000000,
1710054000000,
1730613600000,
1734253200000
]
}