diff --git a/.typos.toml b/.typos.toml index 42b8a797374..262c9605bf6 100644 --- a/.typos.toml +++ b/.typos.toml @@ -16,3 +16,5 @@ is_writeable = "is_writeable" [default.extend-words] # override false-positives Excluder = "Excluder" +# English ordinal suffix ("2nd"), from the "S" date format +nd = "nd" diff --git a/src/Type/Php/DateFunctionReturnTypeHelper.php b/src/Type/Php/DateFunctionReturnTypeHelper.php index 3cce51593b2..88464acf0d7 100644 --- a/src/Type/Php/DateFunctionReturnTypeHelper.php +++ b/src/Type/Php/DateFunctionReturnTypeHelper.php @@ -37,12 +37,6 @@ public function getTypeFromFormatType(Type $formatType, bool $useMicrosec): Type $type = TypeCombinator::union(...$types); } - if ($type->isNumericString()->no() && $formatType->isNonEmptyString()->yes()) { - $type = TypeCombinator::union($type, new IntersectionType([ - new StringType(), new AccessoryNonEmptyStringType(), - ])); - } - return $type; } @@ -84,6 +78,20 @@ private function buildReturnTypeFromFormat(string $formatString, bool $useMicros return $useMicrosec ? new IntersectionType([new StringType(), new AccessoryNonFalsyStringType(), new AccessoryNumericStringType()]) : new ConstantStringType('000'); + case 'a': + return $this->buildConstantStringsType(['am', 'pm']); + case 'A': + return $this->buildConstantStringsType(['AM', 'PM']); + case 'S': + return $this->buildConstantStringsType(['st', 'nd', 'rd', 'th']); + case 'D': + return $this->buildConstantStringsType(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']); + case 'l': + return $this->buildConstantStringsType(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']); + case 'M': + return $this->buildConstantStringsType(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']); + case 'F': + return $this->buildConstantStringsType(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']); } $date = date($formatString); @@ -117,4 +125,18 @@ private function buildNumericRangeType(int $min, int $max, bool $zeroPad): Type return new UnionType($types); } + /** + * @param list $values + */ + private function buildConstantStringsType(array $values): Type + { + $types = []; + + foreach ($values as $value) { + $types[] = new ConstantStringType($value); + } + + return new UnionType($types); + } + } diff --git a/tests/PHPStan/Analyser/nsrt/bug-15537.php b/tests/PHPStan/Analyser/nsrt/bug-15537.php new file mode 100644 index 00000000000..66ec43762e6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-15537.php @@ -0,0 +1,29 @@ +format( 'a' ) ); + echo translate_meridiem( date( 'A', $timestamp ) ); + + assertType( "'am'|'pm'", $datetime->format( 'a' ) ); + assertType( "'AM'|'PM'", $datetime->format( 'A' ) ); + assertType( "'nd'|'rd'|'st'|'th'", $datetime->format( 'S' ) ); + assertType( "'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", $datetime->format( 'D' ) ); + assertType( "'Friday'|'Monday'|'Saturday'|'Sunday'|'Thursday'|'Tuesday'|'Wednesday'", $datetime->format( 'l' ) ); + assertType( "'Apr'|'Aug'|'Dec'|'Feb'|'Jan'|'Jul'|'Jun'|'Mar'|'May'|'Nov'|'Oct'|'Sep'", $datetime->format( 'M' ) ); + assertType( "'April'|'August'|'December'|'February'|'January'|'July'|'June'|'March'|'May'|'November'|'October'|'September'", $datetime->format( 'F' ) ); + + assertType( "'am'|'pm'", date( 'a', $timestamp ) ); + assertType( "'Friday'|'Monday'|'Saturday'|'Sunday'|'Thursday'|'Tuesday'|'Wednesday'", date_format( $datetime, 'l' ) ); +} diff --git a/tests/PHPStan/Analyser/nsrt/date-format.php b/tests/PHPStan/Analyser/nsrt/date-format.php index e01319f2804..cd7b0ba7ab5 100644 --- a/tests/PHPStan/Analyser/nsrt/date-format.php +++ b/tests/PHPStan/Analyser/nsrt/date-format.php @@ -7,7 +7,7 @@ function (string $s): void { assertType('\'\'', date('')); assertType('string', date($s)); - assertType('non-falsy-string', date('D')); + assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", date('D')); assertType('numeric-string', date('Y')); assertType('numeric-string', date('Ghi')); }; @@ -15,7 +15,7 @@ function (string $s): void { function (\DateTime $dt, string $s): void { assertType('\'\'', date_format($dt, '')); assertType('string', date_format($dt, $s)); - assertType('non-falsy-string', date_format($dt, 'D')); + assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", date_format($dt, 'D')); assertType('numeric-string', date_format($dt, 'Y')); assertType('numeric-string', date_format($dt, 'Ghi')); }; @@ -23,7 +23,7 @@ function (\DateTime $dt, string $s): void { function (\DateTimeInterface $dt, string $s): void { assertType('\'\'', $dt->format('')); assertType('string', $dt->format($s)); - assertType('non-falsy-string', $dt->format('D')); + assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", $dt->format('D')); assertType('numeric-string', $dt->format('Y')); assertType('numeric-string', $dt->format('Ghi')); }; @@ -31,7 +31,7 @@ function (\DateTimeInterface $dt, string $s): void { function (\DateTime $dt, string $s): void { assertType('\'\'', $dt->format('')); assertType('string', $dt->format($s)); - assertType('non-falsy-string', $dt->format('D')); + assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", $dt->format('D')); assertType('numeric-string', $dt->format('Y')); assertType('numeric-string', $dt->format('Ghi')); }; @@ -39,7 +39,7 @@ function (\DateTime $dt, string $s): void { function (\DateTimeImmutable $dt, string $s): void { assertType('\'\'', $dt->format('')); assertType('string', $dt->format($s)); - assertType('non-falsy-string', $dt->format('D')); + assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", $dt->format('D')); assertType('numeric-string', $dt->format('Y')); assertType('numeric-string', $dt->format('Ghi')); };