Skip to content

Commit

Permalink
Merge pull request #5 from fkleon/feature-age-display
Browse files Browse the repository at this point in the history
Rewrite age display logic
  • Loading branch information
fkleon authored May 2, 2024
2 parents bdb09e0 + 31e96b9 commit a323f14
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 46 deletions.
69 changes: 69 additions & 0 deletions src/models/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {Period} from '@js-joda/core';

const listFormat = new Intl.ListFormat('en', {
style: 'long',
type: 'conjunction',
});

const pluralRules = new Intl.PluralRules('en');

const pluralSuffixes = new Map<Intl.LDMLPluralRule, string>([
['one', ''],
['other', 's'],
]);

const pluralise = (word: string, n: number) => {
const rule = pluralRules.select(n);
const suffix = pluralSuffixes.get(rule);
return `${word}${suffix}`;
};

export const formatAge = (period: Period) => {
const parts = [];

const years = period.years();
const months = period.months();
const days = period.days();

// Special dates
if (period.isNegative()) {
// not hatched yet
return '🥚';
} else if (period.isZero()) {
// welcome!
return '🐣';
}

if (period.years() >= 66_000_000 && period.years() <= 72_700_000) {
return '🦖';
}

if (period.years() >= 4_500_000_000) {
return '🌌';
}

// Always display years
if (years > 0) {
parts.push(`${years} ${pluralise('year', years)}`);
}

// Display months up to 20 years
if (months > 0 && years < 20) {
parts.push(`${months} ${pluralise('month', months)}`);
}

// Display days up to 3 months
if (days > 0 && years < 1 && months < 3) {
parts.push(`${days} ${pluralise('day', days)}`);
}

const formattedAge = listFormat.format(parts);

// Birthday
if (period.months() === 0 && period.days() === 0) {
// birthday!
return formattedAge + ' 🎈';
} else {
return formattedAge;
}
};
49 changes: 6 additions & 43 deletions src/views/child.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,54 +10,17 @@ import {
Sex,
} from '../models/state';
import {LocalDate, Period, convert} from '@js-joda/core';

const formatAge = (period: Period) => {
const parts = [];

const years = period.years();
const months = period.months();
// TODO: week approximation problem
const weeks = ~~(period.days() / 7);
const days = period.days() % 7;

if (years > 0) {
parts.push(`${years} year${years > 1 ? 's' : ''}`);
}

if (years < 2) {
if (months > 0) {
parts.push(`${months} month${months > 1 ? 's' : ''}`);
}

if (years < 1) {
if (months < 3 && weeks > 0) {
parts.push(`${weeks} week${weeks > 1 ? 's' : ''}`);
}

if (months < 3) {
if (weeks < 12 && days > 0) {
parts.push(`${days} day${days > 1 ? 's' : ''}`);
}
}
}
}

if (period.isNegative()) {
parts.push('🥚');
} else if (!period.isZero() && period.months() === 0 && period.days() === 0) {
parts.push('🎈');
}

return parts.length === 0 ? '🐣' : parts.join(', ');
};
import {formatAge} from '../models/format';

const ChildComponent: m.Component<MitosisAttr<Child, IChildActions>> = {
oncreate({dom}) {
(dom as HTMLElement).querySelector('input')?.focus();
},
view({attrs: {state, actions}}) {
const name = state.name ?? 'Unnnamed';
const age = state.age ? `(${formatAge(state.age)} old)` : '';
const name = state.name ?? 'Unnamed';
const summary = `${name}${
state.age ? `, ${formatAge(state.age)} old` : ''
}`;

return m(
'details',
Expand All @@ -70,7 +33,7 @@ const ChildComponent: m.Component<MitosisAttr<Child, IChildActions>> = {
},
m(
'summary',
`Child ${state.idx + 1}: ${name} ${age}`,
summary,
m(
'a',
{
Expand Down
2 changes: 1 addition & 1 deletion test/child.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ o.spec('Child component', () => {

// Summary
out.should.have(1, 'summary');
out.should.contain('Child 1: Ava');
out.should.contain('Ava');

// DOB input
out.should.have(
Expand Down
41 changes: 41 additions & 0 deletions test/format.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import o from 'ospec';
import {formatAge} from '../src/models/format';
import {Period} from '@js-joda/core';

o.spec('Format age', () => {
const testCases: [Period, string][] = [
[Period.ofYears(-1), '🥚'],
[Period.ofMonths(-1), '🥚'],
[Period.ofDays(-1), '🥚'],
[Period.ZERO, '🐣'],
[Period.ofDays(1), '1 day'],
[Period.ofDays(2), '2 days'],
[Period.ofDays(31), '31 days'],
[Period.ofWeeks(1), '7 days'],
[Period.ofWeeks(1).plusDays(1), '8 days'],
[Period.ofMonths(1), '1 month'],
[Period.ofMonths(1).plusDays(1), '1 month and 1 day'],
[Period.ofMonths(2), '2 months'],
[Period.ofMonths(2).plusDays(2), '2 months and 2 days'],
[Period.ofMonths(3), '3 months'],
// only display days up to 3 months
[Period.ofMonths(3).plusDays(1), '3 months'],
[Period.ofYears(1), '1 year 🎈'],
[Period.ofYears(1).plusMonths(1), '1 year and 1 month'],
[Period.ofYears(1).plusMonths(1).plusDays(1), '1 year and 1 month'],
[Period.ofYears(2), '2 years 🎈'],
[Period.ofYears(19).plusMonths(11), '19 years and 11 months'],
// only display months up to 20 years
[Period.ofYears(20), '20 years 🎈'],
[Period.ofYears(20).plusMonths(1), '20 years'],
// really old
[Period.ofYears(66_000_000), '🦖'],
[Period.ofYears(4_500_000_000), '🌌'],
];

testCases.forEach(([age, expectedFormat]) => {
o(age.toString(), () => {
o(formatAge(age)).equals(expectedFormat);
});
});
});
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"outDir": "build",
"noImplicitAny": true,
"lib": [
"es2020"
"es2021"
],
"module": "es6",
"target": "es2020",
"target": "es2021",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
},
Expand Down

0 comments on commit a323f14

Please sign in to comment.