date-fns-tz
Version:
Time zone support for date-fns v3 with the Intl API
106 lines (105 loc) • 3.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.tzParseTimezone = void 0;
const index_js_1 = require("../tzTokenizeDate/index.js");
const index_js_2 = require("../newDateUTC/index.js");
const MILLISECONDS_IN_HOUR = 3600000;
const MILLISECONDS_IN_MINUTE = 60000;
const patterns = {
timezone: /([Z+-].*)$/,
timezoneZ: /^(Z)$/,
timezoneHH: /^([+-]\d{2})$/,
timezoneHHMM: /^([+-])(\d{2}):?(\d{2})$/,
};
// Parse constious time zone offset formats to an offset in milliseconds
function tzParseTimezone(timezoneString, date, isUtcDate) {
// Empty string
if (!timezoneString) {
return 0;
}
// Z
let token = patterns.timezoneZ.exec(timezoneString);
if (token) {
return 0;
}
let hours;
let absoluteOffset;
// ±hh
token = patterns.timezoneHH.exec(timezoneString);
if (token) {
hours = parseInt(token[1], 10);
if (!validateTimezone(hours)) {
return NaN;
}
return -(hours * MILLISECONDS_IN_HOUR);
}
// ±hh:mm or ±hhmm
token = patterns.timezoneHHMM.exec(timezoneString);
if (token) {
hours = parseInt(token[2], 10);
const minutes = parseInt(token[3], 10);
if (!validateTimezone(hours, minutes)) {
return NaN;
}
absoluteOffset = Math.abs(hours) * MILLISECONDS_IN_HOUR + minutes * MILLISECONDS_IN_MINUTE;
return token[1] === '+' ? -absoluteOffset : absoluteOffset;
}
// IANA time zone
if (isValidTimezoneIANAString(timezoneString)) {
date = new Date(date || Date.now());
const utcDate = isUtcDate ? date : toUtcDate(date);
const offset = calcOffset(utcDate, timezoneString);
const fixedOffset = isUtcDate ? offset : fixOffset(date, offset, timezoneString);
return -fixedOffset;
}
return NaN;
}
exports.tzParseTimezone = tzParseTimezone;
function toUtcDate(date) {
return (0, index_js_2.newDateUTC)(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
}
function calcOffset(date, timezoneString) {
const tokens = (0, index_js_1.tzTokenizeDate)(date, timezoneString);
// ms dropped because it's not provided by tzTokenizeDate
const asUTC = (0, index_js_2.newDateUTC)(tokens[0], tokens[1] - 1, tokens[2], tokens[3] % 24, tokens[4], tokens[5], 0).getTime();
let asTS = date.getTime();
const over = asTS % 1000;
asTS -= over >= 0 ? over : 1000 + over;
return asUTC - asTS;
}
function fixOffset(date, offset, timezoneString) {
const localTS = date.getTime();
// Our UTC time is just a guess because our offset is just a guess
let utcGuess = localTS - offset;
// Test whether the zone matches the offset for this ts
const o2 = calcOffset(new Date(utcGuess), timezoneString);
// If so, offset didn't change, and we're done
if (offset === o2) {
return offset;
}
// If not, change the ts by the difference in the offset
utcGuess -= o2 - offset;
// If that gives us the local time we want, we're done
const o3 = calcOffset(new Date(utcGuess), timezoneString);
if (o2 === o3) {
return o2;
}
// If it's different, we're in a hole time. The offset has changed, but we don't adjust the time
return Math.max(o2, o3);
}
function validateTimezone(hours, minutes) {
return -23 <= hours && hours <= 23 && (minutes == null || (0 <= minutes && minutes <= 59));
}
const validIANATimezoneCache = {};
function isValidTimezoneIANAString(timeZoneString) {
if (validIANATimezoneCache[timeZoneString])
return true;
try {
new Intl.DateTimeFormat(undefined, { timeZone: timeZoneString });
validIANATimezoneCache[timeZoneString] = true;
return true;
}
catch (error) {
return false;
}
}