Skip to content

Proposal: add IntlDatePatternGenerator #6771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 10, 2021

Conversation

deltragon
Copy link
Contributor

@deltragon deltragon commented Mar 14, 2021

ICU exposes the DateTimePatternGenerator class that allows generating a formatting pattern from a given "skeleton" for a given locale.
This allows more flexibility than the current $format argument of IntlDateFormatter::format(), which takes either a few pre-defined constants or a hard-coded format.
This functionality also has been requested in the past.

For example, if an application requires a format that will always use the long version of a year, but the short version of a month (eg. "MM/dd/YYYY" for "en_US", or "dd.MM.YYYY" for "de_DE"), one is out of luck.
IntlDateFormatter::SHORT will use the short year for "de_DE" ("dd.MM.YY"), and IntlDateFormatter::MEDIUM will use the long version of a month for "en_US" ("MMM dd, YYYY").

With IntlDatePatternGenerator::getBestPattern(), a skeleton can be provided to generate the appropriate pattern for a given locale.
In the use-case given above, the skeleton "YYYYMMdd" will generate the desired patterns for each locale, which can then be passed to IntlDateFormatter::format().

Note about the implementation:
This had been previously implemented as a static method on IntlDateFormatter, creating a new DateTimePatternGenerator on every call.
It is now a separate class that has to be instantiated before use. This leaves us open to implementing other methods/options provided by the underlying DateTimePatternGenerator. I am not sure if there is any use in those, however, as getBestPattern() seems to be the only one with a real-world use-case to me.

