Skip to content

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

Merged
merged 4 commits into from
Oct 11, 2018
Merged

Fix DateFormatter TimeZone Setter #1704

merged 4 commits into from
Oct 11, 2018

Conversation

Kaiede
Copy link
Contributor

@Kaiede Kaiede commented Sep 24, 2018

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.

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.
@Kaiede
Copy link
Contributor Author

Kaiede commented Sep 24, 2018

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.

@spevans
Copy link
Contributor

spevans commented Sep 24, 2018

@swift-ci test

@Kaiede
Copy link
Contributor Author

Kaiede commented Sep 24, 2018

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.
@Kaiede
Copy link
Contributor Author

Kaiede commented Sep 24, 2018

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 {
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'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.

@maksimorlovich
Copy link
Contributor

LGTM! Thanks @Kaiede!

Copy link
Contributor

@parkera parkera left a 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

@parkera
Copy link
Contributor

parkera commented Sep 26, 2018

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.
@itaiferber
Copy link
Contributor

@swift-ci Please test

@millenomi
Copy link
Contributor

@swift-ci please test and merge

1 similar comment
@millenomi
Copy link
Contributor

@swift-ci please test and merge

@millenomi
Copy link
Contributor

11:06:16 Test Case 'TestNSDateComponents.test_dateDifferenceComponents' started at 2018-10-10 11:06:06.360
11:06:16 TestFoundation/TestCalendar.swift:338: error: TestNSDateComponents.test_dateDifferenceComponents : XCTAssertEqual failed: ("Optional(21)") is not equal to ("Optional(20)") - 
11:06:16 TestFoundation/TestCalendar.swift:373: error: TestNSDateComponents.test_dateDifferenceComponents : XCTAssertEqual failed: ("Optional(3)") is not equal to ("Optional(2)") - 
11:06:16 TestFoundation/TestCalendar.swift:387: error: TestNSDateComponents.test_dateDifferenceComponents : XCTAssertEqual failed: ("Optional(16)") is not equal to ("Optional(17)") - 
11:06:16 Test Case 'TestNSDateComponents.test_dateDifferenceComponents' failed (0.003 seconds)

@Kaiede Looks like something regressed, possibly?

@millenomi
Copy link
Contributor

Or is newly uncovered.

@Kaiede
Copy link
Contributor Author

Kaiede commented Oct 10, 2018

@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.

@millenomi
Copy link
Contributor

Thanks!

@Kaiede
Copy link
Contributor Author

Kaiede commented Oct 11, 2018

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.
@millenomi
Copy link
Contributor

I ran tests with the Mojave merge on Mac, so I'm surprised. @shahmishal was looking into possibly adding CI to run those tests.

@Kaiede
Copy link
Contributor Author

Kaiede commented Oct 11, 2018

@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.

@millenomi
Copy link
Contributor

@Kaiede Swift Foundation is revlocked to the Swift toolchain it ships with — so you must either build your own via $SOURCES/swift/utils/build-toolchain, or, much better, download the latest one from https://swift.org/download/ for your branch (in this case, the one under 'Trunk Development (master)').

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.

@millenomi
Copy link
Contributor

(Switch toolchains from Xcode > Preferences > Components > Toolchains.)

@millenomi
Copy link
Contributor

@swift-ci please test

2 similar comments
@millenomi
Copy link
Contributor

@swift-ci please test

@millenomi
Copy link
Contributor

@swift-ci please test

@Kaiede
Copy link
Contributor Author

Kaiede commented Oct 11, 2018

@Kaiede Swift Foundation is revlocked to the Swift toolchain it ships with — so you must either build your own via $SOURCES/swift/utils/build-toolchain, or, much better, download the latest one from https://swift.org/download/ for your branch (in this case, the one under 'Trunk Development (master)').

Okay, that's what I'm doing wrong. Thanks.

@millenomi
Copy link
Contributor

We really ought to have that written down somewhere easy to find.

@millenomi millenomi merged commit 7418081 into swiftlang:master Oct 11, 2018
@gparker42
Copy link

This broke CI. TestDateFormatter.swift's test_expectedTimeZone is failing. Reverting in #1721.

TestFoundation/TestDateFormatter.swift:401: error: TestDateFormatter.test_expectedTimeZone : XCTAssertEqual failed: ("Optional("GMT")") is not equal to ("Optional("UTC")")

There is a suspicious build warning in that test:

TestFoundation/TestDateFormatter.swift:381:13: warning: initialization of immutable value 'gmt' was never used; consider replacing with assignment to '_' or removing it
        let gmt = TimeZone(abbreviation: DEFAULT_TIMEZONE)

@gparker42
Copy link

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.

@millenomi
Copy link
Contributor

Per side discussion: let's revert, then we will put the PR back up again.

@Kaiede
Copy link
Contributor Author

Kaiede commented Oct 12, 2018

This broke CI. TestDateFormatter.swift's test_expectedTimeZone is failing. Reverting in #1721.

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.

There is a suspicious build warning in that test:

TestFoundation/TestDateFormatter.swift:381:13: warning: initialization of immutable value 'gmt' was never used; consider replacing with assignment to '_' or removing it
        let gmt = TimeZone(abbreviation: DEFAULT_TIMEZONE)

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. :(

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.

7 participants