diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f5dc344..20ee438 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,14 +1,18 @@
name: Node CI
-on: [push]
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Use Node.js 18.x
- uses: actions/setup-node@v1
+ uses: actions/setup-node@v3
with:
node-version: 18.x
- name: npm install, build, and test
diff --git a/README.md b/README.md
index bc5ca8d..34bbe20 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,7 @@ So, a relative date phrase is used for up to a month and then the actual date is
| Property Name | Attribute Name | Possible Values | Default Value |
|:---------------|:-----------------|:--------------------------------------------------------------------------------------------|:---------------------------------|
| `datetime` | `datetime` | `string` | - |
-| `format` | `format` | `'datetime'\|'relative'\|'duration'` | `'auto'` |
+| `format` | `format` | `'datetime'\|'relative'\|'duration'` | `'auto'` |
| `date` | - | `Date \| null` | - |
| `tense` | `tense` | `'auto'\|'past'\|'future'` | `'auto'` |
| `precision` | `precision` | `'year'\|'month'\|'day'\|'hour'\|'minute'\|'second'` | `'second'` |
@@ -83,7 +83,7 @@ So, a relative date phrase is used for up to a month and then the actual date is
*: If unspecified, `formatStyle` will return `'narrow'` if `format` is `'elapsed'` or `'micro'`, `'short'` if the format is `'relative'` or `'datetime'`, otherwise it will be `'long'`.
-**: If unspecified, `month` will return the same value as `formatStyle` whenever `format` is `'datetime'`, otherwise it wil be `'short'`.
+**: If unspecified, `month` will return the same value as `formatStyle` whenever `format` is `'datetime'`, otherwise it will be `'short'`.
***: If unspecified, `weekday` will return the same value as `formatStyle` whenever `format` is `'datetime'`, otherwise it will be `undefined`.
@@ -122,7 +122,7 @@ Unless specified, it will consider `weekday` to be `'long'`, `month` to be `'lon
###### `format=relative`
-The default `relative` format will display dates relative to the current time (unless they are past the `threshold` value - see below). The values are rounded to display a single unit, for example if the time between the given `datetime` and the current wall clock time exceeds a day, then the format will _only_ ouput in days, and will not display hours, minutes or seconds. Some examples of this format with the default options and an `en` locale:
+The default `relative` format will display dates relative to the current time (unless they are past the `threshold` value - see below). The values are rounded to display a single unit, for example if the time between the given `datetime` and the current wall clock time exceeds a day, then the format will _only_ output in days, and will not display hours, minutes or seconds. Some examples of this format with the default options and an `en` locale:
- `in 20 days`
- `20 days ago`
@@ -146,7 +146,7 @@ This is similar to the `format=duration`, except the `formatStyle` defaults to `
###### `format=auto`
-This is identical to `format=relative`. Code that uses `format=auto` should migrae to `format=relative` as this will be the new default in a later version.
+This is identical to `format=relative`. Code that uses `format=auto` should migrate to `format=relative` as this will be the new default in a later version.
###### `format=micro`
diff --git a/package-lock.json b/package-lock.json
index 1f1b311..7c40a12 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6753,9 +6753,9 @@
}
},
"node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -11874,9 +11874,9 @@
}
},
"word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
+ "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
"dev": true
},
"wordwrapjs": {
diff --git a/src/duration.ts b/src/duration.ts
index 5dc7a3c..a0529e3 100644
--- a/src/duration.ts
+++ b/src/duration.ts
@@ -151,33 +151,45 @@ export function roundToSingleUnit(duration: Duration, {relativeTo = Date.now()}:
if (!days && hours >= 21) days += Math.round(hours / 24)
if (days || weeks || months || years) hours = 0
+ // Resolve calendar dates
const currentYear = relativeTo.getFullYear()
let currentMonth = relativeTo.getMonth()
const currentDate = relativeTo.getDate()
- if (days >= 27 || (years + months && days)) {
- relativeTo.setDate(currentDate + days * sign)
- months += Math.abs(
- relativeTo.getFullYear() >= currentYear
- ? relativeTo.getMonth() - currentMonth
- : relativeTo.getMonth() - currentMonth - 12,
- )
- if (months) {
- days = 0
+ if (days >= 27 || years + months + days) {
+ const newDate = new Date(relativeTo)
+ newDate.setFullYear(currentYear + years * sign)
+ newDate.setMonth(currentMonth + months * sign)
+ newDate.setDate(currentDate + days * sign)
+ const yearDiff = newDate.getFullYear() - relativeTo.getFullYear()
+ const monthDiff = newDate.getMonth() - relativeTo.getMonth()
+ const daysDiff = Math.abs(Math.round((Number(newDate) - Number(relativeTo)) / 86400000))
+ const monthsDiff = Math.abs(yearDiff * 12 + monthDiff)
+ if (daysDiff < 27) {
+ if (days >= 6) {
+ weeks += Math.round(days / 7)
+ days = 0
+ } else {
+ days = daysDiff
+ }
+ months = years = 0
+ } else if (monthsDiff < 11) {
+ months = monthsDiff
+ years = 0
+ } else {
+ months = 0
+ years = yearDiff * sign
}
+ if (months || years) days = 0
currentMonth = relativeTo.getMonth()
}
-
- if (days >= 6) weeks += Math.round(days / 7)
- if (weeks || months || years) days = 0
+ if (years) months = 0
if (weeks >= 4) months += Math.round(weeks / 4)
if (months || years) weeks = 0
-
- if (months >= 11 || (years && months)) {
- relativeTo.setMonth(relativeTo.getMonth() + months * sign)
- years += Math.abs(currentYear - relativeTo.getFullYear())
+ if (days && weeks && !months && !years) {
+ weeks += Math.round(days / 7)
+ days = 0
}
- if (years) months = 0
return new Duration(
years * sign,
diff --git a/test/duration.ts b/test/duration.ts
index ef96d97..d90c490 100644
--- a/test/duration.ts
+++ b/test/duration.ts
@@ -293,6 +293,7 @@ suite('duration', function () {
['P9M20DT25H', 'P10M', {relativeTo: new Date('2023-01-12T00:00:00Z')}],
['P11M', 'P1Y', {relativeTo: new Date('2022-11-01T00:00:00Z')}],
['-P11M', '-P1Y', {relativeTo: new Date('2022-11-01T00:00:00Z')}],
+ ['-P11M15D', '-P1Y', {relativeTo: new Date('2024-01-06T00:00:00')}],
['P1Y4D', 'P1Y', {relativeTo: new Date('2022-11-01T00:00:00Z')}],
['P1Y5M13D', 'P1Y', {relativeTo: new Date('2023-01-01T00:00:00Z')}],
['P1Y5M15D', 'P1Y', {relativeTo: new Date('2023-01-01T00:00:00Z')}],
@@ -308,7 +309,12 @@ suite('duration', function () {
relativeTo: new Date('2022-01-01T00:00:00Z'),
},
],
- ['-P27D', '-P1M', {relativeTo: new Date('2023-02-28T00:00:00Z')}],
+ ['-P27D', '-P27D', {relativeTo: new Date('2023-02-28T00:00:00Z')}],
+ ['-P27D', '-P1M', {relativeTo: new Date('2023-02-27T00:00:00Z')}],
+ ['P1Y2M1D', 'P2Y', {relativeTo: new Date('2022-12-31T12:00:00.000Z')}],
+ ['-P1Y8D', '-P1Y', {relativeTo: new Date('2024-01-11T12:00:00.000Z')}],
+ ['-P1Y7DT19H43M19S', '-P1Y', {relativeTo: new Date('2024-01-11T12:00:00.000Z')}],
+ ['-P1Y11D', '-P2Y', {relativeTo: new Date('2024-01-11T12:00:00.000Z')}],
])
for (const [input, expected, opts] of roundTests) {
test(`roundToSingleUnit(${input}) === ${expected}`, () => {
diff --git a/test/relative-time.js b/test/relative-time.js
index 1f20571..c49d731 100644
--- a/test/relative-time.js
+++ b/test/relative-time.js
@@ -482,7 +482,7 @@ suite('relative-time', function () {
time.setAttribute('datetime', datetime)
time.setAttribute('format', 'micro')
await Promise.resolve()
- assert.equal(time.shadowRoot.textContent, '10y')
+ assert.equal(time.shadowRoot.textContent, '11y')
})
test('micro formats future times', async () => {
@@ -2416,14 +2416,14 @@ suite('relative-time', function () {
datetime: '2024-03-01T12:00:00.000Z',
tense: 'future',
format: 'auto',
- expected: 'in 3 years',
+ expected: 'in 2 years',
},
{
reference: '2022-12-31T12:00:00.000Z',
datetime: '2024-03-01T12:00:00.000Z',
tense: 'future',
format: 'micro',
- expected: '3y',
+ expected: '2y',
},
{
reference: '2021-04-24T12:00:00.000Z',
@@ -2432,6 +2432,13 @@ suite('relative-time', function () {
format: 'micro',
expected: '2y',
},
+ {
+ reference: '2024-01-04T12:00:00.000Z',
+ datetime: '2020-02-16T16:16:41.000Z',
+ tense: 'past',
+ format: 'auto',
+ expected: '4 years ago',
+ },
])
for (const {