From 017be69511d1d3638fa834ffb1300fbbb9a187b7 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 29 Nov 2023 21:41:16 -0400 Subject: data: language: formatDateDuration, formatRelativeDate Also related counting functions. --- package-lock.json | 38 ++++++++++++++-- package.json | 1 + src/data/things/language.js | 108 ++++++++++++++++++++++++++++++++++++++++++++ src/strings-default.yaml | 48 ++++++++++++++++++++ 4 files changed, 191 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index bca46bd..1da6e9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "GPL-3.0", "dependencies": { + "@js-temporal/polyfill": "^0.4.4", "chroma-js": "^2.4.2", "command-exists": "^1.2.9", "eslint": "^8.37.0", @@ -241,6 +242,18 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@js-temporal/polyfill": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.4.4.tgz", + "integrity": "sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==", + "dependencies": { + "jsbi": "^4.3.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3077,6 +3090,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, "node_modules/json-parse-even-better-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", @@ -5231,8 +5249,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tuf-js": { "version": "2.1.0", @@ -5878,6 +5895,15 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@js-temporal/polyfill": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.4.4.tgz", + "integrity": "sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==", + "requires": { + "jsbi": "^4.3.0", + "tslib": "^2.4.1" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7928,6 +7954,11 @@ "argparse": "^2.0.1" } }, + "jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, "json-parse-even-better-errors": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.0.tgz", @@ -9473,8 +9504,7 @@ "tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "tuf-js": { "version": "2.1.0", diff --git a/package.json b/package.json index 7cd9e0e..a1c165b 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "node": ">= 20.9.0" }, "dependencies": { + "@js-temporal/polyfill": "^0.4.4", "chroma-js": "^2.4.2", "command-exists": "^1.2.9", "eslint": "^8.37.0", diff --git a/src/data/things/language.js b/src/data/things/language.js index d8af962..fa529a8 100644 --- a/src/data/things/language.js +++ b/src/data/things/language.js @@ -1,3 +1,5 @@ +import { Temporal, toTemporalInstant } from '@js-temporal/polyfill'; + import {isLanguageCode} from '#validators'; import {Tag} from '#html'; @@ -284,6 +286,108 @@ export class Language extends Thing { return this.intl_date.formatRange(startDate, endDate); } + formatDateDuration({ + years: numYears = 0, + months: numMonths = 0, + days: numDays = 0, + approximate = false, + }) { + let basis; + + const years = this.countYears(numYears, {unit: true}); + const months = this.countMonths(numMonths, {unit: true}); + const days = this.countDays(numDays, {unit: true}); + + if (numYears && numMonths && numDays) + basis = this.formatString('count.dateDuration.yearsMonthsDays', {years, months, days}); + else if (numYears && numMonths) + basis = this.formatString('count.dateDuration.yearsMonths', {years, months}); + else if (numYears && numDays) + basis = this.formatString('count.dateDuration.yearsDays', {years, days}); + else if (numYears) + basis = this.formatString('count.dateDuration.years', {years}); + else if (numMonths && numDays) + basis = this.formatString('count.dateDuration.monthsDays', {months, days}); + else if (numMonths) + basis = this.formatzString('count.dateDuration.months', {months}); + else if (numDays) + basis = this.formatString('count.dateDuration.days', {days}); + else + return this.formatString('count.dateDuration.zero'); + + if (approximate) { + return this.formatString('count.dateDuration.approximate', { + duration: basis, + }); + } else { + return basis; + } + } + + formatRelativeDate(currentDate, referenceDate, { + considerRoundingDays = false, + approximate = true, + absolute = true, + } = {}) { + const currentInstant = toTemporalInstant.apply(currentDate); + const referenceInstant = toTemporalInstant.apply(referenceDate); + + const comparison = + Temporal.Instant.compare(currentInstant, referenceInstant); + + if (comparison === 0) { + return this.formatString('count.dateDuration.same'); + } + + const currentTDZ = currentInstant.toZonedDateTimeISO('Etc/UTC'); + const referenceTDZ = referenceInstant.toZonedDateTimeISO('Etc/UTC'); + + const earlierTDZ = (comparison === -1 ? currentTDZ : referenceTDZ); + const laterTDZ = (comparison === 1 ? currentTDZ : referenceTDZ); + + const {years, months, days} = + laterTDZ.since(earlierTDZ, { + largestUnit: 'year', + smallestUnit: + (considerRoundingDays + ? (laterTDZ.since(earlierTDZ, { + largestUnit: 'year', + smallestUnit: 'day', + }).years + ? 'month' + : 'day') + : 'day'), + roundingMode: 'halfCeil', + }); + + const duration = + this.formatDateDuration({ + years, months, days, + approximate: false, + }); + + const relative = + this.formatString( + 'count.dateDuration', + (approximate + ? (comparison === -1 + ? 'approximateEarlier' + : 'approximateLater') + : (comparison === -1 + ? 'earlier' + : 'later')), + {duration}); + + if (absolute) { + return this.formatString('count.dateDuration.relativeAbsolute', { + relative, + absolute: this.formatDate(currentDate), + }); + } else { + return relative; + } + } + formatDuration(secTotal, {approximate = false, unit = false} = {}) { if (secTotal === 0) { return this.formatString('count.duration.missing'); @@ -442,7 +546,11 @@ Object.assign(Language.prototype, { countCommentaryEntries: countHelper('commentaryEntries', 'entries'), countContributions: countHelper('contributions'), countCoverArts: countHelper('coverArts'), + countDays: countHelper('days'), + countMonths: countHelper('months'), countTimesReferenced: countHelper('timesReferenced'), countTimesUsed: countHelper('timesUsed'), countTracks: countHelper('tracks'), + countWeeks: countHelper('weeks'), + countYears: countHelper('years'), }); diff --git a/src/strings-default.yaml b/src/strings-default.yaml index af2ddc4..30b0414 100644 --- a/src/strings-default.yaml +++ b/src/strings-default.yaml @@ -129,6 +129,16 @@ count: many: "" other: "{DAYS} days" + months: + _: "{MONTHS}" + withUnit: + zero: "" + one: "{MONTHS} month" + two: "" + few: "" + many: "" + other: "{MONTHS} months" + timesReferenced: _: "{TIMES_REFERENCED}" withUnit: @@ -149,6 +159,16 @@ count: many: "" other: "used {TIMES_USED} times" + weeks: + _: "{WEEKS}" + withUnit: + zero: "" + one: "{WEEKS} week" + two: "" + few: "" + many: "" + other: "{WEEKS} weeks" + words: _: "{WORDS}" thousand: "{WORDS}k" @@ -160,6 +180,16 @@ count: many: "" other: "{WORDS} words" + years: + _: "{YEARS}" + withUnit: + zero: "" + one: "{YEARS} year" + two: "" + few: "" + many: "" + other: "{YEARS} years" + # Numerical things that aren't exactly counting, per se duration: @@ -172,6 +202,24 @@ count: _: "{MINUTES}:{SECONDS}" withUnit: "{MINUTES}:{SECONDS} minutes" + dateDuration: + earlier: "{DURATION} earlier" + later: "{DURATION} later" + same: "on the same date" + zero: "no days apart" + approximate: "about {DURATION}" + approximateEarlier: "about {DURATION} earlier" + approximateLater: "about {DURATION} later" + relativeAbsolute: "{ABSOLUTE}; {RELATIVE}" + + years: "{YEARS}" + months: "{MONTHS}" + days: "{DAYS}" + yearsMonthsDays: "{YEARS}, {MONTHS}, {DAYS}" + yearsMonths: "{YEARS}, {MONTHS}" + yearsDays: "{YEARS}, {DAYS}" + monthsDays: "{MONTHS}, {DAYS}" + fileSize: terabytes: "{TERABYTES} TB" gigabytes: "{GIGABYTES} GB" -- cgit 1.3.0-6-gf8a5