« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--package-lock.json38
-rw-r--r--package.json1
-rw-r--r--src/data/things/language.js108
-rw-r--r--src/strings-default.yaml48
4 files changed, 191 insertions, 4 deletions
diff --git a/package-lock.json b/package-lock.json
index bca46bd5..1da6e9ad 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 7cd9e0ef..a1c165bc 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 d8af9620..fa529a8e 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 af2ddc42..30b04141 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"