-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added caching logic in days util function (#2343)
- Loading branch information
1 parent
13b138d
commit 569da74
Showing
3 changed files
with
282 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import pureDayjs from "dayjs"; | ||
import localeData from "dayjs/plugin/localeData"; | ||
import utc from "dayjs/plugin/utc"; | ||
import weekday from "dayjs/plugin/weekday"; | ||
import weekOfYear from "dayjs/plugin/weekOfYear"; | ||
|
||
import timezone from "./timezonePlugin"; | ||
|
||
pureDayjs.extend(weekOfYear); | ||
pureDayjs.extend(weekday); | ||
pureDayjs.extend(localeData); | ||
pureDayjs.extend(utc); | ||
pureDayjs.extend(timezone); | ||
|
||
class LRUCache { | ||
constructor(limit) { | ||
this.limit = limit; | ||
this.cache = new Map(); | ||
} | ||
|
||
get(key) { | ||
const value = this.cache.get(key); | ||
this.cache.delete(key); | ||
this.cache.set(key, value); | ||
|
||
return value; | ||
} | ||
|
||
set(key, value) { | ||
if (this.cache.size >= this.limit) { | ||
const oldestKey = this.cache.keys().next().value; | ||
this.cache.delete(oldestKey); | ||
} | ||
this.cache.set(key, value); | ||
} | ||
|
||
has(key) { | ||
return this.cache.has(key); | ||
} | ||
} | ||
|
||
const hasTimezone = dateString => { | ||
const timezoneRegex = /Z|[+-]\d{2}:\d{2}$|[+-]\d{4}$|GMT([+-]\d{4})?$/; | ||
|
||
return timezoneRegex.test(dateString); | ||
}; | ||
|
||
const cache = new LRUCache(1000); | ||
|
||
const dayjs = (...args) => { | ||
const cacheKey = JSON.stringify(args); | ||
|
||
if (cache.has(cacheKey) && args[0] !== undefined) return cache.get(cacheKey); | ||
|
||
const userTimezone = pureDayjs.tz().$x.$timezone; | ||
const browserTimezone = pureDayjs.tz.guess(); | ||
const timezone = userTimezone || browserTimezone; | ||
|
||
if (userTimezone === browserTimezone || userTimezone === undefined) { | ||
const result = pureDayjs(...args); | ||
|
||
if (args[0] !== undefined) cache.set(cacheKey, result); | ||
|
||
return result; | ||
} | ||
|
||
if (args.length > 0 && (typeof args[0] === "string" || args[0] === null)) { | ||
const pureDayjsArgs = args.slice(0, Math.min(args.length, 2)); | ||
|
||
if (hasTimezone(args[0])) { | ||
args[0] = pureDayjs(...pureDayjsArgs); | ||
} else { | ||
args[0] = pureDayjs(...pureDayjsArgs).format("YYYY-MM-DD HH:mm:ss"); | ||
args[1] = "YYYY-MM-DD HH:mm:ss"; | ||
} | ||
} | ||
|
||
if (args[0]?.toString() === "Invalid Date") { | ||
const result = pureDayjs(...args); | ||
|
||
if (args[0] !== undefined) cache.set(cacheKey, result); | ||
|
||
return result; | ||
} | ||
|
||
const result = | ||
args.length === 2 ? pureDayjs.tz(...args, timezone) : pureDayjs.tz(...args); | ||
|
||
if (args[0] !== undefined) cache.set(cacheKey, result); | ||
|
||
return result; | ||
}; | ||
|
||
Object.assign(dayjs, { ...pureDayjs }); | ||
|
||
export default dayjs; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
const typeToPos = { | ||
year: 0, | ||
month: 1, | ||
day: 2, | ||
hour: 3, | ||
minute: 4, | ||
second: 5, | ||
}; | ||
|
||
const dateTimeFormatDefaults = { | ||
year: "numeric", | ||
month: "2-digit", | ||
day: "2-digit", | ||
hour: "2-digit", | ||
minute: "2-digit", | ||
second: "2-digit", | ||
}; | ||
|
||
// Cache time-zone lookups from Intl.DateTimeFormat, | ||
// as it is a *very* slow method. | ||
const dtfCache = {}; | ||
const getDateTimeFormat = (timezone, options = {}) => { | ||
const timeZoneName = options.timeZoneName || "short"; | ||
const key = `${timezone}|${timeZoneName}`; | ||
let dtf = dtfCache[key]; | ||
if (!dtf) { | ||
dtf = new Intl.DateTimeFormat("en-US", { | ||
...dateTimeFormatDefaults, | ||
hour12: false, | ||
timeZone: timezone, | ||
timeZoneName, | ||
}); | ||
dtfCache[key] = dtf; | ||
} | ||
|
||
return dtf; | ||
}; | ||
|
||
const localeStringifierCache = {}; | ||
const getLocaleStringifier = timezone => { | ||
let localeStringifier = localeStringifierCache[timezone]; | ||
if (!localeStringifier) { | ||
localeStringifier = new Intl.DateTimeFormat("en-US", { | ||
...dateTimeFormatDefaults, | ||
timeZone: timezone, | ||
}); | ||
localeStringifierCache[timezone] = localeStringifier; | ||
} | ||
|
||
return localeStringifier; | ||
}; | ||
|
||
// eslint-disable-next-line no-unused-vars | ||
export default (o, c, d) => { | ||
let defaultTimezone; | ||
|
||
const makeFormatParts = (timestamp, timezone, options = {}) => { | ||
const date = new Date(timestamp); | ||
const dtf = getDateTimeFormat(timezone, options); | ||
|
||
return dtf.formatToParts(date); | ||
}; | ||
|
||
const tzOffset = (timestamp, timezone) => { | ||
const formatResult = makeFormatParts(timestamp, timezone); | ||
const filled = []; | ||
for (let i = 0; i < formatResult.length; i += 1) { | ||
const { type, value } = formatResult[i]; | ||
const pos = typeToPos[type]; | ||
|
||
if (pos >= 0) { | ||
filled[pos] = parseInt(value, 10); | ||
} | ||
} | ||
const hour = filled[3]; | ||
// Workaround for the same behavior in different node version | ||
// https://github.com/nodejs/node/issues/33027 | ||
/* istanbul ignore next */ | ||
const fixedHour = hour === 24 ? 0 : hour; | ||
const utcString = `${filled[0]}-${filled[1]}-${filled[2]} ${fixedHour}:${filled[4]}:${filled[5]}:000`; | ||
const utcTs = d.utc(utcString).valueOf(); | ||
let asTS = Number(timestamp); | ||
const over = asTS % 1000; | ||
asTS -= over; | ||
|
||
return (utcTs - asTS) / (60 * 1000); | ||
}; | ||
|
||
// find the right offset a given local time. The o input is our guess, which determines which | ||
// offset we'll pick in ambiguous cases (e.g. there are two 3 AMs b/c Fallback DST) | ||
// https://github.com/moment/luxon/blob/master/src/datetime.js#L76 | ||
const fixOffset = (localTS, o0, tz) => { | ||
// Our UTC time is just a guess because our offset is just a guess | ||
let utcGuess = localTS - o0 * 60 * 1000; | ||
// Test whether the zone matches the offset for this ts | ||
const o2 = tzOffset(utcGuess, tz); | ||
// If so, offset didn't change and we're done | ||
if (o0 === o2) { | ||
return [utcGuess, o0]; | ||
} | ||
// If not, change the ts by the difference in the offset | ||
utcGuess -= (o2 - o0) * 60 * 1000; | ||
// If that gives us the local time we want, we're done | ||
const o3 = tzOffset(utcGuess, tz); | ||
if (o2 === o3) { | ||
return [utcGuess, o2]; | ||
} | ||
|
||
// If it's different, we're in a hole time. | ||
// The offset has changed, but the we don't adjust the time | ||
return [localTS - Math.min(o2, o3) * 60 * 1000, Math.max(o2, o3)]; | ||
}; | ||
|
||
const proto = c.prototype; | ||
|
||
// eslint-disable-next-line default-param-last | ||
proto.tz = function (timezone = defaultTimezone, keepLocalTime) { | ||
const oldOffset = this.utcOffset(); | ||
const date = this.toDate(); | ||
const target = getLocaleStringifier(timezone).format(date); | ||
const diff = Math.round((date - new Date(target)) / 1000 / 60); | ||
let ins = d(target) | ||
.$set("millisecond", this.$ms) | ||
.utcOffset(-Math.round(date.getTimezoneOffset() / 15) * 15 - diff, true); | ||
if (keepLocalTime) { | ||
const newOffset = ins.utcOffset(); | ||
ins = ins.add(oldOffset - newOffset, "minute"); | ||
} | ||
ins.$x.$timezone = timezone; | ||
|
||
return ins; | ||
}; | ||
|
||
proto.offsetName = function (type) { | ||
// type: short(default) / long | ||
const zone = this.$x.$timezone || d.tz.guess(); | ||
const result = makeFormatParts(this.valueOf(), zone, { | ||
timeZoneName: type, | ||
}).find(m => m.type.toLowerCase() === "timezonename"); | ||
|
||
return result && result.value; | ||
}; | ||
|
||
const oldStartOf = proto.startOf; | ||
proto.startOf = function (units, startOf) { | ||
if (!this.$x || !this.$x.$timezone) { | ||
return oldStartOf.call(this, units, startOf); | ||
} | ||
|
||
const withoutTz = d(this.format("YYYY-MM-DD HH:mm:ss:SSS")); | ||
const startOfWithoutTz = oldStartOf.call(withoutTz, units, startOf); | ||
|
||
return startOfWithoutTz.tz(this.$x.$timezone, true); | ||
}; | ||
|
||
d.tz = function (input, arg1, arg2) { | ||
const parseFormat = arg2 && arg1; | ||
const timezone = arg2 || arg1 || defaultTimezone; | ||
const previousOffset = tzOffset(Number(d()), timezone); | ||
if (typeof input !== "string") { | ||
// timestamp number || js Date || Day.js | ||
return d(input).tz(timezone); | ||
} | ||
const localTs = d.utc(input, parseFormat).valueOf(); | ||
const [targetTs, targetOffset] = fixOffset( | ||
localTs, | ||
previousOffset, | ||
timezone | ||
); | ||
const ins = d(targetTs).utcOffset(targetOffset); | ||
ins.$x.$timezone = timezone; | ||
|
||
return ins; | ||
}; | ||
|
||
d.tz.guess = function () { | ||
return Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
}; | ||
|
||
d.tz.setDefault = function (timezone) { | ||
defaultTimezone = timezone; | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters