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

Add support for width-aware noon and midnight options for DayPeriod #444

Merged
merged 17 commits into from
Jan 30, 2021

Conversation

nordzilla
Copy link
Member

Fixes #435

This patch adds functionality and tests for width-aware DayPeriod patterns for noon and midnight.

https://unicode.org/reports/tr35/tr35-dates.html#dfst-period

According to the spec, when formatting with this pattern:

  • The noon options are used from 12:00:00 before 12:01:00
  • The midnight options are used from 00:00:00 before 00:01:00
  • If the locale does not support noon and/or midnight the appropriate AmPm option will be used as a fallback.
  • All other times will continue to use AmPm as expected.

There is no public functionality in icu4x to create a DateTimeFormat with a custom pattern. The function that handles formatting with a pattern, write_pattern(), lives in the format module, which is private. In order for tests to have access to the write_pattern() function, all testing must live within the format module itself, so this patch restructures the previous format.rs module into format/mod.rs and adds a format/tests directory where the relevant testing lives.

}
}
}
}
Copy link
Member Author

@nordzilla nordzilla Jan 12, 2021

Choose a reason for hiding this comment

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

This test moved to the newtests/mod.rs file.

Copy link
Member

Choose a reason for hiding this comment

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

Is this a conventional place to save tests? @zbraniecki @Manishearth

Copy link
Member

Choose a reason for hiding this comment

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

Why not /components/datetime/tests/dayperiod.rs ?

Copy link
Member

Choose a reason for hiding this comment

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

Given that you are writing these tests as parameterized tests with fixtures, I definitely think that they belong as an integration test, not hidden in a test directory in the library.

(sorry for the triple-comment)

Copy link
Member Author

Choose a reason for hiding this comment

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

I would have liked for it to be an integration test, but as I noted in the description of the PR, there is no public functionality to format dates with a custom pattern, so it has to live within the privateformat module. This seemed, to me, to be the best compromise, but I'm open to other suggestions.

Copy link
Member

@Manishearth Manishearth Jan 13, 2021

Choose a reason for hiding this comment

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

For unit tests it's fine to create a tests module. I personally don't feel a need to prefer integration tests over unit tests, though it is definitely easier to find integration tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

@sffc So I looked into this a bit more this morning, specifically into how I might use StructProvider.

Even if I used StructProvider to get a custom payload, it seems that both write_pattern() and DateTimeFormat must take a DatesV1 struct specifically as their data.

Unless there is another way to format the datetime using the custom provider, I'm not seeing any viable options to format with a custom pattern publicly. That being said, I'm still trying to build an overall familiarity with the code base, so I certainly could have missed a key insight here.

Copy link
Member

Choose a reason for hiding this comment

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

Right. Create a DatesV1, either using Default::default() or by loading a stock locale from test data, then modify a field like patterns.time.full with your desired pattern.

I like the idea of data-driven tests being able to overwrite locale data for that testing. @zbraniecki thoughts?

Copy link
Member Author

@nordzilla nordzilla Jan 14, 2021

Choose a reason for hiding this comment

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

Okay, so I've now moved this test back to the file where it existed previously, and I moved the new tests to the components/datetime/tests folder to be integration tests. I really like this a lot more.

But it feels somewhat hacky to me to grab the CLDR data for the locale, and then just overwrite the long patterns for each relevant case in my test data. Is this really intended functionality? I had previously overlooked that every member in DatesV1 is public.

I had a conversation with @zbraniecki about how custom patterns are often misused in practice. My understanding is that this is why we don't really have a public functionality to directly do something like DateTimeFormat::try_format(&date_time, &pattern), and that we prefer to have people use only the options bags.

It just seems strange to me to have this preference to funnel people through pre-existing options, but still allow "backdoors" for custom patterns by letting people directly mutate the DatesV1 data directly. I'm almost of the opinion that, if the functionality exists anyway, why not provide something likeDateTimeFormat::try_format(&date_time, &pattern)?

