swiftlang / swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence
swift.org
Apache License 2.0
5.3k stars 1.14k forks source link

[SR-9668] Adding one month returns a different date on Linux vs Mac. #3558

Open swift-ci opened 5 years ago

swift-ci commented 5 years ago
Previous ID SR-9668
Radar None
Original Reporter svanimpe (JIRA User)
Type Bug

Attachment: Download

Environment Swift 4.2.1
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Foundation | |Labels | Bug | |Assignee | None | |Priority | Medium | md5: b5cd101c9fb210b5c8555f3e1539b89b

Issue Description:

My unit tests unfortunately detected a difference in behavior on Mac vs Linux:

let calendar = Calendar(identifier: .gregorian)
var dateComponents = DateComponents()
dateComponents.calendar = calendar
dateComponents.day = 31
dateComponents.month = 1
dateComponents.year = 2019
let start = calendar.date(from: dateComponents)!
let end = calendar.date(byAdding: .month, value: 1, to: start)!

On Mac, adding one month to Jan. 31 returns Feb. 28 (which is what I hoped it would do). On Linux however, this returns March 1.

spevans commented 5 years ago

I cant replicate this any of Ubuntu 14.04, 16.04 or 18.04 using 4.2.1. All three versions return Feb 28

swift-ci commented 5 years ago

Comment by Steven Van Impe (JIRA)

That code example was based on my project's code, but I can't reproduce the issue with that either. I want as far as copy-pasting all my code into a new project, with only minimal adjustments and still couldn't reproduce the issue. I then copy pasted my full files into a new project, and finally, I can reproduce the issue. See the attached project, and run `swift test` to reproduce it.

This also pinpointed the cause of the issue: the only difference between this project and the previous ones I made in an attempt to reproduce the issue, is that in this project, the source and test files both declare `let calendar = Calendar(identifier: .gregorian)`. If I share the same calendar object between source and test, the issue goes away.

Is this an error on my part? Should I use a single Calendar object for my entire application? Or is there an issue with Calendar on Linux?

spevans commented 5 years ago

I think this might be a timezone issue. On both macOS and Linux if I run:

TZ=CET swift test
I get all tests passed, but with

TZ=UTC swift test
I get the same 2 test failures.

Are your macOS and Linux Docker images set to different timezones?

swift-ci commented 5 years ago

Comment by Steven Van Impe (JIRA)

I think you're right. My Mac is set to CET. Docker seems to use UTC.

Since this is a server-side Swift app, I explicitly set the time zone for all dates to CET, as all my users are in that timezone. I had hoped that would get rid of time zone issues.

What seems to happen here is that, with a system time zone set to CET, a test with Jan. 31 00:00 CET returns Feb 28. 00:00 CET (the expected result). However, with a system time zone of UTC, the calculation first subtracts one hour to get Jan. 30 23:00 UTC. Then, it adds one month and gets Feb. 28 23:00 UTC. Finally, it adds one hour again, which results in Mar. 1.

Is this the normal behavior? If so, how do you recommend I solve this issue, so my code is independent of where it is deployed?

belkadan commented 5 years ago

@millenomi, who's the calendar expert for corelibs Foundation?

millenomi commented 5 years ago

My bad, I misread. Disregard the deleted comment.

Did you try this with master lately? We have overhauled Calendar and fixed ICU to a single release so that you should get consistent behavior now.

swift-ci commented 5 years ago

Comment by Steven Van Impe (JIRA)

@millenomi As per your deleted comment, explicitly setting the time zone of the `calendar` object resolves the issue as well. So I now have three possible solutions:

Should I go with option #3?

millenomi commented 5 years ago

I think it's a solid workaround. You can get the timezone via TimeZone.current if needed.

Have you checked with master? I'm curious to see if this is an issue that goes away with the rework I landed.

swift-ci commented 5 years ago

Comment by Steven Van Impe (JIRA)

Unfortunately, no. I only have the version(s) installed I run in production.

spevans commented 5 years ago

I just checked against swift-DEVELOPMENT-SNAPSHOT-2019-01-15-a-ubuntu18.04 and it has the same behaviour as 4.2.1 on Linux and macOS. I take it this is as intended. So I don't think this bug is valid since Linux and macOS both work the same way.

swift-ci commented 5 years ago

Comment by Steven Van Impe (JIRA)

@spevans Can you explain why option #1 works as well? As far as I can tell, this has nothing to do with time zones, and yet, it also resolves the issue.