mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-06 23:32:08 +00:00
Âge : l'anniversaire compte une fois passé (#198)
L'âge d'une personne vivante était l'année en cours moins l'année de naissance : né en décembre, on prenait un an dès janvier. Même défaut pour l'âge au décès et les âges de l'onglet Vie. Quand les deux dates sont complètes, l'âge se compte au jour près (utils/age.js) ; une année seule garde la différence des années. Fixes #198 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3dfc8625e9
commit
d013c8b8fc
4 files changed
+65
-7
No files matched your search
@@ -18,6 +18,7 @@ import { t, dateLocale } from '../i18n/index.js'
|
||||
import { formatPlace } from '../utils/place.js'
|
||||
import { firstNameLabel, fullNameLabel, isFirstNameUnknown } from '../utils/personName.js'
|
||||
import { isYearOnly } from '../utils/dateInput.js'
|
||||
import { ageInYears } from '../utils/age.js'
|
||||
import DateField from './DateField.jsx'
|
||||
import { runOnce } from '../utils/saveOnLeave.js'
|
||||
import { normalizeSex, SEX_CHOICES } from '../utils/personSex.js'
|
||||
@@ -448,9 +449,9 @@ const PersonCard = ({
|
||||
const hasDeathYear = Number.isFinite(deathYearNumber)
|
||||
const age = hasBirthYear
|
||||
? person.isAlive
|
||||
? new Date().getFullYear() - birthYearNumber
|
||||
? ageInYears(person.birthDate ?? birthYearNumber)
|
||||
: hasDeathYear
|
||||
? deathYearNumber - birthYearNumber
|
||||
? ageInYears(person.birthDate ?? birthYearNumber, person.deathDate ?? deathYearNumber)
|
||||
: null
|
||||
: null
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { isYearOnly } from './dateInput.js'
|
||||
|
||||
// Âge en années révolues (#198) : l'anniversaire compte une fois passé, pas dès le 1er janvier.
|
||||
// Une année seule (rangée au 1er janvier) n'a pas de jour : on garde la différence des années.
|
||||
const partsOf = (value) => {
|
||||
if (value === null || value === undefined || value === '') return null
|
||||
if (Number.isFinite(value)) return { year: Number(value), md: null }
|
||||
const text = String(value).slice(0, 10)
|
||||
const m = text.match(/^(\d{4})(?:-(\d{2})-(\d{2}))?/)
|
||||
if (!m) return null
|
||||
return { year: Number(m[1]), md: m[2] && !isYearOnly(text) ? `${m[2]}-${m[3]}` : null }
|
||||
}
|
||||
|
||||
const today = () => {
|
||||
const now = new Date()
|
||||
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// end absent = aujourd'hui ; null si la naissance (ou la fin donnée) manque
|
||||
export function ageInYears(birth, end = today()) {
|
||||
const from = partsOf(birth)
|
||||
const to = partsOf(end)
|
||||
if (!from || !to) return null
|
||||
const age = to.year - from.year
|
||||
return from.md && to.md && to.md < from.md ? age - 1 : age
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { ageInYears } from './age.js'
|
||||
|
||||
describe('ageInYears', () => {
|
||||
it("ne compte l'anniversaire qu'une fois passé (#198)", () => {
|
||||
expect(ageInYears('1980-12-15', '2026-10-29')).toBe(45)
|
||||
expect(ageInYears('1980-12-15', '2026-12-15')).toBe(46)
|
||||
expect(ageInYears('1980-03-02T00:00:00.000Z', '2026-10-29')).toBe(46)
|
||||
})
|
||||
|
||||
it('garde la différence des années pour une année seule', () => {
|
||||
expect(ageInYears('1980-01-01', '2026-10-29')).toBe(46)
|
||||
expect(ageInYears(1980, '2026-10-29')).toBe(46)
|
||||
expect(ageInYears('1920-05-10', 1995)).toBe(75)
|
||||
})
|
||||
|
||||
it('calcule un âge au décès au jour près', () => {
|
||||
expect(ageInYears('1920-05-10', '1995-05-09T00:00:00.000Z')).toBe(74)
|
||||
})
|
||||
|
||||
it('rend null sans naissance', () => {
|
||||
expect(ageInYears(null)).toBe(null)
|
||||
expect(ageInYears('', '2026-10-29')).toBe(null)
|
||||
})
|
||||
})
|
||||
@@ -8,6 +8,7 @@
|
||||
import { t, dateLocale } from '../i18n/index.js'
|
||||
import { firstNameLabel } from './personName.js'
|
||||
import { formatPlace } from './place.js'
|
||||
import { ageInYears } from './age.js'
|
||||
|
||||
const EVENT_LABELS = {
|
||||
baptism: t('Baptême'),
|
||||
@@ -90,7 +91,11 @@ export function describeDate(date, end, precision) {
|
||||
}
|
||||
}
|
||||
|
||||
const ageAt = (birthYear, year) => (birthYear && year && year > birthYear ? t('{age} ans', { age: year - birthYear }) : '')
|
||||
// Âge à une date, au jour près quand les deux dates sont complètes (#198), sinon à l'année
|
||||
const ageAt = (birth, end) => {
|
||||
const age = ageInYears(birth, end || null)
|
||||
return age > 0 ? t('{age} ans', { age }) : ''
|
||||
}
|
||||
const joinMeta = (...parts) => parts.filter(Boolean).join(' · ')
|
||||
|
||||
// Le prénom d'un proche, cliquable dans la frise : il ouvre sa fiche
|
||||
@@ -123,6 +128,7 @@ export function buildLifeTimeline(person, family = {}, life = null) {
|
||||
const lifeUnions = new Map((life?.unions || []).map((u) => [String(u.id), u]))
|
||||
const events = life?.events || []
|
||||
const birthYear = yearOf(person.birthDate) ?? person.birthYear ?? null
|
||||
const birthDate = person.birthDate || birthYear
|
||||
const deathYear = yearOf(person.deathDate) ?? person.deathYear ?? null
|
||||
const lastYear = deathYear ?? new Date().getFullYear()
|
||||
const within = (year) => year && (!birthYear || year >= birthYear) && year <= lastYear
|
||||
@@ -165,14 +171,14 @@ export function buildLifeTimeline(person, family = {}, life = null) {
|
||||
startDatePrecision: detail.startDatePrecision || null, startPlace: detail.startPlace || '',
|
||||
},
|
||||
meta: year
|
||||
? joinMeta(place, detail.startDatePrecision ? describeDate(detail.startDate, null, detail.startDatePrecision) : '', ageAt(birthYear, year))
|
||||
? joinMeta(place, detail.startDatePrecision ? describeDate(detail.startDate, null, detail.startDatePrecision) : '', ageAt(birthDate, detail.startDate || year))
|
||||
: joinMeta(place, t('date inconnue')),
|
||||
note: extra?.notes || '', sources: extra?.sources || '',
|
||||
...milestoneFields(extra, { type: 'marriage', unionId: union.id }),
|
||||
})
|
||||
const hasEndEvent = events.some((e) => String(e.unionId) === String(union.id) && ['divorce', 'annulment'].includes(e.type))
|
||||
if (union.endYear && !hasEndEvent) {
|
||||
out.push({ key: `union-end-${union.id}`, year: union.endYear, order: 3, kind: 'minor', title: t('Fin de l’union avec {name}', { name: union.partner?.firstName || partnerName }), meta: ageAt(birthYear, union.endYear) })
|
||||
out.push({ key: `union-end-${union.id}`, year: union.endYear, order: 3, kind: 'minor', title: t('Fin de l’union avec {name}', { name: union.partner?.firstName || partnerName }), meta: ageAt(birthDate, union.endYear) })
|
||||
}
|
||||
if (union.partner && within(yearOf(union.partner.deathDate) ?? union.partner.deathYear)) {
|
||||
const year2 = yearOf(union.partner.deathDate) ?? union.partner.deathYear
|
||||
@@ -190,7 +196,7 @@ export function buildLifeTimeline(person, family = {}, life = null) {
|
||||
}
|
||||
out.push({
|
||||
key: `event-${event.id}`, year, order: 1, kind: 'minor', title,
|
||||
meta: joinMeta(formatPlace(event.place || ''), year ? describeDate(event.date, event.dateEnd, event.datePrecision) : t('date inconnue'), ageAt(birthYear, year)),
|
||||
meta: joinMeta(formatPlace(event.place || ''), year ? describeDate(event.date, event.dateEnd, event.datePrecision) : t('date inconnue'), ageAt(birthDate, event.date || year)),
|
||||
note: event.notes || '', sources: event.sources || '',
|
||||
mediaIds: event.mediaIds || [],
|
||||
// Seuls les moments propres à la personne se modifient depuis l'onglet (pas ceux d'une union)
|
||||
@@ -219,7 +225,7 @@ export function buildLifeTimeline(person, family = {}, life = null) {
|
||||
out.push({
|
||||
key: 'death', year: deathYear, order: 9, kind: 'mile',
|
||||
title: place ? t('Décès à {place}', { place: place.split(',')[0] }) : t('Décès'),
|
||||
meta: deathYear ? joinMeta(describeDate(person.deathDate, details.deathDateEnd, details.deathDatePrecision), ageAt(birthYear, deathYear)) : t('date inconnue'),
|
||||
meta: deathYear ? joinMeta(describeDate(person.deathDate, details.deathDateEnd, details.deathDatePrecision), ageAt(birthDate, person.deathDate || deathYear)) : t('date inconnue'),
|
||||
note: extra?.notes || '', sources: extra?.sources || '',
|
||||
...milestoneFields(extra, { type: 'death' }),
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user