Home > Back-end >  Swift: calendar.date(byAdding...) doesn't work properly
Swift: calendar.date(byAdding...) doesn't work properly

Time:01-01

I would like to get the 15th day of the previous month from the current date, so I tried this:

extension Date {
    var archiveDate: Date? {
        let calendar = Calendar.current
        guard let monthFixed = calendar.date(byAdding: .month, value: -1, to: self) else {
            return nil
        }
        return calendar.date(bySetting: .day, value: 15, of: monthFixed)
    }
}

But when priting the date (print(Date().archiveDate!)), I get this:

2021-12-14 23:00:00 0000

Why am I getting december 14th instead of november 14th? Thank you for your help

CodePudding user response:

From the Apple's documentation for date(bySetting:value:of:):

The algorithm will try to produce a result which is in the next-larger component to the one given. So for the “set to Thursday” example, find the Thursday in the Week in which the given date resides (which could be a forwards or backwards move, and not necessarily the nearest Thursday).

The problem:

The given function tries to match the date you have passed (15 in your case), and it can either go backward or forward for us; in this case, it goes forward.

The algorithm will search for the next 15th and give that date back. So if your current date is 2nd on May, it will go to 15th of May. But if it is the 16th of May, it will go to the 15th of June.

The solution:

  1. If the date given is less than the 15th of that Month, you can go back by 1 month and set the day to 15.
  2. If the date is after the 15th of that Month, you have to go back by 2 months and set the day to 15.

Code:

extension Date {
    var archiveDate: Date? {
        let calendar = Calendar.current
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "dd"
        let currentDate = Int(dateFormatter.string(from: self)) ?? 0
        
        var valueToReduce = -1
        if currentDate > 15 {
            valueToReduce = -2
        }

        guard let monthFixed = calendar.date(byAdding: .month, value: valueToReduce, to: self) else {
            return nil
        }
        return calendar.date(bySetting: .day, value: 15, of: monthFixed)
    }
}

A shorter version for the same code would be:

extension Date {
    var archiveDate: Date? {
        let calendar = Calendar.current
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "dd"
        
        guard let monthFixed = calendar.date(byAdding: .month, value: (Int(dateFormatter.string(from: self)) ?? 0) > 15 ? -2 : -1, to: self) else {
            return nil
        }
        return calendar.date(bySetting: .day, value: 15, of: monthFixed)
    }
}

CodePudding user response:

You can try and replace your extension with the below one:

extension Date {
        var archiveDate: Date? {
            let calendar = Calendar.current
            guard let monthFixed = calendar.date(byAdding: .month, value: -1, to: self) else {
                return nil
            }
            let startOfMonth = monthFixed.startOfMonth
            let newDate = calendar.date(bySetting: .day, value: 15, of: startOfMonth) ?? Date()
            return newDate
        }
        
        var startOfMonth: Date {
            let calendar = Calendar.current
            let components = calendar.dateComponents([.year, .month], from: self)
            return  calendar.date(from: components)!
        }
    }
  • Related