Skip to content

Commit 25a2d97

Browse files
committed
ext/intl: GH-20255 IntlDateFormatter adding proleptic gregorian calendar support.
To be consistent with DateImmutable class, we add the possibility to set the calendar in a (real) proleptic gregorian via a new flag constant. For now, intention needs to be clear but can be made default eventually. Close GH-21101
1 parent bbf82d7 commit 25a2d97

7 files changed

Lines changed: 85 additions & 9 deletions

File tree

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ PHP NEWS
1414
string. (Weilin Du)
1515
. Fixed IntlListFormatter::__construct() leaving stale global error state
1616
after successful calls. (Weilin Du)
17+
. Implemented GH-20255 (Add a predefined calendar constant in
18+
IntlDateFormatter for the proleptic gregorian calendar). (David Carlier)
1719

1820
- Reflection:
1921
. Added ReflectionAttribute::inNamespace(),

ext/intl/dateformat/dateformat.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class IntlDateFormatter
3131
/** @cvalue UCAL_TRADITIONAL */
3232
public const int TRADITIONAL = UNKNOWN;
3333

34+
public const int PROLEPTIC_GREGORIAN = -16;
35+
3436
/**
3537
* @param IntlCalendar|int|null $calendar
3638
*/

ext/intl/dateformat/dateformat_arginfo.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/intl/dateformat/dateformat_helpers.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ extern "C" {
2626
#include "../calendar/calendar_class.h"
2727
}
2828

29+
// Artificial value to set for a pure proleptic gregorian calendar (until icu provides it eventually)
30+
#define UCAL_PHP_PROLEPTIC_GREGORIAN -16
31+
2932
using icu::GregorianCalendar;
3033

