From 765d8e1297a85bd18a3ecaa1532ab1ec3db35da8 Mon Sep 17 00:00:00 2001 From: Louis Lam Date: Thu, 15 Dec 2022 13:39:48 +0800 Subject: [PATCH] Fix #2318 --- .../dayjs/{plugins => plugin}/timezone.d.ts | 0 .../dayjs/{plugins => plugin}/timezone.js | 2 +- server/server.js | 2 +- src/main.js | 2 +- src/modules/dayjs/constant.js | 25 +++ src/modules/dayjs/plugin/timezone/index.d.ts | 20 ++ src/modules/dayjs/plugin/timezone/index.js | 188 ++++++++++++++++++ 7 files changed, 236 insertions(+), 3 deletions(-) rename server/modules/dayjs/{plugins => plugin}/timezone.d.ts (100%) rename server/modules/dayjs/{plugins => plugin}/timezone.js (97%) create mode 100644 src/modules/dayjs/constant.js create mode 100644 src/modules/dayjs/plugin/timezone/index.d.ts create mode 100644 src/modules/dayjs/plugin/timezone/index.js diff --git a/server/modules/dayjs/plugins/timezone.d.ts b/server/modules/dayjs/plugin/timezone.d.ts similarity index 100% rename from server/modules/dayjs/plugins/timezone.d.ts rename to server/modules/dayjs/plugin/timezone.d.ts diff --git a/server/modules/dayjs/plugins/timezone.js b/server/modules/dayjs/plugin/timezone.js similarity index 97% rename from server/modules/dayjs/plugins/timezone.js rename to server/modules/dayjs/plugin/timezone.js index 73cdf58a5..894a59570 100644 --- a/server/modules/dayjs/plugins/timezone.js +++ b/server/modules/dayjs/plugin/timezone.js @@ -60,7 +60,7 @@ void 0 === t && (t = r); let n = this.utcOffset(); let i = this.toDate(); - let a = i.toLocaleString("en-US", { timeZone: t }); + let a = i.toLocaleString("en-US", { timeZone: t }).replace("\u202f", " "); let u = Math.round((i - new Date(a)) / 1e3 / 60); let f = o(a).$set("millisecond", this.$ms).utcOffset(15 * -Math.round(i.getTimezoneOffset() / 15) - u, !0); if (e) { diff --git a/server/server.js b/server/server.js index 5e4ff2b96..594c29b31 100644 --- a/server/server.js +++ b/server/server.js @@ -8,7 +8,7 @@ console.log("Welcome to Uptime Kuma"); // As the log function need to use dayjs, it should be very top const dayjs = require("dayjs"); dayjs.extend(require("dayjs/plugin/utc")); -dayjs.extend(require("dayjs/plugin/timezone")); +dayjs.extend(require("./modules/dayjs/plugin/timezone")); dayjs.extend(require("dayjs/plugin/customParseFormat")); // Check Node.js Version diff --git a/src/main.js b/src/main.js index 5567023fd..53314164c 100644 --- a/src/main.js +++ b/src/main.js @@ -17,7 +17,7 @@ import lang from "./mixins/lang"; import { router } from "./router"; import { appName } from "./util.ts"; import dayjs from "dayjs"; -import timezone from "dayjs/plugin/timezone"; +import timezone from "./modules/dayjs/plugin/timezone"; import utc from "dayjs/plugin/utc"; import relativeTime from "dayjs/plugin/relativeTime"; dayjs.extend(utc); diff --git a/src/modules/dayjs/constant.js b/src/modules/dayjs/constant.js new file mode 100644 index 000000000..02ffe1bce --- /dev/null +++ b/src/modules/dayjs/constant.js @@ -0,0 +1,25 @@ +export var SECONDS_A_MINUTE = 60; +export var SECONDS_A_HOUR = SECONDS_A_MINUTE * 60; +export var SECONDS_A_DAY = SECONDS_A_HOUR * 24; +export var SECONDS_A_WEEK = SECONDS_A_DAY * 7; +export var MILLISECONDS_A_SECOND = 1e3; +export var MILLISECONDS_A_MINUTE = SECONDS_A_MINUTE * MILLISECONDS_A_SECOND; +export var MILLISECONDS_A_HOUR = SECONDS_A_HOUR * MILLISECONDS_A_SECOND; +export var MILLISECONDS_A_DAY = SECONDS_A_DAY * MILLISECONDS_A_SECOND; +export var MILLISECONDS_A_WEEK = SECONDS_A_WEEK * MILLISECONDS_A_SECOND; // English locales + +export var MS = 'millisecond'; +export var S = 'second'; +export var MIN = 'minute'; +export var H = 'hour'; +export var D = 'day'; +export var W = 'week'; +export var M = 'month'; +export var Q = 'quarter'; +export var Y = 'year'; +export var DATE = 'date'; +export var FORMAT_DEFAULT = 'YYYY-MM-DDTHH:mm:ssZ'; +export var INVALID_DATE_STRING = 'Invalid Date'; // regex + +export var REGEX_PARSE = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/; +export var REGEX_FORMAT = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g; \ No newline at end of file diff --git a/src/modules/dayjs/plugin/timezone/index.d.ts b/src/modules/dayjs/plugin/timezone/index.d.ts new file mode 100644 index 000000000..8d9035905 --- /dev/null +++ b/src/modules/dayjs/plugin/timezone/index.d.ts @@ -0,0 +1,20 @@ +import { PluginFunc, ConfigType } from 'dayjs/esm' + +declare const plugin: PluginFunc +export = plugin + +declare module 'dayjs/esm' { + interface Dayjs { + tz(timezone?: string, keepLocalTime?: boolean): Dayjs + offsetName(type?: 'short' | 'long'): string | undefined + } + + interface DayjsTimezone { + (date: ConfigType, timezone?: string): Dayjs + (date: ConfigType, format: string, timezone?: string): Dayjs + guess(): string + setDefault(timezone?: string): void + } + + const tz: DayjsTimezone +} diff --git a/src/modules/dayjs/plugin/timezone/index.js b/src/modules/dayjs/plugin/timezone/index.js new file mode 100644 index 000000000..a7bd673e3 --- /dev/null +++ b/src/modules/dayjs/plugin/timezone/index.js @@ -0,0 +1,188 @@ +/** + * Copy from node_modules/dayjs/plugin/timezone.js + * Try to fix https://github.com/louislam/uptime-kuma/issues/2318 + * Source: https://github.com/iamkun/dayjs/tree/dev/src/plugin/utc + * License: MIT + */ +import { MIN, MS } from "../../constant"; +let typeToPos = { + year: 0, + month: 1, + day: 2, + hour: 3, + minute: 4, + second: 5 +}; // Cache time-zone lookups from Intl.DateTimeFormat, +// as it is a *very* slow method. + +let dtfCache = {}; + +let getDateTimeFormat = function getDateTimeFormat(timezone, options) { + if (options === void 0) { + options = {}; + } + + let timeZoneName = options.timeZoneName || "short"; + let key = timezone + "|" + timeZoneName; + let dtf = dtfCache[key]; + + if (!dtf) { + dtf = new Intl.DateTimeFormat("en-US", { + hour12: false, + timeZone: timezone, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + timeZoneName: timeZoneName + }); + dtfCache[key] = dtf; + } + + return dtf; +}; + +export default (function (o, c, d) { + let defaultTimezone; + + let makeFormatParts = function makeFormatParts(timestamp, timezone, options) { + if (options === void 0) { + options = {}; + } + + let date = new Date(timestamp); + let dtf = getDateTimeFormat(timezone, options); + return dtf.formatToParts(date); + }; + + let tzOffset = function tzOffset(timestamp, timezone) { + let formatResult = makeFormatParts(timestamp, timezone); + let filled = []; + + for (let i = 0; i < formatResult.length; i += 1) { + let _formatResult$i = formatResult[i]; + let type = _formatResult$i.type; + let value = _formatResult$i.value; + let pos = typeToPos[type]; + + if (pos >= 0) { + filled[pos] = parseInt(value, 10); + } + } + + let hour = filled[3]; // Workaround for the same behavior in different node version + // https://github.com/nodejs/node/issues/33027 + + /* istanbul ignore next */ + + let fixedHour = hour === 24 ? 0 : hour; + let utcString = filled[0] + "-" + filled[1] + "-" + filled[2] + " " + fixedHour + ":" + filled[4] + ":" + filled[5] + ":000"; + let utcTs = d.utc(utcString).valueOf(); + let asTS = +timestamp; + let 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 + + let fixOffset = function 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 + + let 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 + + let 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) ]; + }; + + let proto = c.prototype; + + proto.tz = function (timezone, keepLocalTime) { + if (timezone === void 0) { + timezone = defaultTimezone; + } + + let oldOffset = this.utcOffset(); + let date = this.toDate(); + let target = date.toLocaleString("en-US", { + timeZone: timezone + }).replace("\u202f", " "); + let diff = Math.round((date - new Date(target)) / 1000 / 60); + let ins = d(target).$set(MS, this.$ms).utcOffset(-Math.round(date.getTimezoneOffset() / 15) * 15 - diff, true); + + if (keepLocalTime) { + let newOffset = ins.utcOffset(); + ins = ins.add(oldOffset - newOffset, MIN); + } + + ins.$x.$timezone = timezone; + return ins; + }; + + proto.offsetName = function (type) { + // type: short(default) / long + let zone = this.$x.$timezone || d.tz.guess(); + let result = makeFormatParts(this.valueOf(), zone, { + timeZoneName: type + }).find(function (m) { + return m.type.toLowerCase() === "timezonename"; + }); + return result && result.value; + }; + + let oldStartOf = proto.startOf; + + proto.startOf = function (units, startOf) { + if (!this.$x || !this.$x.$timezone) { + return oldStartOf.call(this, units, startOf); + } + + let withoutTz = d(this.format("YYYY-MM-DD HH:mm:ss:SSS")); + let startOfWithoutTz = oldStartOf.call(withoutTz, units, startOf); + return startOfWithoutTz.tz(this.$x.$timezone, true); + }; + + d.tz = function (input, arg1, arg2) { + let parseFormat = arg2 && arg1; + let timezone = arg2 || arg1 || defaultTimezone; + let previousOffset = tzOffset(+d(), timezone); + + if (typeof input !== "string") { + // timestamp number || js Date || Day.js + return d(input).tz(timezone); + } + + let localTs = d.utc(input, parseFormat).valueOf(); + + let _fixOffset = fixOffset(localTs, previousOffset, timezone); + let targetTs = _fixOffset[0]; + let targetOffset = _fixOffset[1]; + + let 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; + }; +});