I suppose I just want to gain clarity around the design decisions behind not having a custom format with pattern function, and for making all of the DatesV1 members public, since I wasn't involved in the project at the time when these were conceived or implemented.

In the interest of not having duplicated conversations, if there are links to relevant pre-existing conversations that would be helpful to me.

Copy link
Member

Choose a reason for hiding this comment

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

In the interest of not having duplicated conversations, if there are links to relevant pre-existing conversations that would be helpful to me.

We've had conversations about this before, but I think they weren't written down.

The fields in all the data structs are public by design. The data struct is designed to be the place where you can "overlay" stock data with custom overrides.

That being said, I agree that in this particular case, it would probably be cleaner to have a low level pattern API. We can revisit this test when we have such API.

@jira-pull-request-webhook
Copy link

Notice: the branch changed across the force-push!

  • components/datetime/src/format/tests/mod.rs is different

View Diff Across Force-Push

~ Your Friendly Jira-GitHub PR Checker Bot

@jira-pull-request-webhook
Copy link

Notice: the branch changed across the force-push!

  • components/datetime/src/format/tests/patterns/tests/dayperiods.json is different

View Diff Across Force-Push

~ Your Friendly Jira-GitHub PR Checker Bot

@codecov-io
Copy link

codecov-io commented Jan 12, 2021

Codecov Report

Merging #444 (fc237db) into master (d2f47f7) will increase coverage by 0.20%.
The diff coverage is 90.12%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #444      +/-   ##
==========================================
+ Coverage   75.28%   75.49%   +0.20%     
==========================================
  Files         113      113              
  Lines        6058     6116      +58     
==========================================
+ Hits         4561     4617      +56     
- Misses       1497     1499       +2     
Impacted Files Coverage Δ
components/datetime/src/provider/mod.rs 0.00% <0.00%> (ø)
components/provider_cldr/src/transform/dates.rs 69.02% <ø> (ø)
components/datetime/src/pattern/mod.rs 86.79% <88.00%> (+2.41%) ⬆️
components/datetime/src/pattern/parser.rs 96.47% <97.61%> (+0.05%) ⬆️
components/datetime/src/fields/symbols.rs 77.31% <100.00%> (+0.23%) ⬆️
components/datetime/src/format.rs 100.00% <100.00%> (ø)
components/datetime/src/provider/helpers.rs 68.75% <100.00%> (+1.72%) ⬆️
components/datetime/src/date.rs 59.34% <0.00%> (+2.19%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d2f47f7...fc237db. Read the comment docs.

@coveralls
Copy link

coveralls commented Jan 12, 2021

Pull Request Test Coverage Report for Build d48ff169ff786e1a24ae8e08ab4a6c8c62092f22-PR-444

  • 73 of 81 (90.12%) changed or added relevant lines in 6 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.2%) to 74.783%

Changes Missing Coverage Covered Lines Changed/Added Lines %
components/datetime/src/pattern/parser.rs 41 42 97.62%
components/datetime/src/pattern/mod.rs 22 25 88.0%
components/datetime/src/provider/mod.rs 0 4 0.0%
Totals Coverage Status
Change from base Build d2f47f76a82b64a1204bec2a81130fcb70fcea06: 0.2%
Covered Lines: 4733
Relevant Lines: 6329

💛 - Coveralls

@nordzilla nordzilla changed the title Day period noon midnight Add support for width-aware noon and midnight options for DayPeriod Jan 12, 2021
zbraniecki
zbraniecki previously approved these changes Jan 12, 2021
Copy link
Member

@zbraniecki zbraniecki left a comment

Choose a reason for hiding this comment

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

lgtm!

Copy link
Member

@sffc sffc left a comment

Choose a reason for hiding this comment

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

(setting review bit)

@jira-pull-request-webhook
Copy link

Notice: the branch changed across the force-push!

  • components/datetime/src/lib.rs is no longer changed in the branch
  • components/datetime/src/options/components.rs is no longer changed in the branch
  • components/datetime/src/options/style.rs is no longer changed in the branch

View Diff Across Force-Push

~ Your Friendly Jira-GitHub PR Checker Bot

@jira-pull-request-webhook
Copy link

Hooray! The files in the branch are the same across the force-push. 😃

~ Your Friendly Jira-GitHub PR Checker Bot

zbraniecki
zbraniecki previously approved these changes Jan 15, 2021
Copy link
Member

@zbraniecki zbraniecki left a comment

Choose a reason for hiding this comment

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

lgtm!

@nordzilla
Copy link
Member Author

I just want to clarify everything that we have discussed formally in a comment.

So the goal is to add two new sets of functionality to this PR:

  1. Modify the DataProvider to generate special patterns, as @sffc noted (pseudo-syntax)
if (m == 0) {
  if (h == 0) {
    "'midnight'"
  } else if (h == 12) {
    "'noon'"
  } else {
    "h a"
  }
} else {
  "h:mm a"
}
  1. As DateTimeFormat is constructed, when tokenizing the pattern, determine which dayperiod fields are needed based on how specific the display is.

Is this correct?

I think that number 2) makes sense, I am still a bit unclear still about number 1) because the DataProvider isn't necessarily aware of any specific datetime. What does m == 0 mean in the context of only the DataProvider?

It's the DateTimeFormat that puts this all together. I would think all of this logic should live in DateTimeFormat.

@sffc
Copy link
Member

sffc commented Jan 21, 2021

The pseudo-syntax I posted above is something we should do later in #380. For now we just need to do what you posted in #444 (comment), which is a bit simpler.

@nordzilla
Copy link
Member Author

nordzilla commented Jan 23, 2021

As a first pass to achieve the functionality described in #444 (comment), I have naively added the capability for a Pattern to retrieve the maximum time granularity contained inside the pattern.

Currently, the maximum granularity is computed every time the pattern is written. This is obviously going to be slower than computing the maximum granularity once on creation and caching the value, but it was one of the simplest approaches that touched the least amount of existing code in order to get new tests written for the functionality.

I am curious to see if and how much this regresses the benchmarks. I suspect that I will likely add one more commit to compute and cache this value.

components/datetime/src/format/mod.rs Outdated Show resolved Hide resolved
components/datetime/src/format/mod.rs Outdated Show resolved Hide resolved
components/datetime/src/pattern/mod.rs Outdated Show resolved Hide resolved
components/datetime/src/pattern/mod.rs Show resolved Hide resolved
components/datetime/src/provider.rs Outdated Show resolved Hide resolved
@jira-pull-request-webhook
Copy link

Notice: the branch changed across the force-push!

  • components/datetime/src/format/mod.rs is different

View Diff Across Force-Push

~ Your Friendly Jira-GitHub PR Checker Bot

- Restructures `format` module to use directory structure instead of single file.
- Adds a `format/tests` directory.
- Moves existing local tests from `format.rs` to `format/tests/mod.rs`
- Adds JSON test data for `DayPeriod` patterns.
- Adds JSON-serializable structs for testing formatting patterns.
- Adds test cases for `DayPeriod` patterns.
- Adds parsing test cases for the `b` `DayPeriod` pattern.
- 24:00 will not be a valid time once unicode-org#355 is fixed.
- Removes logic than handles 24:00 for now.
- Converts `DayPeriod` pattern tests to be integration tests.
  - Tests no longer direclty use the private `write_pattern()`.
  - Tests now mutate the `DatesV1` struct to the desired pattern,
    using `DateTimeFormat` to format the custom patterns.
@jira-pull-request-webhook
Copy link

