11 Commits

Author SHA1 Message Date
Tina Liu
47c62e35c8
[Gregorian Calendar] Handle adding date components across DST transition like Calendar_ICU (#352)
Currently we implement adding units larger than `.day` as follows:
- Convert the date to date components
- Update the value of the added component
- Convert the date components back to date

The time of the day information is reduced to a `DateComponents` with, e.g. `hour == 1`, `minute == 30`, at step 1. Then when we convert the `DateComponents` back to `Date` at step 3, we always use the *first* occurrence of the time if it falls into the DST repeated time frame.

This is different from `Calendar_ICU`'s implementation, which uses the *latter* and rewind back the date by the DST transition interval (e.g. 1 hour for Pacific Time).

These yeild the same result except for when the input time and the output time are *both* during DST transition time frame. Update the implementation to match Calendar_ICU's behavior.
2023-12-22 10:11:34 +08:00
Tina Liu
06a025bd0d
[Gregorian Calendar] WIP: Implement ordinality(of:in:for:) (#336)
* [Gregorian Calendar] Drive-by fix for quarter handling in `dateComponents(from:)`

The current handling for quarter is wrong for leap years. Fix this by reversing the leap year check.

* [Gregorian Calendar] WIP: Implement `ordinality(of:in:for:)`

The implementation follows that in Calendar_ICU. But instead of mutating the stored ICU calendar, we pass around a `Date` and its `DateComponents` to for keeping track of the calculation state.

Unfinished task: There are a few places where we try to find the next date matching a given date component repeatedly in while loops. We need to add failsafe to prevent infinite loop.

* Address compiler warning

* Fix `start(of:at:)`

Similar to #335, it's necessary to only specify relevant components when converting `Date` to `DateComponents` with `dateComponents(:)`. Address the same issue here.
2023-12-12 09:57:00 +08:00
Tina Liu
67a09e0908
[Gregorian Calendar] Bugs in adding functions (#335)
* Add more tests; some of them are currently failing

* [Gregorian Calendar] Bugs in adding functions

Week handling is currently wrong, so adding .week and .yearForWeekOfYear to dates across year boundaries would be wrong.

Adding is implemented as such:
- calculate all date component values for the given date
- add the given value to the given component
- calculate date from the updated date components

When calculating date from the given date components, the heuristics of handling conflicting components, implemented as `ResolvedDateComponents`, may favor the wrong component. We tried working around this by passing in the component to take priority to `ResolvedDateComponents(preferComponent:dateComponents)` in the current implementation, but that logic was still flawed.

Simplify this by only including relevant components in the calculation rather than deferring to `ResolvedDateComponents` to resolve the components.
2023-12-06 09:06:15 -08:00
Tina Liu
4387ce0f10
[WIP Gregorian Calendar] Implement date(byAdding components: DateComponents, to date: Date, wrappingComponents: Bool) (#317)
* WIP: Implement `date(byAdding components: DateComponents, to date: Date, wrappingComponents: Bool)`

This largely follows ICU's calendar::add and calendar::roll.

* It takes around a minute to run these compatibility tests as of this moment. I expect it to take even longer as we add more tests to it. Disable them on CI for now. The appropriate tests should assert their expected absolute values respectively.

* Add a convenience initializer to Date.Components and use it in tests
2023-12-02 07:30:45 -08:00
Tony Parker
61f69de6b3
Workaround compatibility issue with nanoseconds fix (#319) 2023-12-01 11:35:00 -08:00
Tina Liu
a7020a3d07
[GregorianCalendar] Implement TimeZone support for date(from components: DateComponents) (#311)
* [GregorianCalendar] Implement TimeZone support for `date(from components: DateComponents)`

To support DST-observing time zone, add a helper function for TimeZone to return the raw offset and DST offset individually so we can fine tune the behavior for the time during the skipped time frame and the repeated time frame.

* remove an accidental import

* Add non-compatibility tests

* Review feedback: Remove mention of "GMT" in the date argument

* Review feedback: Change the returning type of DST offset from Int to TimeInterval to be consistent with the existing dstOffset API

* Implement the required function for _TimeZoneBridged

* Fix a missing import
2023-12-01 09:57:19 -08:00
Tina Liu
cd3d29426f
WIP Gregorian Calendar in FoundationEssentials (#303)
* WIP Gregorian Calendar in FoundationEssentials

Implement `dateComponents(from date:)`. The implementation largely follows that of ICU in calendar.cpp and gregocal.cpp. Julian day calculation algorithm is referenced [The Explanatory Supplement to the Astronomical Almanac](https://aa.usno.navy.mil/publications/exp_supp).

First, calculate the Julian day number from a given `Date`. Then, convert the Julian day number to Gregorian calendar date fields[^1]. The remaining part is the time within the day.

There are a few things to look out for
- Julian day starts at noon, while `Date` uses midnight as the reference time, so we need to adjust it acccordingly at every conversion. Note that ICU uses Modified Julian Day number throughout their codebase, which starts at midnight, so
- Dates close to Gregorian calendar adoption, which is also referred as "cutover" or "transition" date interchangeably, needs to be handled with care. Date before the adoption date were represented in Julian Calendar and Gregorian Calendar after the adoption date. The common Gregorian Calendar adoption date is Friday, 15 October 1582. It's also customizable in the API.
- The week number of a month and a year depends on `firstWeekday` and `minimumDaysInFirstWeek` values. The week starts at `firstWeekday`. Week one must contain `minimumDaysInFirstWeek` number of days.

[^1]: A nice readable version of the Julain - Gregorian converting algorithm: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation.

* Disable a test that's not available until we implement the function in `GregorianCalendar`.

* Add a precondition for first weekday setter instead of silently dropping the value

* Gregorian Calendar part 2: Implement `date(from components:)`.

The implementation largely follows that of ICU. The logic follows this pattern generally
- Calculate the Julian day number from a given year, month, day of month number
- Add time field values if they are set
- Adjust for timezone offset

The complexity lies in the following situations
(1) The passed-in date components lacks essential fields to determine the Julian day number
(2) The date components has week-related fields set, but no year, month, day of month fields set
(3) The date components has both week-related fields and month, day of month fields set
(4) The day is near Gregorian calendar transition date

For (1), simply assume the start of the year/month/day if the field is missing. The value would be either 0 or 1, depending on if it's 0 or 1 indexed.
For (2), calculate the julian day of the start of the month. Advance by multiples of the given week number.
For (3), we need to determine which fields take over precedence of others. ICU tracks the order of when the fields are modified; newly modified ones always take precedence over past ones. We have not been using this mechanism at all in existing Calendar_ICU's implementation. Instead we've been setting the fields arbitrarily. Therefore, here we simplify ICU's heuristics by  assuming the timestamps of modified fields are all equal.
For (4), recalculate the date using Julian calendar if the found date is before Gregorian transition date.
2023-11-02 16:33:46 -07:00
Charles Hu
7272a47c0b
Refactoring some import conditions (#298) 2023-10-25 15:11:37 -07:00
Tony Parker
8010dfe6b1
Date comparison handles nanoseconds incorrectly (#289)
rdar://116433981

Co-authored-by: David Smith <david_smith@apple.com>
2023-10-09 13:57:40 -07:00
Charles Hu
30bc5b99f2
Introduce Availability Macros for FoundationPreview (#279) 2023-10-04 14:44:37 -07:00
Tony Parker
d942713680
Sink TimeZone, Locale, Calendar to Essentials (#266)
* Sink Locale, Calendar to Essentials

* Add a Calendar test, make sure that _lock is populated early for NSSwiftCalendar
2023-09-25 16:09:16 -07:00