'1792-09-22', 2 => '1793-09-22', 3 => '1794-09-23', 4 => '1795-09-23', 5 => '1796-09-22', 6 => '1797-09-22', 7 => '1798-09-22', 8 => '1799-09-23', 9 => '1800-09-23', 10 => '1801-09-23', 11 => '1802-09-23', 12 => '1803-09-23', 13 => '1804-09-23', 14 => '1805-09-23', ]; private const MONTHS = [ 'vendémiaire' => 1, 'vendemiaire' => 1, 'brumaire' => 2, 'frimaire' => 3, 'nivôse' => 4, 'nivose' => 4, 'pluviôse' => 5, 'pluviose' => 5, 'ventôse' => 6, 'ventose' => 6, 'germinal' => 7, 'floréal' => 8, 'floreal' => 8, 'prairial' => 9, 'messidor' => 10, 'thermidor' => 11, 'fructidor' => 12, ]; private const ROMAN = [ 'XIV' => 14, 'XIII' => 13, 'XII' => 12, 'XI' => 11, 'X' => 10, 'IX' => 9, 'VIII' => 8, 'VII' => 7, 'VI' => 6, 'V' => 5, 'IV' => 4, 'III' => 3, 'II' => 2, 'I' => 1, ]; private const GEDCOM_MONTHS = [ 1 => 'JAN', 2 => 'FEB', 3 => 'MAR', 4 => 'APR', 5 => 'MAY', 6 => 'JUN', 7 => 'JUL', 8 => 'AUG', 9 => 'SEP', 10 => 'OCT', 11 => 'NOV', 12 => 'DEC', ]; /** * Convertit un champ date JSONB { valeur, calendrier } en chaîne GEDCOM. * Retourne null si la conversion échoue. */ public function toGedcomDate(?array $dateField): ?string { if (empty($dateField['valeur'])) { return null; } $valeur = trim($dateField['valeur']); $calendrier = $dateField['calendrier'] ?? 'gregorien'; return match ($calendrier) { 'gregorien' => $this->gregorianToGedcom($valeur), 'julien' => $this->julianToGedcom($valeur), 'republicain' => $this->republicanToGedcom($valeur), default => null, }; } /** YYYY-MM-DD → "D MON YYYY" */ public function gregorianToGedcom(string $date): ?string { $d = DateTime::createFromFormat('Y-m-d', $date); if (! $d) { return null; } $day = (int) $d->format('j'); $month = self::GEDCOM_MONTHS[(int) $d->format('n')]; $year = $d->format('Y'); return "{$day} {$month} {$year}"; } /** Julien : même format YYYY-MM-DD, marqué @#DJULIAN@ */ private function julianToGedcom(string $date): ?string { $g = $this->gregorianToGedcom($date); return $g ? "@#DJULIAN@ {$g}" : null; } /** * "15 Vendémiaire An III" → date grégorienne GEDCOM. * Retourne la date avec préfixe @#DFRENCH R@ (standard GEDCOM pour calendrier républicain). */ private function republicanToGedcom(string $date): ?string { $gregory = $this->republicanToGregorian($date); if ($gregory) { return $this->gregorianToGedcom($gregory); } // Fallback : on conserve la date telle quelle dans un format lisible return "({$date})"; } /** * Convertit "15 Vendémiaire An III" → "1794-10-06". */ public function republicanToGregorian(string $input): ?string { // Normaliser l'entrée $input = trim($input); // Pattern : "DD NomDuMois An N" ou "DD NomDuMois An XIV" $pattern = '/^(\d{1,2})\s+([\wéèêôûî]+)\s+[Aa]n\s+([IVXLCDM\d]+)$/iu'; if (! preg_match($pattern, $input, $m)) { return null; } $day = (int) $m[1]; $monthStr = mb_strtolower(trim($m[2])); $yearStr = strtoupper(trim($m[3])); // Résoudre le mois $monthNum = self::MONTHS[$monthStr] ?? null; if (! $monthNum || $day < 1 || $day > 30) { return null; } // Résoudre l'année (chiffres arabes ou romains) $yearNum = is_numeric($yearStr) ? (int) $yearStr : (self::ROMAN[$yearStr] ?? null); if (! $yearNum || ! isset(self::YEAR_STARTS[$yearNum])) { return null; } // Calculer le nombre de jours depuis le 1er Vendémiaire $daysOffset = ($monthNum - 1) * 30 + ($day - 1); $start = new DateTime(self::YEAR_STARTS[$yearNum]); $start->modify("+{$daysOffset} days"); return $start->format('Y-m-d'); } }