Notice: the branch changed across the force-push!

  • components/datetime/src/format.rs is different
  • components/datetime/src/provider.rs is no longer changed in the branch
  • components/datetime/src/provider/helpers.rs is now changed in the branch
  • components/datetime/src/provider/mod.rs is now changed in the branch
  • components/datetime/tests/datetime.rs is different
  • components/provider_cldr/src/transform/dates.rs is different
  • components/provider/src/structs/dates.rs is no longer changed in the branch

View Diff Across Force-Push

~ Your Friendly Jira-GitHub PR Checker Bot

@nordzilla
Copy link
Member Author

All right @sffc I rebased on top of master after you landed #451.

This should be ready for one final look (no changes since last time), and then able to merge with no conflicts.

@nordzilla
Copy link
Member Author

Uh-oh, spoke too soon. Looks like the CI is having an issue with the optional serde serialization, even though it works for me locally.

It probably has something to do with you making serde optional. I will look into this.

- Rewrites the `symbols!()` macro as a token tree muncher.
  - For `Option` members, adds serde attributes to skip serializing if none.
  - Otherwise, includes them in the seralization.
- Regenerates the test data now that `noon` and `midnight` are skipped if not present.
  - Previously `noon` and `midnight` would show up as `null`.
- Moves a few expressions in the dayperiod patterns test to outer loops.
- Adds capability for a pattern to compute its most granular time.
  - e.g. `h:mm:ss` is `Seconds`.
  - e.g. `h` is `Hours`.
  - e.g. `E, dd/MM/y` is `None`.
- Patterns containing `b` the `NoonMidnight` pattern item will now
  display noon or midnight only if the displayed time falls on the hour.
  - This means that `12:00:00` is always noon-compatible.
  - However, `12:05:15` is noon-compatible only if the display pattern
    does not contain the minutes or seconds.
- Time granularity functionality is no longer associated with Pattern or
  PatternItem. It is now local to the format module alone as standalone
  functions.
- Format no longer needs to be a directory.
- Makes TimeGranularity private instead of public.
- Only calculates the time granularity if the `DayPeriod` is `NoonMidnight`.
- Converts `Pattern` from a tuple struct to a traditional struct.
- Adds a new data member `time_granularity` to `Pattern`.
  - `time_granularity` is a lazily initialized, interrior-mutable cached value.
- Makes `Pattern`'s data members private.
  - The cached `time_granularity` is dependent on the `Pattern`'s `items`.
    It is no longer safe to allow `items` to be publicly accessible,
    because mutating `items` must invalidate the cached granularity.
- Adds new method `items()` to `Pattern` to return a slice of its items.
- Implement `From<Vec<PatternItem>` for `Pattern`
  - This is out of convenience in many places where tuple-struct syntax
    was used previously.
- Pattern::from_iter now uses Pattern::from::<Vec<_>>
- `Pattern`'s time granularity is no longer lazily evaluated.
- It is instead evaulated on construction.
@jira-pull-request-webhook
Copy link

Notice: the branch changed across the force-push!

  • components/datetime/src/provider/mod.rs is different

View Diff Across Force-Push

~ Your Friendly Jira-GitHub PR Checker Bot

@nordzilla
Copy link
Member Author

@sffc all fixed. The CI failure was due to me not handling optional serde in the macro rewrite. Should be good to go now.

@nordzilla nordzilla requested a review from sffc January 29, 2021 22:48
sffc
sffc previously approved these changes Jan 29, 2021
components/datetime/src/pattern/mod.rs Outdated Show resolved Hide resolved
- filter_map is more specialized, and arguably more readable.
Copy link
Member

@zbraniecki zbraniecki left a comment

Choose a reason for hiding this comment

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

lgtm!

@nordzilla nordzilla merged commit 1d14a5d into unicode-org:master Jan 30, 2021
@zbraniecki zbraniecki mentioned this pull request Apr 14, 2021
16 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[datetime] Add support for width-aware noon and midnight DayPeriods
7 participants