swiftnsdatensdateformatternsdatecomponentsnsdatecomponentsformatter

DateComponentsFormatter - same dates, different results


Why is dateRemainingText2 giving different result from dateRemainingText?
Obviously dateRemainingText2 is wrong.

Here's my code:

import Foundation

let startDate = Calendar.current.date(from: DateComponents(year: 2022, month: 5, day: 1)) ?? .now
let endDate = Calendar.current.date(from: DateComponents(year: 2020, month: 6, day: 2)) ?? .now

let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [.year, .month, .day]
dateComponentsFormatter.unitsStyle = .full
var dateRemainingText = dateComponentsFormatter.string(from: startDate, to: endDate)! // -1 year, 10 months, 29 days
let dateComponents = Calendar.current.dateComponents([.year, .month, .day], from: startDate, to: endDate)
var dateRemainingText2 = dateComponentsFormatter.string(from: dateComponents) // -1 year, 11 months, 1 day

Solution

  • DateComponentsFormatter.string(from:to:) and DateComponentsFormatter.string(from:) are different methods and so they can do different things.

    From some experimentation, we can see that string(from:) outputs a string that describes the sum of all the date components in the DateComponents passed in.

    This just happens to be the same as the output for string(from:to:) for most of the cases where the components are all positive.

    Examples:

    // 1 month + 7 days
    var dateComponents = DateComponents()
    dateComponents.month = 1
    dateComponents.day = 7
    // 1 month, 7 days
    print(dateComponentsFormatter.string(from: dateComponents)!)
    
    // 7 days - 1 month
    dateComponents.month = -1
    dateComponents.day = 7
    // -24 days
    print(dateComponentsFormatter.string(from: dateComponents)!)
    
    // 1 month - 7 days
    dateComponents.month = 1
    dateComponents.day = -7
    // 24 days
    print(dateComponentsFormatter.string(from: dateComponents)!)
    
    // 1 month + 30 days
    // note that adding 1 month, it's February, and there are only 28 days
    dateComponents.month = 1
    dateComponents.day = 30
    // 2 months, 2 days
    print(dateComponentsFormatter.string(from: dateComponents)!)
    
    // -10 months - 30 days
    // note that after subtracting 10 months, it is March of the previous year, 
    // which also happens to be a leap year, so February has 29 days
    // Subtracting 30 days from that will bring us to January, with one day left
    // which is, where the extra month and day came from
    dateComponents.month = -10
    dateComponents.day = -30
    // -11 months, 1 day
    print(dateComponentsFormatter.string(from: dateComponents)!)
    
    // -1 year - 10 months - 30 days
    // similar to above, except not a leap year
    // so we have 2 days left after subtracting from a 28-day February.
    dateComponents.year = -1
    dateComponents.month = -10
    dateComponents.day = -30
    // -1 year, 11 months, 2 days
    print(dateComponentsFormatter.string(from: dateComponents)!)
    

    The DateComponents used in that last case is what Calendar.dateComponents(_:from:to:) in your code.

    On the other hand, string(from:to) is the designated method to format the period between two dates.