Skip to content
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

8317742: ISO Standard Date Format implementation consistency on DateTimeFormatter and String.format #16033

Closed
wants to merge 11 commits into from

Conversation

wenshao
Copy link
Contributor

@wenshao wenshao commented Oct 3, 2023

j.u.Formatter now prints "%tF" (iso standard date) and the result is incorrect when processing year < 0 or year > 9999


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issues

  • JDK-8317742: ISO Standard Date Format implementation consistency on DateTimeFormatter and String.format (Bug - P4)
  • JDK-8317752: ISO Standard Date Format implementation consistency on DateTimeFormatter and String.format (CSR) (Withdrawn)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/16033/head:pull/16033
$ git checkout pull/16033

Update a local copy of the PR:
$ git checkout pull/16033
$ git pull https://git.openjdk.org/jdk.git pull/16033/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 16033

View PR using the GUI difftool:
$ git pr show -t 16033

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/16033.diff

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Oct 3, 2023

👋 Welcome back wenshao! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Oct 3, 2023

@wenshao The following labels will be automatically applied to this pull request:

  • core-libs
  • i18n

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added core-libs core-libs-dev@openjdk.org i18n i18n-dev@openjdk.org labels Oct 3, 2023
@wenshao
Copy link
Contributor Author

wenshao commented Oct 3, 2023

/label remove i18n

@openjdk openjdk bot removed the i18n i18n-dev@openjdk.org label Oct 3, 2023
@openjdk
Copy link

openjdk bot commented Oct 3, 2023

@wenshao
The i18n label was successfully removed.

@wenshao
Copy link
Contributor Author

wenshao commented Oct 3, 2023

@cl4es could you please review my code? Thank you!

@wenshao wenshao changed the title String Format Fix ""%tF" (iso standard date) incorrect String Format Fix "%tF" (iso standard date) incorrect Oct 3, 2023
@cl4es
Copy link
Member

cl4es commented Oct 4, 2023

I think this deserves a discussion on the mailing list before jumping to a PR, as neither %tF nor the ISO-8601 standard it defers to is particularly well-defined outside of the range 0-9999

@wenshao
Copy link
Contributor Author

wenshao commented Oct 4, 2023

I think this deserves a discussion on the mailing list before jumping to a PR, as neither %tF nor the ISO-8601 standard it defers to is particularly well-defined outside of the range 0-9999

Thank you @cl4es for your suggestion, I have sent an email to mailing.

j.t.DateTimeFormatter defines ISO_LOCAL_DATE, j.u.Formatter.DateTime also defines ISO_STANDARD_DATE ("%tF"), and now their behavior is different outside the range of [0,9999], We run the following code and we can see their different behaviors:

DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
int[] years = {-99999, -9999, -999, -99, -9, 0, 9, 99, 999, 1999, 2999, 9999, 99999};
for (int year : years) {
    LocalDate localDate = LocalDate.of(year, 1, 1);
    System.out.println(formatter.format(localDate) + "\t\t->\t\t" + "%tF".formatted(localDate));
}
  • output
-99999-01-01  ->  100000-01-01
-9999-01-01   ->  10000-01-01
-0999-01-01   ->  1000-01-01
-0099-01-01   ->  0100-01-01
-0009-01-01   ->  0010-01-01
0000-01-01    ->  0001-01-01
0009-01-01    ->  0009-01-01
0099-01-01    ->  0099-01-01
0999-01-01    ->  0999-01-01
1999-01-01    ->  1999-01-01
2999-01-01    ->  2999-01-01
9999-01-01    ->  9999-01-01
+99999-01-01  ->  99999-01-01

@wenshao wenshao changed the title String Format Fix "%tF" (iso standard date) incorrect 8317742: ISO Starndard Date Format implementation consistency on DateTimeFormatter and String.format Oct 9, 2023
@openjdk openjdk bot added the rfr Pull request is ready for review label Oct 9, 2023
@mlbridge
Copy link