(Note: I am aware that this needs to go to internals/an RFC first. I'm currently having issues with my email provider that prevent me from subscribing to/sending to the list. I will send this proposal once these are resolved).

--SKIPIF--
<?php
if (!extension_loaded('intl')) die('skip intl extension not enabled'); ?>
<?php if (version_compare(INTL_ICU_VERSION, '50.1.2', '<')) die('skip for ICU < 50.1.2'); ?>
Copy link
Contributor Author

@deltragon deltragon Mar 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an aside:
This line is included in almost all intl dateformat tests - there, it is used in this form:

<?php if (version_compare(INTL_ICU_VERSION, '50.1.2') >=  0) die('skip for ICU < 50.1.2'); ?>

This, however, ends up skipping the test on my machine (INTL_ICU_VERSION === '67.1'). I assume version_compare is used incorrectly here? Should this be fixed for all other tests as well (and switched to what is the more readable form in my opinion)? Is this line still necessary (as PHP 7.4 requires ICU 50.1)?

Copy link
Member

@kocsismate kocsismate Mar 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skip messages are a bit deceptive: if fact, die('skip for ICU < 50.1.2'); means that the test is skipped because it's for ICU < 50.1.2. It also took quite some time for me to get accustomed to this.

If ICU 50.1 is required now then these tests are probably not in use anymore 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I don't think the version_compare is necessary for this test.

Copy link
Contributor Author

@deltragon deltragon Mar 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, that explains the message.
I still dont understand why these tests are not required for newer ICU versions - I would expect basic tests like

<?php if (version_compare(INTL_ICU_VERSION, '50.1.2') >= 0) die('skip for ICU < 50.1.2'); ?>

to still be in use for newer versions/after PHP 7.4.
(For an incomplete list of affected tests, this is the commit that starts skipping a bunch of them for ICU >= 51.2 (was later changed to 50.1): 4840b0a)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests for other ICU versions are under _variant2, _variant3 etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ICU 50.1 is required now then these tests are probably not in use anymore thinking

I believe the reason why these tests still exist is that our requirement is ICU 50.1, while this checks for ICU 50.1.2.

Though maybe we can bump up the requirements slightly, it looks like RHEL 7 nowadays ships with ICU 50.2 (according to https://rpms.remirepo.net/rpmphp/zoom.php?rpm=icu). @remicollet Would a 50.2 requirement be safe?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same RHEL version comes with PHP 5: https://rpms.remirepo.net/rpmphp/zoom.php?rpm=php so is RHEL even supposed to dictate what PHP 8 should do?

Copy link
Member

@kocsismate kocsismate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'd look forward to an IntlDateTimePatternGenerator class, even though it would expose only a small subset of all the ICU capabilities. IMO the static method on IntlDateFormatter doesn't really fit there.

--SKIPIF--
<?php
if (!extension_loaded('intl')) die('skip intl extension not enabled'); ?>
<?php if (version_compare(INTL_ICU_VERSION, '50.1.2', '<')) die('skip for ICU < 50.1.2'); ?>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I don't think the version_compare is necessary for this test.

@deltragon deltragon force-pushed the intl-datetime-patterngenerator branch from d3bb97c to 2eaed3c Compare March 28, 2021 14:35
@deltragon
Copy link
Contributor Author

@kocsismate Thank you for the review!
I have switched to a IntlDateTimePatternGenerator class as suggested, and addressed your other comments as well.
I have put the new files in ext/intl/dateformat/ as well, since they belong there thematically - or would you put them in a separate folder?
There are also some names of internal methods/macros I am not entirely sure about, is it okay to leave them as is or should I try to be more consistent there? "DateTimePatternGenerator" is a rather unwieldy name, unfortunately.

@deltragon deltragon changed the title Proposal: add IntlDateFormatter::getBestPattern() Proposal: add IntlDateTimePatternGenerator Mar 28, 2021
DTPATTERNGEN_METHOD_FETCH_OBJECT_NO_CHECK;

if (dtpgo->dtpg != NULL) {
intl_errors_set(DTPATTERNGEN_ERROR_P(dtpgo), U_ILLEGAL_ARGUMENT_ERROR, "datetimepatterngenerator_create: cannot call constructor twice", 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason of this? I see that the check is from IntlDateFormatter, but I don't know why these are necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For IntlDateFormatter, there is this test:

$x = new IntlDateFormatter('en', 1, 1);
var_dump($x->__construct('en', 1, 1));

How is that (anti-)pattern handled in other cases?


intl_stringFromChar(skeleton_uncleaned, skeleton_str, skeleton_len, DTPATTERNGEN_ERROR_CODE_P(dtpgo));

INTL_METHOD_CHECK_STATUS(dtpgo, "datetimepatterngenerator_get_best_pattern: skeleton was not a valid UTF-8 string");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
INTL_METHOD_CHECK_STATUS(dtpgo, "datetimepatterngenerator_get_best_pattern: skeleton was not a valid UTF-8 string");
INTL_METHOD_CHECK_STATUS(dtpgo, "Skeleton is not a valid UTF-8 string");

So the function name shouldn't be added (it's superfluous), the message should start with a capital character, and AFAIR we don't use past tense

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm wondering if we can consider an invalid UTF-8 skeleton a programmatic error? As far as I see, we can, because the input should come from the programmer, not from the user. If my hypothesis is ok, then we could throw an exception instead of these warnings, like this:
zend_argument_value_error(2, "must be a valid UTF-8 string") (the 2 should be adapted in case of the method counterpart, since $skeleton is the 1st parameter there).

Last year, we migrated hundreds of warnings to exceptions, so it would be nice to follow this practice here as well, if possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have adjusted the messages.
I would agree that this is most likely a programming error - I used a warning here for consistency with similar cases (eg. IntlDateFormatter::setPattern()), but I can switch to using an exception if preferred.

@deltragon deltragon force-pushed the intl-datetime-patterngenerator branch 2 times, most recently from cb8c1c5 to 5084f32 Compare March 28, 2021 18:56
@deltragon deltragon force-pushed the intl-datetime-patterngenerator branch from 5084f32 to 068cceb Compare April 30, 2021 15:56
@deltragon deltragon changed the title Proposal: add IntlDateTimePatternGenerator Proposal: add IntlDatePatternGenerator Apr 30, 2021
@deltragon deltragon force-pushed the intl-datetime-patterngenerator branch from 05f307a to 5b74562 Compare June 7, 2021 19:38
@deltragon
Copy link
Contributor Author

Rebased and removed the legacy procedural API.

@nikic
Copy link
Member

nikic commented Jun 8, 2021

Looks like this needs a rebase. While at it, it would be great to add a mention of the new class in the UPGRADING file.

@deltragon deltragon force-pushed the intl-datetime-patterngenerator branch from 7e50a00 to 19ffd97 Compare June 8, 2021 12:33
@deltragon
Copy link
Contributor Author

Rebased.

@jrfnl
Copy link
Contributor

jrfnl commented Feb 27, 2022

Just checking - am I reading things correctly when I say that the two procedural functions mentioned in the RFC were not implemented in the final merged implementation ?
https://wiki.php.net/rfc/intldatetimepatterngenerator#proposal

@cmb69
Copy link
Member

cmb69 commented Feb 27, 2022

@jrfnl, ugh, apparently not. I wonder whether that has been explicitly discussed, or was just forgotten.

@deltragon
Copy link
Contributor Author

@jrfnl That was discussed and dropped during the vote, see: https://externals.io/message/114473#114658 and the messages following it.

@jrfnl
Copy link
Contributor

jrfnl commented Feb 27, 2022

Thanks @cmb69 and @deltragon for your quick responses. Just wanted to verify as I'm working on adding detecting for the PHP 8.1 features/deprecations to PHPCompatibility.

@deltragon deltragon deleted the intl-datetime-patterngenerator branch February 27, 2022 20:35
jrfnl added a commit to PHPCompatibility/PHPCompatibility that referenced this pull request Mar 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants