The documentation for Foundation claims that
DateInterval represents closed ranges. Thus, I would expect an interval's end returned from
calendar.dateInterval(of:for:) to be the last possible instance of the unit (say, 31th of December 23:59 when passed a
.year). However, the actual behaviour is:
var calendar = Calendar(identifier: .gregorian) calendar.locale = .current let components = DateComponents(year: 2020, month: 1, day: 1) let startDate = calendar.date(from: components)! let interval = calendar.dateInterval(of: .year, for: startDate)! let endComponents = calendar.dateComponents([.year], from: interval.end) print(endComponents.year!) // prints "2021" // or: let formatter = DateFormatter() formatter.calendar = calendar formatter.dateStyle = .full // prints "Friday, January 1, 2021 at 12:00:00 AM Central European Standard Time" print(formatter.string(from: interval.end))
— that is, if
DateInterval is a closed interval, then January 1, 2021 at 12:00:00 AM belongs to the year 2020, which is very confusing to me. At least, all APIs extracting
DateComponents will return very unexpected values, for instance, querying a date interval for any month and then looking at components' day will always deliver 1 and 1 on both ends.
The way it works, as I deduced from the source code, is that it simply adds the full duration of (in this case, year) and then deliberately does not jump back to the previous day (or whatever instance would make sense), only sets the hour-minute-second to 00:00:00. I would expect the interval's end to be December 31, 2020 at 23:59:59 PM in this case, i.e., subtracting 1 second from the instance the method actually returns.
So, I wonder, if it's a bug in the implementation or is it that
DateInterval was misdocumented, because it actually was meant to represent open intervals? Should this be filed as a bug — or is this all completely intentional?