mlbridge bot commented Oct 9, 2023

@RogerRiggs
Copy link
Contributor

This PR will need an evaluation of the compatibility risk of changing the behavior, describing existing uses and scope and how they might be affected by the change.

@RogerRiggs
Copy link
Contributor

/csr required

@openjdk
Copy link

openjdk bot commented Oct 9, 2023

@RogerRiggs usage: /csr [needed|unneeded], requires that the issue the pull request refers to links to an approved CSR request.

@RogerRiggs
Copy link
Contributor

/csr needed

@openjdk openjdk bot added the csr Pull request needs approved CSR before integration label Oct 9, 2023
@openjdk
Copy link

openjdk bot commented Oct 9, 2023

@RogerRiggs has indicated that a compatibility and specification (CSR) request is needed for this pull request.

@wenshao please create a CSR request for issue JDK-8317742 with the correct fix version. This pull request cannot be integrated until the CSR request is approved.

@RogerRiggs
Copy link
Contributor

The CSR is created, if you can provide (as a comment on the PR) the descriptions as described in the template, I can put them in the CSR.
Thanks.

@wenshao
Copy link
Contributor Author

wenshao commented Oct 9, 2023

When String.format handles "%tF" Specifier, it uses the same behavior as DateTimeFormatter. This will cause the value of printed year to be incompatible with the previous behavior.

The year value range [-∞, -1000] changes:

String.format("%tF", LocalDate.of(-10000, 1, 1))
// before 10001-01-01
// after  -10000-01-01

The year value range [-9999, -1] changes:

String.format("%tF", LocalDate.of(-1, 1, 1))
// before 0002-01-01
// after  -0001-01-01

The year value range [-∞, -1000] changes:

String.format("%tF", LocalDate.of(10000, 1, 1)):
// before 10000-01-01
// after  +10000-01-01

And the "%tF" format no longer handles Locale when printing numbers.

Locale.setDefault(
        Locale.forLanguageTag("th-TH-u-nu-thai"));
String.format("%tF", LocalDate.of(2001, 1, 1));
// before ๒๐๐๑-๐๑-๐๑
// after 2001-01-01

@RogerRiggs
Copy link
Contributor

The template in the CSR has sections for: Summary, Problem, Solution, and Specification and a separate section for a prose description of the compatibility risk.
Please recompose the CSR content to fit those sections. Thanks

@wenshao
Copy link
Contributor Author

wenshao commented Oct 12, 2023

Summary

Keep the behavior of String.format('%tF') when printing LocalDate consistent with the behavior of DateTimeFormatter.ISO_LOCAL_DATE.

Problem

  1. When using String.format('%tF', localDate), use get(ChronoField.YEAR_OF_ERA). When the value range is not [0,9999], the behavior is inconsistent with DateTimeFormatter.ISO_LOCAL_DATE.format.
  2. When String.format('%tF', localDate), Locale will be processed, which is inconsistent with the standard.
  • the year value range [-∞, -1] changes:
String.format("%tF", LocalDate.of(-10000, 1, 1))
// String.format 10001-01-01
// DateTimeFormatter  -99999-01-01
  • the year value range [10000, +∞] changes:
String.format("%tF", LocalDate.of(10000, 1, 1)):
// String.format 10000-01-01
// DateTimeFormatter.format  +10000-01-01
And the "%tF" format no longer handles Locale when printing numbers.
Locale.setDefault(
        Locale.forLanguageTag("th-TH-u-nu-thai"));
String.format("%tF", LocalDate.of(2001, 1, 1));
// String.format ๒๐๐๑-๐๑-๐๑
// DateTimeFormatter.ISO_LOCAL_DATE.format 2001-01-01

Solution

  1. When String.format('%tF', localDate) uses ChronoField.YEAR instead of ChronoField.YEAR_OF_ERA, when year > 9999, the prefix is added with '+'
  2. And don't use Locale to print numbers.

Specification

  • [-∞, -1000]
String.format("%tF", LocalDate.of(-10000, 1, 1))
// -10000-01-01

The year value range [-999, -1], padded to 4 digits

String.format("%tF", LocalDate.of(-1, 1, 1))
// -0001-01-01

The year value range [1, 9999], padded to 4 digits

String.format("%tF", LocalDate.of(1, 1, 1))
// 0001-01-01
  • The year value range [10000, -∞], prefix prints '+'
String.format("%tF", LocalDate.of(10000, 1, 1))
// +10000-01-01

Risk

  1. When String.format('%tF') processes LocalDate/ZonedDateTime/OffsetDateTime, if the value range of year is not in [0-9999], the output will be different from before.
  2. When the Locale of String.format('%tF') is "th-TH-u-nu-thai", the printed result is different from before.

@eirbjo
Copy link
Contributor

eirbjo commented Oct 16, 2023

/issue JDK-8317742

@openjdk
Copy link

openjdk bot commented Oct 16, 2023

@eirbjo Only the author (@wenshao) is allowed to issue the /issue command.

@eirbjo
Copy link
Contributor

eirbjo commented Oct 16, 2023

@wenshao I fixed the spelling of "standard" in the JBS issue and CSR. Can you update the PR title as well? Thanks!

@wenshao wenshao changed the title 8317742: ISO Starndard Date Format implementation consistency on DateTimeFormatter and String.format 8317742: ISO Standard Date Format implementation consistency on DateTimeFormatter and String.format Oct 16, 2023
@openjdk
Copy link

openjdk bot commented Nov 9, 2023

@RogerRiggs The CSR requirement cannot be removed as CSR issues already exist. Please withdraw JDK-8317752 and then use the command /csr unneeded again.

@RogerRiggs
Copy link
Contributor

/csr unneeded

@openjdk openjdk bot removed the csr Pull request needs approved CSR before integration label Nov 9, 2023
@openjdk
Copy link

openjdk bot commented Nov 9, 2023

@RogerRiggs determined that a CSR request is not needed for this pull request.

@openjdk
Copy link

openjdk bot commented Nov 9, 2023

@wenshao This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8317742: ISO Standard Date Format implementation consistency on DateTimeFormatter and String.format

Reviewed-by: rriggs, naoto

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 621 new commits pushed to the master branch:

  • fcb4df2: 8320192: SHAKE256 does not work correctly if n >= 137
  • 2b4e991: 8320208: Update Public Suffix List to b5bf572
  • 6b96bb6: 8319777: Zero: Support 8-byte cmpxchg
  • 020c900: 8320052: Zero: Use __atomic built-ins for atomic RMW operations
  • 30d8953: 8275889: Search dialog has redundant scrollbars
  • cee54de: 8319988: Wrong heading for inherited nested classes
  • 32098ce: 8320348: test/jdk/java/io/File/GetAbsolutePath.windowsDriveRelative fails if working directory is not on drive C
  • a2c0fa6: 8320372: test/jdk/sun/security/x509/DNSName/LeadingPeriod.java validity check failed
  • 3aefd1c: 8320234: Merge doclint.Env.AccessKind with tool.AccessKind
  • d6d7bdc: 8319817: Charset constructor should make defensive copy of aliases
  • ... and 611 more: https://git.openjdk.org/jdk/compare/26c21f50a39a4ae0425b6e7ae63afbdaf627e710...master

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@RogerRiggs, @naotoj) but any other Committer may sponsor as well.

➡️ To flag this PR as ready for integration with the above commit message, type /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Nov 9, 2023
@wenshao
Copy link
Contributor Author

wenshao commented Nov 11, 2023

/integrate

@openjdk openjdk bot added the sponsor Pull request is ready to be sponsored label Nov 11, 2023
@openjdk
Copy link

openjdk bot commented Nov 11, 2023

@wenshao
Your change (at version 4e518b3) is now ready to be sponsored by a Committer.

@wenshao
Copy link
Contributor Author

wenshao commented Nov 17, 2023

