Skip to content

Commit b713fd2

Browse files
westonruterclaudestaabm
authored
Infer literal types for word date format characters (#6838)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Markus Staab <maggus.staab@googlemail.com>
1 parent b06df46 commit b713fd2

4 files changed

Lines changed: 71 additions & 11 deletions

File tree

‎.typos.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ is_writeable = "is_writeable"
1818
[default.extend-words]
1919
# override false-positives
2020
Excluder = "Excluder"
21+
# English ordinal suffix ("2nd"), from the "S" date format
22+
nd = "nd"

‎src/Type/Php/DateFunctionReturnTypeHelper.php‎

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,6 @@ public function getTypeFromFormatType(Type $formatType, bool $useMicrosec): Type
3737
$type = TypeCombinator::union(...$types);
3838
}
3939

40-
if ($type->isNumericString()->no() && $formatType->isNonEmptyString()->yes()) {
41-
$type = TypeCombinator::union($type, new IntersectionType([
42-
new StringType(), new AccessoryNonEmptyStringType(),
43-
]));
44-
}
45-
4640
return $type;
4741
}
4842

@@ -84,6 +78,21 @@ private function buildReturnTypeFromFormat(string $formatString, bool $useMicros
8478
return $useMicrosec
8579
? new IntersectionType([new StringType(), new AccessoryNonFalsyStringType(), new AccessoryNumericStringType()])
8680
: new ConstantStringType('000');
81+
// values which do not depend on the locale
82+
case 'a':
83+
return $this->buildConstantStringsType(['am', 'pm']);
84+
case 'A':
85+
return $this->buildConstantStringsType(['AM', 'PM']);
86+
case 'S':
87+
return $this->buildConstantStringsType(['st', 'nd', 'rd', 'th']);
88+
case 'D':
89+
return $this->buildConstantStringsType(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
90+
case 'l':
91+
return $this->buildConstantStringsType(['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']);
92+
case 'M':
93+
return $this->buildConstantStringsType(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']);
94+
case 'F':
95+
return $this->buildConstantStringsType(['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']);
8796
}
8897

8998
$date = date($formatString);
@@ -117,4 +126,18 @@ private function buildNumericRangeType(int $min, int $max, bool $zeroPad): Type
117126
return new UnionType($types);
118127
}
119128

129+
/**
130+
* @param list<string> $values
131+
*/
132+
private function buildConstantStringsType(array $values): Type
133+
{
134+
$types = [];
135+
136+
foreach ($values as $value) {
137+
$types[] = new ConstantStringType($value);
138+
}
139+
140+
return new UnionType($types);
141+
}
142+
120143
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug15537;
4+
5+
use DateTimeImmutable;
6+
use function PHPStan\Testing\assertType;
7+
8+
/**
9+
* @param 'am'|'pm'|'AM'|'PM' $meridiem
10+
*/
11+
function translate_meridiem( string $meridiem ): string {
12+
return $meridiem;
13+
}
14+
15+
/**
16+
* @param non-empty-string $nonES
17+
*/
18+
function test( DateTimeImmutable $datetime, int $timestamp, string $nonES ): void {
19+
echo translate_meridiem( $datetime->format( 'a' ) );
20+
echo translate_meridiem( date( 'A', $timestamp ) );
21+
22+
assertType( "' '|'-'", date($timestamp ? ' ' : '-') );
23+
assertType( "non-empty-string", date($nonES) );
24+
25+
assertType( "'am'|'pm'", $datetime->format( 'a' ) );
26+
assertType( "'AM'|'PM'", $datetime->format( 'A' ) );
27+
assertType( "'nd'|'rd'|'st'|'th'", $datetime->format( 'S' ) );
28+
assertType( "'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", $datetime->format( 'D' ) );
29+
assertType( "'Friday'|'Monday'|'Saturday'|'Sunday'|'Thursday'|'Tuesday'|'Wednesday'", $datetime->format( 'l' ) );
30+
assertType( "'Apr'|'Aug'|'Dec'|'Feb'|'Jan'|'Jul'|'Jun'|'Mar'|'May'|'Nov'|'Oct'|'Sep'", $datetime->format( 'M' ) );
31+
assertType( "'April'|'August'|'December'|'February'|'January'|'July'|'June'|'March'|'May'|'November'|'October'|'September'", $datetime->format( 'F' ) );
32+
33+
assertType( "'am'|'pm'", date( 'a', $timestamp ) );
34+
assertType( "'Friday'|'Monday'|'Saturday'|'Sunday'|'Thursday'|'Tuesday'|'Wednesday'", date_format( $datetime, 'l' ) );
35+
}

‎tests/PHPStan/Analyser/nsrt/date-format.php‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,39 @@
77
function (string $s): void {
88
assertType('\'\'', date(''));
99
assertType('string', date($s));
10-
assertType('non-falsy-string', date('D'));
10+
assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", date('D'));
1111
assertType('numeric-string', date('Y'));
1212
assertType('numeric-string', date('Ghi'));
1313
};
1414

1515
function (\DateTime $dt, string $s): void {
1616
assertType('\'\'', date_format($dt, ''));
1717
assertType('string', date_format($dt, $s));
18-
assertType('non-falsy-string', date_format($dt, 'D'));
18+
assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", date_format($dt, 'D'));
1919
assertType('numeric-string', date_format($dt, 'Y'));
2020
assertType('numeric-string', date_format($dt, 'Ghi'));
2121
};
2222

2323
function (\DateTimeInterface $dt, string $s): void {
2424
assertType('\'\'', $dt->format(''));
2525
assertType('string', $dt->format($s));
26-
assertType('non-falsy-string', $dt->format('D'));
26+
assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", $dt->format('D'));
2727
assertType('numeric-string', $dt->format('Y'));
2828
assertType('numeric-string', $dt->format('Ghi'));
2929
};
3030

3131
function (\DateTime $dt, string $s): void {
3232
assertType('\'\'', $dt->format(''));
3333
assertType('string', $dt->format($s));
34-
assertType('non-falsy-string', $dt->format('D'));
34+
assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", $dt->format('D'));
3535
assertType('numeric-string', $dt->format('Y'));
3636
assertType('numeric-string', $dt->format('Ghi'));
3737
};
3838

3939
function (\DateTimeImmutable $dt, string $s): void {
4040
assertType('\'\'', $dt->format(''));
4141
assertType('string', $dt->format($s));
42-
assertType('non-falsy-string', $dt->format('D'));
42+
assertType("'Fri'|'Mon'|'Sat'|'Sun'|'Thu'|'Tue'|'Wed'", $dt->format('D'));
4343
assertType('numeric-string', $dt->format('Y'));
4444
assertType('numeric-string', $dt->format('Ghi'));
4545
};

0 commit comments

Comments
 (0)