diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/chat/modules/ToLocaleFormat.sys.mjs | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/chat/modules/ToLocaleFormat.sys.mjs')
-rw-r--r-- | comm/chat/modules/ToLocaleFormat.sys.mjs | 208 |
1 files changed, 208 insertions, 0 deletions
diff --git a/comm/chat/modules/ToLocaleFormat.sys.mjs b/comm/chat/modules/ToLocaleFormat.sys.mjs new file mode 100644 index 0000000000..256a6fb5f0 --- /dev/null +++ b/comm/chat/modules/ToLocaleFormat.sys.mjs @@ -0,0 +1,208 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * JS implementation of the deprecated Date.toLocaleFormat. + * aFormat follows strftime syntax, + * http://pubs.opengroup.org/onlinepubs/007908799/xsh/strftime.html + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; +XPCOMUtils.defineLazyGetter( + lazy, + "dateTimeFormatter", + () => + new Services.intl.DateTimeFormat(undefined, { + dateStyle: "full", + timeStyle: "long", + }) +); +XPCOMUtils.defineLazyGetter( + lazy, + "dateFormatter", + () => + new Services.intl.DateTimeFormat(undefined, { + dateStyle: "full", + }) +); +XPCOMUtils.defineLazyGetter( + lazy, + "timeFormatter", + () => + new Services.intl.DateTimeFormat(undefined, { + timeStyle: "long", + }) +); + +function Day(t) { + return Math.floor(t.valueOf() / 86400000); +} +function DayFromYear(y) { + return ( + 365 * (y - 1970) + + Math.floor((y - 1969) / 4) - + Math.floor((y - 1901) / 100) + + Math.floor((y - 1601) / 400) + ); +} +function DayWithinYear(t) { + return Day(t) - DayFromYear(t.getFullYear()); +} +function weekday(aDate, option) { + return aDate.toLocaleString(undefined, { weekday: option }); +} +function month(aDate, option) { + return aDate.toLocaleString(undefined, { month: option }); +} +function hourMinSecTwoDigits(aDate) { + return aDate.toLocaleString(undefined, { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); +} +function dayPeriod(aDate) { + let dtf = Intl.DateTimeFormat(undefined, { hour: "2-digit" }); + let dayPeriodPart = + dtf.resolvedOptions().hour12 && + dtf.formatToParts(aDate).find(part => part.type === "dayPeriod"); + return dayPeriodPart ? dayPeriodPart.value : ""; +} +function weekNumber(aDate, weekStart) { + let day = aDate.getDay(); + if (weekStart) { + day = (day || 7) - weekStart; + } + return Math.max(Math.floor((DayWithinYear(aDate) + 7 - day) / 7), 0); +} +function weekNumberISO(t) { + let thisWeek = weekNumber(1, t); + let firstDayOfYear = (new Date(t.getFullYear(), 0, 1).getDay() || 7) - 1; + if (thisWeek === 0 && firstDayOfYear >= 4) { + return weekNumberISO(new Date(t.getFullYear() - 1, 11, 31)); + } + if (t.getMonth() === 11 && t.getDate() - ((t.getDay() || 7) - 1) >= 29) { + return 1; + } + return thisWeek + (firstDayOfYear > 0 && firstDayOfYear < 4); +} +function weekYearISO(aDate) { + let thisWeek = weekNumber(1, aDate); + let firstDayOfYear = (new Date(aDate.getFullYear(), 0, 1).getDay() || 7) - 1; + if (thisWeek === 0 && firstDayOfYear >= 4) { + return aDate.getFullYear() - 1; + } + if ( + aDate.getMonth() === 11 && + aDate.getDate() - ((aDate.getDay() || 7) - 1) >= 29 + ) { + return aDate.getFullYear() + 1; + } + return aDate.getFullYear(); +} +function timeZoneOffset(aDate) { + let offset = aDate.getTimezoneOffset(); + let tzoff = Math.floor(Math.abs(offset) / 60) * 100 + (Math.abs(offset) % 60); + return (offset < 0 ? "+" : "-") + String(tzoff).padStart(4, "0"); +} +function timeZone(aDate) { + let dtf = Intl.DateTimeFormat(undefined, { timeZoneName: "short" }); + let timeZoneNamePart = dtf + .formatToParts(aDate) + .find(part => part.type === "timeZoneName"); + return timeZoneNamePart ? timeZoneNamePart.value : ""; +} + +const formatFunctions = { + a: aDate => weekday(aDate, "short"), + A: aDate => weekday(aDate, "long"), + b: aDate => month(aDate, "short"), + B: aDate => month(aDate, "long"), + c: aDate => lazy.dateTimeFormatter.format(aDate), + C: aDate => String(Math.trunc(aDate.getFullYear() / 100)), + d: aDate => String(aDate.getDate()), + D: aDate => ToLocaleFormat("%m/%d/%y", aDate), + e: aDate => String(aDate.getDate()), + F: aDate => ToLocaleFormat("%Y-%m-%d", aDate), + g: aDate => String(weekYearISO(aDate) % 100), + G: aDate => String(weekYearISO(aDate)), + h: aDate => month(aDate, "short"), + H: aDate => String(aDate.getHours()), + I: aDate => String(aDate.getHours() % 12 || 12), + j: aDate => String(DayWithinYear(aDate) + 1), + k: aDate => String(aDate.getHours()), + l: aDate => String(aDate.getHours() % 12 || 12), + m: aDate => String(aDate.getMonth() + 1), + M: aDate => String(aDate.getMinutes()), + n: () => "\n", + p: aDate => dayPeriod(aDate).toLocaleUpperCase(), + P: aDate => dayPeriod(aDate).toLocaleLowerCase(), + r: aDate => hourMinSecTwoDigits(aDate), + R: aDate => ToLocaleFormat("%H:%M", aDate), + s: aDate => String(Math.trunc(aDate.getTime() / 1000)), + S: aDate => String(aDate.getSeconds()), + t: () => "\t", + T: aDate => ToLocaleFormat("%H:%M:%S", aDate), + u: aDate => String(aDate.getDay() || 7), + U: aDate => String(weekNumber(aDate, 0)), + V: aDate => String(weekNumberISO(aDate)), + w: aDate => String(aDate.getDay()), + W: aDate => String(weekNumber(aDate, 1)), + x: aDate => lazy.dateFormatter.format(aDate), + X: aDate => lazy.timeFormatter.format(aDate), + y: aDate => String(aDate.getFullYear() % 100), + Y: aDate => String(aDate.getFullYear()), + z: aDate => timeZoneOffset(aDate), + Z: aDate => timeZone(aDate), + "%": () => "%", +}; +const padding = { + C: { fill: "0", width: 2 }, + d: { fill: "0", width: 2 }, + e: { fill: " ", width: 2 }, + g: { fill: "0", width: 2 }, + H: { fill: "0", width: 2 }, + I: { fill: "0", width: 2 }, + j: { fill: "0", width: 3 }, + k: { fill: " ", width: 2 }, + l: { fill: " ", width: 2 }, + m: { fill: "0", width: 2 }, + M: { fill: "0", width: 2 }, + S: { fill: "0", width: 2 }, + U: { fill: "0", width: 2 }, + V: { fill: "0", width: 2 }, + W: { fill: "0", width: 2 }, + y: { fill: "0", width: 2 }, +}; + +export function ToLocaleFormat(aFormat, aDate) { + // Modified conversion specifiers E and O are ignored. + let specifiers = Object.keys(formatFunctions).join(""); + let pattern = RegExp(`%#?(\\^)?([0_-]\\d*)?(?:[EO])?([${specifiers}])`, "g"); + + return aFormat.replace( + pattern, + (matched, upperCaseFlag, fillWidthFlags, specifier) => { + let result = formatFunctions[specifier](aDate); + if (upperCaseFlag) { + result = result.toLocaleUpperCase(); + } + let fill = specifier in padding ? padding[specifier].fill : ""; + let width = specifier in padding ? padding[specifier].width : 0; + if (fillWidthFlags) { + let newFill = fillWidthFlags[0]; + let newWidth = fillWidthFlags.match(/\d+/); + if (newFill === "-" && newWidth === null) { + fill = ""; + } else { + fill = newFill === "0" ? "0" : " "; + width = newWidth !== null ? Number(newWidth) : width; + } + } + return result.padStart(width, fill); + } + ); +} |