-
Notifications
You must be signed in to change notification settings - Fork 210
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(feat) O3-3684: Improve duration representation of
age
function (#1100
- Loading branch information
Showing
5 changed files
with
118 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import dayjs from 'dayjs'; | ||
import type { i18n } from 'i18next'; | ||
import { age } from '.'; | ||
|
||
window.i18next = { language: 'en' } as i18n; | ||
|
||
describe('Age Helper', () => { | ||
// test cases mostly taken from | ||
// https://webarchive.nationalarchives.gov.uk/ukgwa/20160921162509mp_/http://systems.digital.nhs.uk/data/cui/uig/patben.pdf | ||
// (Table 8) | ||
const now = dayjs('2024-07-30'); | ||
const test1 = now.subtract(1, 'hour').subtract(30, 'minutes'); | ||
const test2 = now.subtract(1, 'day').subtract(2, 'hours').subtract(5, 'minutes'); | ||
const test3 = now.subtract(3, 'days').subtract(17, 'hours').subtract(30, 'minutes'); | ||
const test4 = now.subtract(27, 'days').subtract(5, 'hours').subtract(2, 'minutes'); | ||
const test5 = now.subtract(28, 'days').subtract(5, 'hours').subtract(2, 'minutes'); | ||
const test6 = now.subtract(29, 'days').subtract(5, 'hours').subtract(2, 'minutes'); | ||
const test7 = now.subtract(1, 'year').subtract(1, 'day').subtract(5, 'hours'); | ||
const test8 = now.subtract(1, 'year').subtract(8, 'day').subtract(5, 'hours'); | ||
const test9 = now.subtract(1, 'year').subtract(39, 'day').subtract(5, 'hours'); | ||
const test10 = now.subtract(4, 'year').subtract(39, 'day'); | ||
const test11 = now.subtract(18, 'year').subtract(39, 'day'); | ||
|
||
it('should render durations correctly', () => { | ||
expect(age(test1, now)).toBe('90 min'); | ||
expect(age(test2, now)).toBe('26 hr'); | ||
expect(age(test3, now)).toBe('3 days'); | ||
expect(age(test4, now)).toBe('27 days'); | ||
expect(age(test5, now)).toBe('4 wks'); | ||
expect(age(test6, now)).toBe('4 wks, 1 day'); | ||
expect(age(test7, now)).toBe('12 mths, 1 day'); | ||
expect(age(test8, now)).toBe('12 mths, 8 days'); | ||
expect(age(test9, now)).toBe('13 mths, 9 days'); | ||
expect(age(test10, now)).toBe('4 yrs, 1 mth'); | ||
expect(age(test11, now)).toBe('18 yrs'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,101 +1,55 @@ | ||
/** @module @category Utility */ | ||
import dayjs from 'dayjs'; | ||
import { getLocale } from './omrs-dates'; | ||
import { DurationFormat } from '@formatjs/intl-durationformat'; | ||
import { type DurationInput } from '@formatjs/intl-durationformat/src/types'; | ||
|
||
/** | ||
* Gets the number of days in the year of the given date. | ||
* @param date The date to compute the days within the year. | ||
* @returns The number of days. | ||
*/ | ||
export function daysIntoYear(date: Date) { | ||
return ( | ||
(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()) - Date.UTC(date.getUTCFullYear(), 0, 0)) / | ||
24 / | ||
60 / | ||
60 / | ||
1000 | ||
); | ||
} | ||
|
||
/** | ||
* Checks if two dates are representing the same day. | ||
* @param firstDate The first date. | ||
* @param secondDate The second date. | ||
* @returns True if both are located on the same day. | ||
*/ | ||
export function isSameDay(firstDate: Date, secondDate: Date) { | ||
const firstISO = firstDate.toISOString(); | ||
const secondISO = secondDate.toISOString(); | ||
return firstISO.slice(0, firstISO.indexOf('T')) === secondISO.slice(0, secondISO.indexOf('T')); | ||
} | ||
|
||
/** | ||
* Gets a human readable and locale supported age represention of the provided date string. | ||
* @param dateString The stringified date. | ||
* Gets a human readable and locale supported representation of a person's age, given their birthDate, | ||
* The representation logic follows the guideline here: | ||
* https://webarchive.nationalarchives.gov.uk/ukgwa/20160921162509mp_/http://systems.digital.nhs.uk/data/cui/uig/patben.pdf | ||
* (See Tables 7 and 8) | ||
* | ||
* @param birthDate The birthDate. | ||
* @param currentDate Optional. If provided, calculates the age of the person at the provided currentDate (instead of now). | ||
* @returns A human-readable string version of the age. | ||
*/ | ||
export function age(dateString: string): string { | ||
// Different from npm packages such as https://www.npmjs.com/package/timeago | ||
export function age(birthDate: dayjs.ConfigType, currentDate: dayjs.ConfigType = dayjs()): string { | ||
const to = dayjs(currentDate); | ||
const from = dayjs(birthDate); | ||
|
||
// First calculate the age in years | ||
const today = new Date(); | ||
const birthDate = new Date(dateString); | ||
const monthDifference = today.getUTCMonth() - birthDate.getUTCMonth(); | ||
const dateDifference = today.getUTCDate() - birthDate.getUTCDate(); | ||
let age = today.getUTCFullYear() - birthDate.getUTCFullYear(); | ||
if (monthDifference < 0 || (monthDifference === 0 && dateDifference < 0)) { | ||
age--; | ||
} | ||
|
||
// Now calculate the number of months in addition to the year's age | ||
let monthsAgo = monthDifference >= 0 ? monthDifference : monthDifference + 12; | ||
if (dateDifference < 0) { | ||
monthsAgo--; | ||
} | ||
const hourDiff = to.diff(from, 'hours'); | ||
const dayDiff = to.diff(from, 'days'); | ||
const weekDiff = to.diff(from, 'weeks'); | ||
const monthDiff = to.diff(from, 'months'); | ||
const yearDiff = to.diff(from, 'years'); | ||
|
||
// For patients less than a year old, we calculate the number of days/weeks they have been alive | ||
let totalDaysAgo = daysIntoYear(today) - daysIntoYear(birthDate); | ||
if (totalDaysAgo < 0) { | ||
totalDaysAgo += 365; | ||
} | ||
const weeksAgo = Math.floor(totalDaysAgo / 7); | ||
const duration: DurationInput = {}; | ||
|
||
const locale = getLocale(); | ||
|
||
// Depending on their age, return a different representation of their age. | ||
if (age === 0) { | ||
if (isSameDay(today, birthDate)) { | ||
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' }); | ||
return rtf.format(0, 'day'); | ||
} else if (totalDaysAgo < 31) { | ||
const totalDaysAgoStr = new Intl.NumberFormat(locale, { | ||
style: 'unit', | ||
unit: 'day', | ||
unitDisplay: 'short', | ||
}).format(totalDaysAgo); | ||
|
||
return totalDaysAgoStr; | ||
} else { | ||
const weeksAgoStr = new Intl.NumberFormat(locale, { | ||
style: 'unit', | ||
unit: 'week', | ||
unitDisplay: 'short', | ||
}).format(weeksAgo); | ||
return weeksAgoStr; | ||
} | ||
} else if (age < 2) { | ||
const monthsAgoStr = new Intl.NumberFormat(locale, { | ||
style: 'unit', | ||
unit: 'month', | ||
unitDisplay: 'short', | ||
}).format(monthsAgo + 12); | ||
|
||
return monthsAgoStr; | ||
if (hourDiff < 2) { | ||
const minuteDiff = to.diff(from, 'minutes'); | ||
duration['minutes'] = minuteDiff; | ||
} else if (dayDiff < 2) { | ||
duration['hours'] = hourDiff; | ||
} else if (weekDiff < 4) { | ||
duration['days'] = dayDiff; | ||
} else if (yearDiff < 1) { | ||
const remainderDayDiff = to.subtract(weekDiff, 'weeks').diff(from, 'days'); | ||
duration['weeks'] = weekDiff; | ||
duration['days'] = remainderDayDiff; | ||
} else if (yearDiff < 2) { | ||
const remainderDayDiff = to.subtract(monthDiff, 'months').diff(from, 'days'); | ||
duration['months'] = monthDiff; | ||
duration['days'] = remainderDayDiff; | ||
} else if (yearDiff < 18) { | ||
const remainderMonthDiff = to.subtract(yearDiff, 'year').diff(from, 'months'); | ||
duration['years'] = yearDiff; | ||
duration['months'] = remainderMonthDiff; | ||
} else { | ||
const yearsAgoStr = new Intl.NumberFormat(locale, { | ||
style: 'unit', | ||
unit: 'year', | ||
unitDisplay: 'short', | ||
}).format(age); | ||
return yearsAgoStr; | ||
duration['years'] = yearDiff; | ||
} | ||
|
||
return new DurationFormat(locale, { style: 'short' }).format(duration); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters