Skip to content

Commit f423e1d

Browse files
authored
Merge pull request #6 from blitz-php/devs
Devs
2 parents b25b228 + b2418d9 commit f423e1d

18 files changed

Lines changed: 6221 additions & 1770 deletions

Data/DataTransfertObject.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
use BlitzPHP\Annotations\AnnotationReader;
1515
use BlitzPHP\Contracts\Support\Arrayable;
1616
use BlitzPHP\Contracts\Support\Jsonable;
17-
use BlitzPHP\Utilities\Date;
17+
use BlitzPHP\Utilities\DateTime\Date;
1818
use BlitzPHP\Utilities\Helpers;
1919
use BlitzPHP\Utilities\Iterable\Arr;
2020
use BlitzPHP\Utilities\Iterable\Collection;

DateTime/Date.php

Lines changed: 177 additions & 65 deletions
Large diffs are not rendered by default.

DateTime/FormatDetector.php

Lines changed: 83 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,18 @@ class FormatDetector
2424
'Y-m-d H:i:s' => '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/',
2525
'Y-m-d' => '/^\d{4}-\d{2}-\d{2}$/',
2626

27-
// Formats européens
28-
'd/m/Y H:i:s' => '/^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2}$/',
29-
'd.m.Y H:i:s' => '/^\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2}$/',
30-
'd/m/Y' => '/^\d{2}\/\d{2}\/\d{4}$/',
31-
'd.m.Y' => '/^\d{2}\.\d{2}\.\d{4}$/',
32-
3327
// Formats américains
3428
'm/d/Y H:i:s' => '/^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2}$/',
3529
'm-d-Y H:i:s' => '/^\d{2}-\d{2}-\d{4} \d{2}:\d{2}:\d{2}$/',
3630
'm/d/Y' => '/^\d{2}\/\d{2}\/\d{4}$/',
3731
'm-d-Y' => '/^\d{2}-\d{2}-\d{4}$/',
3832

33+
// Formats européens
34+
'd/m/Y H:i:s' => '/^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2}$/',
35+
'd.m.Y H:i:s' => '/^\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2}$/',
36+
'd/m/Y' => '/^\d{2}\/\d{2}\/\d{4}$/',
37+
'd.m.Y' => '/^\d{2}\.\d{2}\.\d{4}$/',
38+
3939
// Formats spéciaux
4040
'Ymd' => '/^\d{8}$/',
4141
'D, d M Y H:i:s O' => '/^[A-Za-z]{3}, \d{2} [A-Za-z]{3} \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4}$/', // RFC 2822
@@ -54,6 +54,13 @@ public static function detect(string $dateString): ?string
5454
return 'U.u';
5555
}
5656

57+
// Patterns ambigus (slash) gérés séparément
58+
$slashPattern = '/^\d{2}\/\d{2}\/\d{4}( \d{2}:\d{2}:\d{2})?$/';
59+
if (preg_match($slashPattern, $dateString) && null !== $format = self::detectSlashFormat($dateString)) {
60+
return $format;
61+
}
62+
63+
// Autres patterns (non ambigus)
5764
foreach (self::FORMAT_PATTERNS as $format => $pattern) {
5865
if (preg_match($pattern, $dateString)) {
5966
$date = DateTime::createFromFormat($format, $dateString);
@@ -73,18 +80,79 @@ public static function detect(string $dateString): ?string
7380
}
7481
}
7582

