datezone
Version:
A lightweight and comprehensive date and timeZone utility library for JavaScript.
174 lines • 6.01 kB
JavaScript
import { timestampToCalendar } from "./calendar.pub.js";
import { daysInMonthBase } from "./month.pub.js";
import { getUTCtoTimezoneOffsetMinutes } from "./offset.pub.js";
import { isDST, isUTC } from "./timezone.pub.js";
/**
* Cache for recent interval calculations to avoid repeated timezone lookups
*/
const intervalCache = new Map();
const INTERVAL_CACHE_MAX_SIZE = 100;
const intervalCacheKeys = [];
/**
* Create cache key for interval calculations
*/
function createIntervalCacheKey(start, end, timeZone) {
return `${start}-${end}-${timeZone || "local"}`;
}
/**
* Optimized interval to duration for DST timezones
*/
function intervalToDurationDST(start, end, timeZone) {
const cacheKey = createIntervalCacheKey(start, end, timeZone);
const cached = intervalCache.get(cacheKey);
if (cached) {
return cached;
}
// Check if we can use a fast approximation
const duration = end - start;
const hours = Math.abs(duration) / (1000 * 60 * 60);
// For short durations (< 25 hours), offset changes are unlikely
if (hours < 25) {
const offsetStart = getUTCtoTimezoneOffsetMinutes(start, timeZone);
const offsetEnd = getUTCtoTimezoneOffsetMinutes(end, timeZone);
// If offsets are the same, we can use a faster calculation
if (offsetStart === offsetEnd) {
const startCal = timestampToCalendar(start, timeZone);
const endCal = timestampToCalendar(end, timeZone);
const result = intervalToDurationBase(startCal, endCal);
// Cache the result
if (intervalCache.size >= INTERVAL_CACHE_MAX_SIZE) {
const oldestKey = intervalCacheKeys.shift();
if (oldestKey)
intervalCache.delete(oldestKey);
}
intervalCache.set(cacheKey, result);
intervalCacheKeys.push(cacheKey);
return result;
}
}
// Fallback to full calculation for complex cases
const startCal = timestampToCalendar(start, timeZone);
const endCal = timestampToCalendar(end, timeZone);
return intervalToDurationBase(startCal, endCal);
}
/**
* Interval to duration.
*
* @param start The first timestamp (Unix epoch ms).
* @param end The second timestamp (Unix epoch ms).
* @param timeZone The IANA timeZone identifier or `null`/`undefined` to use
* @returns A partial {@link Calendar} where each defined field represents the
* @see https://datezone.dev/docs/reference/duration#intervaltoduration
*/
export function intervalToDuration(start, end, timeZone) {
// Ensure we always measure the positive interval
let _start = start;
let _end = end;
if (_end < _start) {
[_start, _end] = [_end, _start];
}
// Fast paths
if (!timeZone || isUTC(timeZone)) {
const startCal = timestampToCalendar(_start, timeZone);
const endCal = timestampToCalendar(_end, timeZone);
return intervalToDurationBase(startCal, endCal);
}
// Optimized DST calculation
if (isDST(timeZone)) {
return intervalToDurationDST(_start, _end, timeZone);
}
// Non-DST timezone (fixed offset)
const startCal = timestampToCalendar(_start, timeZone);
const endCal = timestampToCalendar(_end, timeZone);
return intervalToDurationBase(startCal, endCal);
}
/**
* Interval to duration base.
*
* @param startCal Calendar representation of the **earlier** instant.
* @param endCal Calendar representation of the **later** instant.
* @returns Calendar-like object containing the differences of each field.
* @see https://datezone.dev/docs/reference/duration#intervaltodurationbase
*/
export function intervalToDurationBase(startCal, endCal) {
let years = endCal.year - startCal.year;
let months = endCal.month - startCal.month;
let days = endCal.day - startCal.day;
let hours = endCal.hour - startCal.hour;
let minutes = endCal.minute - startCal.minute;
let seconds = endCal.second - startCal.second;
let milliseconds = endCal.millisecond - startCal.millisecond;
// Borrow chain for sub-units
if (milliseconds < 0) {
milliseconds += 1000;
seconds -= 1;
}
if (seconds < 0) {
seconds += 60;
minutes -= 1;
}
if (minutes < 0) {
minutes += 60;
hours -= 1;
}
if (hours < 0) {
hours += 24;
days -= 1;
}
if (days < 0) {
months -= 1;
// Determine the year/month preceding the end calendar date
let prevMonth = endCal.month - 1;
let prevYear = endCal.year;
if (prevMonth === 0) {
prevMonth = 12;
prevYear -= 1;
}
days += daysInMonthBase(prevYear, prevMonth);
}
if (months < 0) {
months += 12;
years -= 1;
}
return {
day: days,
hour: hours,
millisecond: milliseconds,
minute: minutes,
month: months,
second: seconds,
year: years,
};
}
/**
* Are intervals overlapping.
*
* @param start1 First interval start (inclusive).
* @param end1 First interval end (exclusive).
* @param start2 Second interval start (inclusive).
* @param end2 Second interval end (exclusive).
* @returns `true` if the intervals share at least one point.
* @see https://datezone.dev/docs/reference/duration#areintervalsoverlapping
*/
export function areIntervalsOverlapping(start1, end1, start2, end2) {
return start1 < end2 && start2 < end1;
}
/**
* Extracts the clamp from a timestamp.
*
* @param value The candidate value.
* @param min Lower bound.
* @param max Upper bound.
* @returns `min` if *value < min*, `max` if *value > max* otherwise the original value.
* @see https://datezone.dev/docs/reference/duration#clamp
*/
export function clamp(value, min, max) {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
//# sourceMappingURL=duration.pub.js.map