-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Fix DateFormatter TimeZone Setter #1704
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
Conversation
Because of the use of 'timeZone' instead of 'newValue' in the setter, setting the timeZone of a DateFormatter doesn't actually work. Instead, it winds up re-setting the old value, which happens to be whatever was originally set, usually what we get from copying the default down in CoreFoundation. Also adds a unit test to help prevent breaks like this again.
I found this by accident trying to debug/find a similar break in 4.1 (which is where we are stuck on arm32 at the moment due to 32-bit alignment problems in the compiler with 4.2). Still trying to find that one, sadly. |
@swift-ci test |
What's the general rules for bundling fixes? I found the bug in 4.1/4.2 I was looking for. It's related enough I could just add the commit to this PR, but it's also somewhat unrelated (TimeZone.current has effectively been "GMT" for at least a year, no matter what it actually reports). |
This one is nasty, since it creates problems for DateFormatter, while appearing to behave correctly. (Mostly) By copying 1 too many bytes, we wind up with the null terminator in the CFString. This breaks DateFormatter in an annoying way that the system TimeZone is always considered to be GMT by the formatter. You can ask for the zone’s abbreviation, and it will be correct. The name will appear to be correct, except the length is 1 longer than it should be. All sorts of fun. But the moment you ask DateFormatter to give you a string (or parse one), it assumes the TimeZone is GMT and ruins your day. Included is a test case which can detect this. The downside is that it doesn’t detect the problem if GMT is already the current TimeZone. It is possible a second, more targeted test should also be added here? This also breaks getting the description on the NSTimeZone/CFTimeZone, but not the TimeZone because of the wrapping at play, because of the terminator that gets inserted into the string description.
I've added caa8d0a to the PR, since that's also required to unblock my scenario of using the system timezone to parse/format date strings (think logging, and interpreting time-of-day schedules without requiring a TZ in the string). |
f.timeZone = losAngeles | ||
XCTAssertEqual(f.string(from: now), f.timeZone.abbreviation()) | ||
|
||
guard gmt != TimeZone.current else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% happy with the way this test works, which pretty much is trying to make sure that we can use "TimeZone.current" for zones other than GMT and get what we expect when we then run it through DateFormatter.
If this is too much, I can try to write a more targeted test, although I'm not entirely sure that would be any less fragile.
LGTM! Thanks @Kaiede! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Also cc @itaiferber
Once this is in master, a PR with a cherry-pick to 4.2 will get it into the next version of that. Generally I approve those if they are manageable bug fixes that unblock clients or address serious issues. Other stuff can target the next major release (5.0). |
Cleans up the DateFormatter test, improves the information about how it doesn’t catch issues when the system time zone is already GMT, and adds a more focused test for the SystemTimeZone name issue that can catch it when GMT is the system time zone.
@swift-ci Please test |
@swift-ci please test and merge |
1 similar comment
@swift-ci please test and merge |
@Kaiede Looks like something regressed, possibly? |
Or is newly uncovered. |
@millenomi My bet is on newly uncovered. This was not something I was seeing fail with my changes locally, but, my sync point is now 16 days old, and the last PR push was 8 days old. A lot of stuff recently got merged here, and it's possible that for some reason I just wasn't paying close enough attention. In general, the calendar test failing is interesting because it is relying on the current time zone to function properly, in addition to the current Calendar. The bug that I'm fixing is that TimeZone.current is always acting like GMT when using ICU/DateFormatter. So it kinda makes sense that my fix could make this test appear to fail, depending on the time zone the machine running the tests happens to be using. Instead, the failing test should be using Calendar current with a fixed UTC timezone to avoid this sort of problem. It's testing too many variables at once in practice. EDIT: I'll take a look at this tonight, and see if I can't add a fix for the test to this PR. It should get fixed with my PR since my fix revealed the problem. |
Thanks! |
Yeah, my suspicions were right. If the test runs in GMT, it passes, if it runs in PDT, it fails with the exact failures the CI also hit. I also realize I didn't see it before because there's another bug when running these tests on macOS using the xcworkspace where CoreFoundation can't find the current time zone and also defaults to GMT. I had hacked around it to run my own tests, but I didn't run the whole suite that way when I discovered that problem. Is the xcworkspace not really used anymore? It seems to be getting progressively broken. I think I ran into a few new breaks attempting to pull the latest set of changes from master, and couldn't even get SwiftFoundation to link on Mojave anymore. |
The test assumes that it is running as GMT/UTC. Because of the TimeZone.current fixes, the test is no longer guaranteed to be using GMT/UTC, and will use the system TimeZone and breaks when in PDT for example. By making the UTC assumption explicit, we remove one more variable on this test.
I ran tests with the Mojave merge on Mac, so I'm surprised. @shahmishal was looking into possibly adding CI to run those tests. |
@millenomi I just open the xcworkspace from the Terminal and attempt to run/build from there. So I'm wondering what I'm doing differently. Either way, the fix for the test has been added to the PR. |
@Kaiede Swift Foundation is revlocked to the Swift toolchain it ships with — so you must either build your own via I have just built and ran tests for #1600 successfully, though I am seeing the test failures you describe with the off-by-oneness. But I can build and run correctly. |
(Switch toolchains from Xcode > Preferences > Components > Toolchains.) |
@swift-ci please test |
Okay, that's what I'm doing wrong. Thanks. |
We really ought to have that written down somewhere easy to find. |
This broke CI. TestDateFormatter.swift's test_expectedTimeZone is failing. Reverting in #1721.
There is a suspicious build warning in that test:
|
Looks like the build czar's force-merge privileges don't apply to swift-corelibs-foundation, so you have a bit of time to fix it before then. |
Per side discussion: let's revert, then we will put the PR back up again. |
Sorry about that, looks like the pre-checkin CI just happened to be on a PT time zone, which doesn't expose this issue with the test. Same with my local test environment.
Ironically, it looks suspicious, but is entirely unrelated code that was part of an "inconclusive test" message that was removed based on feedback, but not the let statement that helped feed the guard. The issue with the break is that if TimeZone.current.abbreviation gives you "GMT", but then ask a DateFormatter using that TimeZone what the abbreviated zone is, it gives you the string "UTC". Neat. :( |
Because of the use of 'timeZone' instead of 'newValue' in the setter, setting the timeZone of a DateFormatter doesn't actually work. Instead, it winds up re-setting the old value, which happens to be whatever was originally set, usually what we get from copying the default down in CoreFoundation.
A good fix here is to avoid any ambiguity with the property name in the setter and getter.
Also adds a unit test to help prevent breaks like this again.