Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ext/intl/dateformat/dateformat.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class IntlDateFormatter
/** @cvalue UCAL_TRADITIONAL */
public const int TRADITIONAL = UNKNOWN;

public const int PROLEPTIC_GREGORIAN = -16;

/**
* @param IntlCalendar|int|null $calendar
*/
Expand Down
8 changes: 7 additions & 1 deletion ext/intl/dateformat/dateformat_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 16 additions & 4 deletions ext/intl/dateformat/dateformat_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ extern "C" {
#include "../calendar/calendar_class.h"
}

// Artificial value to set for a pure proleptic gregorian calendar (until icu provides it eventually)
#define UCAL_PHP_PROLEPTIC_GREGORIAN -16

using icu::GregorianCalendar;

zend_result datefmt_process_calendar_arg(
Expand All @@ -43,22 +46,31 @@ zend_result datefmt_process_calendar_arg(

} else if (!calendar_obj) {
zend_long v = calendar_long;
if (v != (zend_long)UCAL_TRADITIONAL && v != (zend_long)UCAL_GREGORIAN) {
if (v != (zend_long)UCAL_TRADITIONAL && v != (zend_long)UCAL_GREGORIAN &&
v != (zend_long)UCAL_PHP_PROLEPTIC_GREGORIAN) {
intl_errors_set(err, U_ILLEGAL_ARGUMENT_ERROR,
"Invalid value for calendar type; it must be one of "
"IntlDateFormatter::TRADITIONAL (locale's default calendar) or"
" IntlDateFormatter::GREGORIAN. Alternatively, it can be an "
" IntlDateFormatter::GREGORIAN or IntlDateFormatter::PROLEPTIC_GREGORIAN."
" Alternatively, it can be an "
"IntlCalendar object");
return FAILURE;
} else if (v == (zend_long)UCAL_TRADITIONAL) {
cal = Calendar::createInstance(locale, status);
} else { //UCAL_GREGORIAN
cal = new GregorianCalendar(locale, status);
GregorianCalendar *gcal = new GregorianCalendar(locale, status);
if (v == (zend_long)UCAL_PHP_PROLEPTIC_GREGORIAN) {
// set the Julian to gregorian cutover date to -infinity
// to make it a proleptic gregorian calendar
// TODO: consider making it default behavior over typical "gregorian" icu like calendar
gcal->setGregorianChange(-std::numeric_limits<double>::infinity(), status);
}
cal = gcal;
}

calendar_owned = true;

cal_int_type = calendar_long;

} else if (calendar_obj) {
cal = calendar_fetch_native_calendar(calendar_obj);
if (cal == NULL) {
Expand Down
2 changes: 1 addition & 1 deletion ext/intl/tests/dateformat___construct_bad_tz_cal.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ try {
?>
--EXPECT--
IntlException: IntlDateFormatter::__construct(): No such time zone: "bad timezone"
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
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
TypeError: IntlDateFormatter::__construct(): Argument #5 ($calendar) must be of type IntlCalendar|int|null, stdClass given

6 changes: 3 additions & 3 deletions ext/intl/tests/dateformat_errors.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ var_dump(intl_get_error_message());

?>
--EXPECT--
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
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
NULL
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"
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"
NULL
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"
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"
54 changes: 54 additions & 0 deletions ext/intl/tests/gh20255.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
--TEST--
IntlDateFormatter with PROLEPTIC_GREGORIAN calendar
--EXTENSIONS--
intl
--SKIPIF--
<?php if (PHP_INT_SIZE < 8) die('skip 64-bit only'); ?>
--FILE--
<?php
var_dump(IntlDateFormatter::PROLEPTIC_GREGORIAN);

// A pre-cutover date: DateTime uses proleptic Gregorian internally (cannot be represented in 32 bits systems)
$dt = new DateTime('1200-03-01 12:00:00 UTC');

// New constant
$fmt_proleptic = new IntlDateFormatter(
'en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE,
'UTC', IntlDateFormatter::PROLEPTIC_GREGORIAN, 'yyyy-MM-dd'
);

// Existing workaround
$cal = new IntlGregorianCalendar('UTC', 'en_US');
$cal->setGregorianChange(-INF);
$fmt_workaround = new IntlDateFormatter(
'en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE,
'UTC', $cal, 'yyyy-MM-dd'
);

// Default hybrid Gregorian
$fmt_hybrid = new IntlDateFormatter(
'en_US', IntlDateFormatter::NONE, IntlDateFormatter::NONE,
'UTC', IntlDateFormatter::GREGORIAN, 'yyyy-MM-dd'
);

$proleptic = $fmt_proleptic->format($dt);
$workaround = $fmt_workaround->format($dt);
$hybrid = $fmt_hybrid->format($dt);

// Should round-trip the proleptic Gregorian date correctly
echo "Proleptic: $proleptic\n";

// Must match the manual workaround
echo "Matches workaround: ";
var_dump($proleptic === $workaround);

// Must differ from hybrid for pre-cutover dates
echo "Differs from hybrid: ";
var_dump($proleptic !== $hybrid);

?>
--EXPECT--
int(-16)
Proleptic: 1200-03-01
Matches workaround: bool(true)
Differs from hybrid: bool(true)
Loading