@naotoj @RogerRiggs Can it be integrated?

@naotoj
Copy link
Member

naotoj commented Nov 17, 2023

I'll take another look at your latest changes and tests.

Comment on lines 4503 to 4507
if (t instanceof ZonedDateTime
|| t instanceof OffsetDateTime
|| t instanceof LocalDateTime
|| t instanceof LocalDate) {
yearField = ChronoField.YEAR;
Copy link
Contributor

Choose a reason for hiding this comment

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

This enumeration of type doesn't seem very principled and may not be correct for an arbitrary Temporal.
(But so far, I don't have a better suggestion).

Copy link
Member

Choose a reason for hiding this comment

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

Haven't tried, but I think t.getChronology() instanceof IsoChronology should work

Copy link
Member

Choose a reason for hiding this comment

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

Now I tried and it didn't compile 🙂 It should have been t.query(TemporalQueries.chronology()) instanceof IsoChronology

Copy link
Contributor

Choose a reason for hiding this comment

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

JapaneseChronology is an IsoChronology so run the test again (before committing) to make sure.

Copy link
Member

Choose a reason for hiding this comment

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

JapaneseChronology is not extending IsoChronology, and that is the gist of the change I suggested.

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 added the testcase of IsoChronology and it also passed.

Copy link
Contributor

Choose a reason for hiding this comment

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

The distinction is pretty subtle, I was referring to the Chronology.isISOBased() method. (True for JapaneseChronology).
IsoChronology is final, so there are no subclasses.
This does address the compatibility issue and is an improvement over the original proposal.

@openjdk openjdk bot removed the sponsor Pull request is ready to be sponsored label Nov 18, 2023
Copy link
Member

@naotoj naotoj left a comment

Choose a reason for hiding this comment

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

Looks good. Thanks for fixing the issue. Please change the copyright year from 2022 to 2023 in the test before integrating.

@wenshao
Copy link
Contributor Author

wenshao commented Nov 21, 2023

/integrate

@openjdk openjdk bot added the sponsor Pull request is ready to be sponsored label Nov 21, 2023
@openjdk
Copy link

openjdk bot commented Nov 21, 2023

@wenshao
Your change (at version fec8f5e) is now ready to be sponsored by a Committer.

@naotoj
Copy link
Member

naotoj commented Nov 21, 2023

/sponsor

@openjdk
Copy link

openjdk bot commented Nov 21, 2023

Going to push as commit 61d81d6.
Since your change was applied there have been 636 commits pushed to the master branch:

  • c4aba87: 8320272: Make method_entry_barrier address shared
  • 9311749: 8320526: Use title case in building.md
  • 9598ff8: 8315969: compiler/rangechecks/TestRangeCheckHoistingScaledIV.java: make flagless
  • 53eb6f1: 8187591: -Werror turns incubator module warning to an error
  • 570dffb: 8310807: java/nio/channels/DatagramChannel/Connect.java timed out
  • 21a59b9: 8282726: java/net/vthread/BlockingSocketOps.java timeout/hang intermittently on Windows
  • 9232070: 8318480: Obsolete UseCounterDecay and remove CounterDecayMinIntervalLength
  • e055fae: 8264425: Update building.md on non-English locales on Windows
  • c4aee66: 8320222: Wrong bytecode accepted, and StackMap table generated
  • 604d29a: 8304446: javap --system flag doesn't override system APIs
  • ... and 626 more: https://git.openjdk.org/jdk/compare/26c21f50a39a4ae0425b6e7ae63afbdaf627e710...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Nov 21, 2023
@openjdk openjdk bot closed this Nov 21, 2023
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review sponsor Pull request is ready to be sponsored labels Nov 21, 2023
@openjdk
Copy link

openjdk bot commented Nov 21, 2023

@naotoj @wenshao Pushed as commit 61d81d6.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs core-libs-dev@openjdk.org i18n i18n-dev@openjdk.org integrated Pull request has been integrated
5 participants