3134
zend_result datefmt_process_calendar_arg(
@@ -43,22 +46,31 @@ zend_result datefmt_process_calendar_arg(
4346

4447
} else if (!calendar_obj) {
4548
zend_long v = calendar_long;
46-
if (v != (zend_long)UCAL_TRADITIONAL && v != (zend_long)UCAL_GREGORIAN) {
49+
if (v != (zend_long)UCAL_TRADITIONAL && v != (zend_long)UCAL_GREGORIAN &&
50+
v != (zend_long)UCAL_PHP_PROLEPTIC_GREGORIAN) {
4751
intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR,
4852
"Invalid value for calendar type; it must be one of "
4953
"IntlDateFormatter::TRADITIONAL (locale's default calendar) or"
50-
" IntlDateFormatter::GREGORIAN. Alternatively, it can be an "
54+
" IntlDateFormatter::GREGORIAN or IntlDateFormatter::PROLEPTIC_GREGORIAN."
55+
" Alternatively, it can be an "
5156
"IntlCalendar object");
5257
return FAILURE;
5358
} else if (v == (zend_long)UCAL_TRADITIONAL) {
5459
cal = Calendar::createInstance(locale, status);
5560
} else { //UCAL_GREGORIAN
56-
cal = new GregorianCalendar(locale, status);
61+
GregorianCalendar *gcal = new GregorianCalendar(locale, status);
62+
if (v == (zend_long)UCAL_PHP_PROLEPTIC_GREGORIAN) {
63+
// set the Julian to gregorian cutover date to -infinity
64+
// to make it a proleptic gregorian calendar
65+
// TODO: consider making it default behavior over typical "gregorian" icu like calendar
66+
gcal->setGregorianChange(-std::numeric_limits<double>::infinity(), status);
67+
}
68+
cal = gcal;
5769
}
70+
5871
calendar_owned = true;
5972

6073
cal_int_type = calendar_long;
61-
6274
} else if (calendar_obj) {
6375
cal = calendar_fetch_native_calendar(calendar_obj);
6476
if (cal == NULL) {

ext/intl/tests/dateformat___construct_bad_tz_cal.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ try {
2323
?>
2424
--EXPECT--
2525
IntlException: IntlDateFormatter::__construct(): No such time zone: "bad timezone"
26-
IntlException: IntlDateFormatter::__construct(): Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN. Alternatively, it can be an IntlCalendar object
26+
IntlException: IntlDateFormatter::__construct(): Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN or IntlDateFormatter::PROLEPTIC_GREGORIAN. Alternatively, it can be an IntlCalendar object
2727
TypeError: IntlDateFormatter::__construct(): Argument #5 ($calendar) must be of type IntlCalendar|int|null, stdClass given

ext/intl/tests/dateformat_errors.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ var_dump(intl_get_error_message());
2626

2727
?>
2828
--EXPECT--
29-
IntlException: IntlDateFormatter::__construct(): Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN. Alternatively, it can be an IntlCalendar object
29+
IntlException: IntlDateFormatter::__construct(): Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN or IntlDateFormatter::PROLEPTIC_GREGORIAN. Alternatively, it can be an IntlCalendar object
3030
NULL
31-
string(245) "IntlDateFormatter::create(): Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN. Alternatively, it can be an IntlCalendar object: U_ILLEGAL_ARGUMENT_ERROR"
31+
string(287) "IntlDateFormatter::create(): Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN or IntlDateFormatter::PROLEPTIC_GREGORIAN. Alternatively, it can be an IntlCalendar object: U_ILLEGAL_ARGUMENT_ERROR"
3232
NULL
33-
string(234) "datefmt_create(): Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN. Alternatively, it can be an IntlCalendar object: U_ILLEGAL_ARGUMENT_ERROR"
33+
string(276) "datefmt_create(): Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN or IntlDateFormatter::PROLEPTIC_GREGORIAN. Alternatively, it can be an IntlCalendar object: U_ILLEGAL_ARGUMENT_ERROR"

ext/intl/tests/gh20255.phpt

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
IntlDateFormatter with PROLEPTIC_GREGORIAN calendar
3+
--EXTENSIONS--
4+
intl
5+
--SKIPIF--
6+
<?php if (PHP_INT_SIZE < 8) die('skip 64-bit only'); ?>
7+
--FILE--
8+
<?php
9+
var_dump(IntlDateFormatter::PROLEPTIC_GREGORIAN);
10+
11+
// A pre-cutover date: DateTime uses proleptic Gregorian internally (cannot be represented in 32 bits systems)
12+
$dt = new DateTime('1200-03-01 12:00:00 UTC');
13+
14+
// New constant
15+
$fmt_proleptic = new IntlDateFormatter(
16+
'en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE,
17+
'UTC', IntlDateFormatter::PROLEPTIC_GREGORIAN, 'yyyy-MM-dd'
18+
);
19+
20+
// Existing workaround
21+
$cal = new IntlGregorianCalendar('UTC', 'en_US');
22+
$cal->setGregorianChange(-INF);
23+
$fmt_workaround = new IntlDateFormatter(
24+
'en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE,
25+
'UTC', $cal, 'yyyy-MM-dd'
26+
);
27+
28+
// Default hybrid Gregorian
29+
$fmt_hybrid = new IntlDateFormatter(
30+
'en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE,
31+
'UTC', IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd'
32+
);
33+
34+
$proleptic = $fmt_proleptic->format($dt);
35+
$workaround = $fmt_workaround->format($dt);
36+
$hybrid = $fmt_hybrid->format($dt);
37+
38+
// Should round-trip the proleptic Gregorian date correctly
39+
echo "Proleptic: $proleptic\n";
40+
41+
// Must match the manual workaround
42+
echo "Matches workaround: ";
43+
var_dump($proleptic === $workaround);
44+
45+
// Must differ from hybrid for pre-cutover dates
46+
echo "Differs from hybrid: ";
47+
var_dump($proleptic !== $hybrid);
48+
49+
?>
50+
--EXPECT--
51+
int(-16)
52+
Proleptic: 1200-03-01
53+
Matches workaround: bool(true)
54+
Differs from hybrid: bool(true)

0 commit comments

Comments
 (0)