76-
private static function guessFormatFromParsedDate(string $dateString): string
83+
/**
84+
* Détecte le format pour les chaînes avec slash (ambiguïté DD/MM vs MM/DD).
85+
*
86+
* @param string $dateString La chaîne à analyser (ex. : '01/08/2025').
87+
*
88+
* @return ?string Le format détecté ('d/m/Y' ou 'm/d/Y') ou null en cas d'ambiguïté.
89+
*/
90+
private static function detectSlashFormat(string $dateString): ?string
7791
{
78-
$date = new DateTime($dateString);
79-
$parts = [
80-
'Y' => $date->format('Y'),
81-
'm' => $date->format('m'),
82-
'd' => $date->format('d'),
83-
'H' => $date->format('H'),
84-
'i' => $date->format('i'),
85-
's' => $date->format('s'),
92+
$formats = [
93+
'with_time' => [
94+
'd/m/Y H:i:s' => ['regex' => '/^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2}$/'],
95+
'm/d/Y H:i:s' => ['regex' => '/^\d{2}\/\d{2}\/\d{4} \d{2}:\d{2}:\d{2}$/'],
96+
],
97+
'without_time' => [
98+
'd/m/Y' => ['regex' => '/^\d{2}\/\d{2}\/\d{4}$/'],
99+
'm/d/Y' => ['regex' => '/^\d{2}\/\d{2}\/\d{4}$/'],
100+
],
86101
];
87102

103+
// Déterminer si avec ou sans temps
104+
$hasTime = str_contains($dateString, ' ');
105+
106+
$candidates = $hasTime ? $formats['with_time'] : $formats['without_time'];
107+
108+
$possibleFormats = [];
109+
110+
foreach ($candidates as $format => $info) {
111+
if (preg_match($info['regex'], $dateString)) {
112+
$date = DateTime::createFromFormat($format, $dateString);
113+
if ($date && $date->format($format) === $dateString) {
114+
$possibleFormats[$format] = [
115+
'date' => $date,
116+
'day' => (int) $date->format('d'),
117+
'month' => (int) $date->format('m'),
118+
];
119+
}
120+
}
121+
}
122+
123+
// Si un seul candidat valide, le retourner
124+
if (count($possibleFormats) === 1) {
125+
return key($possibleFormats);
126+
}
127+
128+
// Si zéro, fallback sur parsing auto
129+
if (empty($possibleFormats)) {
130+
return self::guessFormatFromParsedDate($dateString);
131+
}
132+
133+
// Ambiguïté : appliquer heuristique
134+
// Extraire les valeurs parsed pour chaque format
135+
$ddmm = $possibleFormats['d/m/Y'] ?? null; // Ou avec H:i:s
136+
$mmdd = $possibleFormats['m/d/Y'] ?? null; // Ou avec H:i:s
137+
138+
// Si jour >12 dans DD/MM, impossible → préférer MM/DD
139+
if ($ddmm && $ddmm['day'] > 12) {
140+
return 'm/d/Y';
141+
}
142+
143+
// Si mois >12 dans MM/DD, impossible → préférer DD/MM
144+
if ($mmdd && $mmdd['month'] > 12) {
145+
return 'd/m/Y';
146+
}
147+
148+
// Sinon ambigu (≤12 pour les deux) : biais européen DD/MM
149+
return null; // on laisse le foreach s'en occuper suivant l'ordre de definition des regex
150+
}
151+
152+
private static function guessFormatFromParsedDate(string $dateString): string
153+
{
154+
$date = new DateTime($dateString);
155+
88156
// Analyse la structure originale pour déterminer les séparateurs
89157
if (str_contains($dateString, 'T')) {
90158
$format = 'Y-m-d\TH:i:s';

DateTime/Holiday.php

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ protected static function getEasterDate(int $year): Date
249249
if (! isset(self::$easterCache[$year])) {
250250
$timestamp = easter_date($year);
251251
// On travaille en UTC pour éviter les problèmes de décalage horaire
252-
self::$easterCache[$year] = Date::createFromTimestamp($timestamp)->setTimezone('UTC');
252+
self::$easterCache[$year] = new Date(date('Y-m-d', $timestamp) . ' 00:00:00', 'UTC');
253253
}
254254

255255
return self::$easterCache[$year]->copy();
@@ -288,17 +288,18 @@ protected static function getDefinitions(string $countryCode): array
288288
// Définitions par défaut
289289
return match (strtoupper($countryCode)) {
290290
'FR' => [
291-
'new_year' => '01-01', // Jour de l'An
292-
'easter_monday' => 'easter + 1', // Lundi de Pâques
293-
'labour_day' => '05-01', // Fête du Travail
294-
'victory_day' => '05-08', // Victoire 1945
295-
'ascension_day' => 'easter + 39', // Ascension
296-
'whit_monday' => 'easter + 50', // Lundi de Pentecôte
297-
'national_day' => '07-14', // Fête Nationale
298-
'assumption_day' => '08-15', // Assomption
299-
'all_saints_day' => '11-01', // Toussaint
300-
'armistice_day' => '11-11', // Armistice 1918
301-
'christmas_day' => '12-25', // Noël
291+
'new_year' => '01-01', // Jour de l'An
292+
'easter' => 'easter', // Pâques
293+
'easter_monday' => 'easter + 1', // Lundi de Pâques
294+
'labour_day' => '05-01', // Fête du Travail
295+
'victory_day' => '05-08', // Victoire 1945
296+
'ascension_day' => 'easter + 39', // Ascension
297+
'whit_monday' => 'easter + 50', // Lundi de Pentecôte
298+
'national_day' => '07-14', // Fête Nationale
299+
'assumption_day' => '08-15', // Assomption
300+
'all_saints_day' => '11-01', // Toussaint
301+
'armistice_day' => '11-11', // Armistice 1918
302+
'christmas_day' => '12-25', // Noël
302303
],
303304
'CM' => [
304305
'new_year' => '01-01', // Jour de l'An

0 commit comments

Comments
 (0)