115

I'm trying to get the first and last day of the month in swift.

So far I have the following:

let dateFormatter = NSDateFormatter()
let date = NSDate()
dateFormatter.dateFormat = "yyyy-MM-dd"
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: date)

let month = components.month
let year = components.year

let startOfMonth = ("\(year)-\(month)-01")

But I'm not sure how to get the last date. Is there a built in method I'm missing? Obviously it has to take into account leap years etc.

Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
user2363025
  • 6,365
  • 19
  • 48
  • 89

12 Answers12

165

Swift 3 and 4 drop-in extensions

This actually gets a lot easier with Swift 3+:

  1. You can do it without guard (you could if you wanted to, but because DateComponents is a non-optional type now, it's no longer necessary).
  2. Using iOS 8's startOfDayForDate (now startOfDay), you don't need to manually set the time to 12pm unless you're doing some really crazy calendar calculations across time zones.

It's worth mentioning that some of the other answers claim you can shortcut this by using Calendar.current.date(byAdding: .month, value: 0, to: Date())!, but where this fails, is that it doesn't actually zero out the day, or account for differences in timezones.

Here you go:

extension Date {
    func startOfMonth() -> Date {
        return Calendar.current.date(from: Calendar.current.dateComponents([.year, .month], from: Calendar.current.startOfDay(for: self)))!
    }
    
    func endOfMonth() -> Date {
        return Calendar.current.date(byAdding: DateComponents(month: 1, day: -1), to: self.startOfMonth())!
    }
}

print(Date().startOfMonth())     // "2018-02-01 08:00:00 +0000\n"
print(Date().endOfMonth())       // "2018-02-28 08:00:00 +0000\n"
Community
  • 1
  • 1
brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • Produces warnings in Swift 3.0 . `Non-optional expression of type 'Calendar' used in a check for optionals`. Any suggestions? – Allreadyhome Sep 20 '16 at 20:18
  • 2
    Updated for Swift 3 – brandonscript Sep 20 '16 at 21:36
  • Why are you getting `.hour` in your Swift 3 answer? – Rob Dec 19 '16 at 12:46
  • Why in `endOfMonth ` are you getting date components only to overwrite them? I might suggest simply `func endOfMonth() -> Date? { return Calendar.current.date(byAdding: DateComponents(month: 1, day: -1), to: self.startOfMonth()!) }` – Rob Dec 19 '16 at 12:52
  • 3
    As an aside, it gives me the willies to add `DateComponents(month: 1, day: -1)` because you're relying upon undocumented behavior whereby it does `month` before `day`. E.g. if today is May 15, the start of the month is May 1, and if it subtracted day before month, you'd get May 30th rather than May 31st. It seems more safe to do month +1 and day -1 in two separate operations. Or use `calendar.range(of:in:for:)` instead. – Rob Dec 19 '16 at 12:57
  • Great comments Rob, I'll look into it. Immediately, the reason .hour was there was from the Swift 2 answer where I didn't use startOfDayForDate. – brandonscript Dec 19 '16 at 15:25
  • OK Rob, one question I have looking into `calendar.range(of:in:for:)`, is that for a given month (say, Dec), the range is `1...<32`, with the `lowerBound=1` and `upperBound=32`. That effectively pushes the range from the 1st of _this_ month, to the 1st of _next_ month. We still need to do the one-day subtraction. Any thoughts on working around that? – brandonscript Dec 20 '16 at 20:52
  • 10
    The start of month does not work for me. It gives me the previous day at 22:00 (so the last day of the previous month). If I also use the day component then it works: `Calendar.current.date(from: Calendar.current.dateComponents([.year, .month, .day], from: Calendar.current.startOfDay(for: self)))!` – giorgos.nl May 02 '17 at 14:06
  • adding a date component does not return the first day. – shubh14896 Apr 14 '18 at 05:45
  • Yeah, all the issues are time zone issues. That’s why the “startOfDayForDate” is required (or a 12pm thing). OTOH, I think there are better APIs for this now. (See LuisChen) – brandonscript Jun 13 '18 at 05:49
  • 1
    The correct to me would be DateComponents(month: 1, second: -1) otherwise, using day: -1 you will get the beginning of the last day. You want the end of the last day. – erickva Apr 04 '19 at 06:03
  • Except timezonea, so really want about 12pm on that day. – brandonscript Apr 04 '19 at 13:31
  • I am having a strange behaviour with this approach, the end of month is returning 2020-12-31 not 2019-12-31. – Matheus Weber Dec 09 '19 at 13:47
  • Sorry, the problem is when I convert to string with dateFormatter.string, – Matheus Weber Dec 09 '19 at 13:53
  • I know that not all timezone starts their day in 00:00. But, why `Calendar.current.startOfDay` is required? It seems like `calendar.components([.Year, .Month], fromDate: date)` is sufficient to "reset" the time to start of day. – Cheok Yan Cheng Dec 31 '21 at 02:27
  • Depends whether `date` already has a time set; if it does, you’ll get the current time, and many timezone problems will happen. – brandonscript Dec 31 '21 at 02:30
  • @brandonscript Sorry. I still don't get it. It seems that, `Calendar.current.date(from: Calendar.current.dateComponents([.year, .month], from: date))!` (without using startOfDay) will always able to return a Date, with local time 00:00 . Does that mean calling startOfDay in the middle of process is redundant? – Cheok Yan Cheng Jan 02 '22 at 14:26
  • 1
    No, because we’re calling start of day on self (today’s date). I ran several tests without it and it failed across timezones. – brandonscript Jan 02 '22 at 15:56
  • Do you mind to share your test case? As I still not yet find such test case, which will fail when not using startOfDay during the computation. – Cheok Yan Cheng May 20 '22 at 08:39
138

You get the first day of the month simply with

let components = calendar.components([.Year, .Month], fromDate: date)
let startOfMonth = calendar.dateFromComponents(components)!
print(dateFormatter.stringFromDate(startOfMonth)) // 2015-11-01

To get the last day of the month, add one month and subtract one day:

let comps2 = NSDateComponents()
comps2.month = 1
comps2.day = -1
let endOfMonth = calendar.dateByAddingComponents(comps2, toDate: startOfMonth, options: [])!
print(dateFormatter.stringFromDate(endOfMonth)) // 2015-11-30

Alternatively, use the rangeOfUnit method which gives you the start and the length of the month:

var startOfMonth : NSDate?
var lengthOfMonth : NSTimeInterval = 0
calendar.rangeOfUnit(.Month, startDate: &startOfMonth, interval: &lengthOfMonth, forDate: date)

For a date on the last day of month, add the length of the month minus one second:

let endOfMonth = startOfMonth!.dateByAddingTimeInterval(lengthOfMonth - 1)

Updated for Swift5:

extension Date {
    var startOfDay: Date {
        return Calendar.current.startOfDay(for: self)
    }

    var startOfMonth: Date {

        let calendar = Calendar(identifier: .gregorian)
        let components = calendar.dateComponents([.year, .month], from: self)

        return  calendar.date(from: components)!
    }

    var endOfDay: Date {
        var components = DateComponents()
        components.day = 1
        components.second = -1
        return Calendar.current.date(byAdding: components, to: startOfDay)!
    }

    var endOfMonth: Date {
        var components = DateComponents()
        components.month = 1
        components.second = -1
        return Calendar(identifier: .gregorian).date(byAdding: components, to: startOfMonth)!
    }

    func isMonday() -> Bool {
        let calendar = Calendar(identifier: .gregorian)
        let components = calendar.dateComponents([.weekday], from: self)
        return components.weekday == 2
    }
}
Alex Stone
  • 46,408
  • 55
  • 231
  • 407
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • @Martin, How to get previous and next month by button action[swift] – iDeveloper Oct 01 '16 at 07:16
  • 1
    @iDeveloper: Sorry, but that is a completely different question which cannot be simply answered in a comment. – Martin R Oct 01 '16 at 07:36
  • @MartinR : Have you tested this code for December month ? – Dhiru Dec 04 '17 at 09:02
  • @Dhiru: I don't know, I wrote that 2 years ago. Is there a problem? – Martin R Dec 04 '17 at 10:16
  • 1
    FWIW (This may be new, not sure) I think you want comps2.day = 0 not comps2.day = -1 day = 1 -> First of month, day = 0 -> last day of previous month, day = -1 -> 2nd to last day of previous month. Or something else is wrong in my code, but change -1 -> 0 fixed things ¯\\_(ツ)_/¯ – nickneedsaname Sep 13 '19 at 22:26
  • In the given solution it will be the beginning of the last day of the month. Which may be desireble in case of exclusive condition (e.g. include all data between the beginning of the month and before the last day). For the inclusive condition one must check @nickneedsaname comment or use -1 second instead of a -1 day. In most cases the end of month will be at 23:59:59 and not at 00:00:00 – Maks Mar 19 '20 at 08:10
  • 2
    @Maks: For a check “is the date in this month” I would compute the start of this month and the start of the next month without subtracting anything. Then compare with `if date >= startDate && date < endDate` – Martin R Mar 19 '20 at 08:36
51

With Swift 3 & iOS 10 the easiest way I found to do this is Calendar's dateInterval(of:for:):

guard let interval = calendar.dateInterval(of: .month, for: Date()) else { return }

You can then use interval.start and interval.end to get the dates you need.

LuisCien
  • 6,362
  • 4
  • 34
  • 42
27

Swift 3

Many date example for :

Last 6 month, last 3 month, yesterday, last 7 day, last 30 day, previous month, current month start & end, last month start & end date

let startDate = dateFormatter.string(from: Date().getThisMonthStart()!)
let endDate = dateFormatter.string(from: Date().getThisMonthEnd()!)

extension Date {

func getLast6Month() -> Date? {
    return Calendar.current.date(byAdding: .month, value: -6, to: self)
}

func getLast3Month() -> Date? {
    return Calendar.current.date(byAdding: .month, value: -3, to: self)
}

func getYesterday() -> Date? {
    return Calendar.current.date(byAdding: .day, value: -1, to: self)
}

func getLast7Day() -> Date? {
    return Calendar.current.date(byAdding: .day, value: -7, to: self)
}
func getLast30Day() -> Date? {
    return Calendar.current.date(byAdding: .day, value: -30, to: self)
}

func getPreviousMonth() -> Date? {
    return Calendar.current.date(byAdding: .month, value: -1, to: self)
}

// This Month Start
func getThisMonthStart() -> Date? {
    let components = Calendar.current.dateComponents([.year, .month], from: self)
    return Calendar.current.date(from: components)!
}

func getThisMonthEnd() -> Date? {
    let components:NSDateComponents = Calendar.current.dateComponents([.year, .month], from: self) as NSDateComponents
    components.month += 1
    components.day = 1
    components.day -= 1
    return Calendar.current.date(from: components as DateComponents)!
}

//Last Month Start
func getLastMonthStart() -> Date? {
    let components:NSDateComponents = Calendar.current.dateComponents([.year, .month], from: self) as NSDateComponents
    components.month -= 1
    return Calendar.current.date(from: components as DateComponents)!
}

//Last Month End
func getLastMonthEnd() -> Date? {
    let components:NSDateComponents = Calendar.current.dateComponents([.year, .month], from: self) as NSDateComponents
    components.day = 1
    components.day -= 1
    return Calendar.current.date(from: components as DateComponents)!
}

}
Bhadresh Kathiriya
  • 3,147
  • 2
  • 21
  • 41
14

Swift 4

If you only need the ordinal day:

func lastDay(ofMonth m: Int, year y: Int) -> Int {
    let cal = Calendar.current
    var comps = DateComponents(calendar: cal, year: y, month: m)
    comps.setValue(m + 1, for: .month)
    comps.setValue(0, for: .day)
    let date = cal.date(from: comps)!
    return cal.component(.day, from: date)
}

lastDay(ofMonth: 2, year: 2018)  // 28
lastDay(ofMonth: 2, year: 2020)  // 29
Miguel Gallego
  • 427
  • 4
  • 7
7

Here is easiest solution:

extension Date {

func startOfMonth() -> Date {
    let interval = Calendar.current.dateInterval(of: .month, for: self)
    return (interval?.start.toLocalTime())! // Without toLocalTime it give last months last date
}

func endOfMonth() -> Date {
    let interval = Calendar.current.dateInterval(of: .month, for: self)
    return interval!.end
}

// Convert UTC (or GMT) to local time
func toLocalTime() -> Date {
    let timezone    = TimeZone.current
    let seconds     = TimeInterval(timezone.secondsFromGMT(for: self))
    return Date(timeInterval: seconds, since: self)
}}

and then call these with your date instance:

print(Date().startOfMonth())
print(Date().endOfMonth())
Zulqarnain Mustafa
  • 1,615
  • 2
  • 22
  • 25
7

This is the simplest way that I found (Swift 5+):

extension Date {

    func getStart(of component: Calendar.Component, calendar: Calendar = Calendar.current) -> Date? {
        return calendar.dateInterval(of: component, for: self)?.start
    }

    func getEnd(of component: Calendar.Component, calendar: Calendar = Calendar.current) -> Date? {
        return calendar.dateInterval(of: component, for: self)?.end
    }
}
rubik
  • 572
  • 4
  • 15
5

2017...

First, get the month you need:

    let cal = Calendar.current
    let d = Calendar.current.date(byAdding: .month, value: 0, to: Date())!
    
    // for "last month" just use -1, for "next month" just use 1, etc
    

To get the day-of-the-week for the first day of the month:

    let c = cal.dateComponents([.year, .month], from: d)
    let FDOM = cal.date(from: c)!
    let dowFDOM = cal.component(.weekday, from: FDOM)

    print("the day-of-week on the 1st is ... \(dowFDOM)")
    // so, that's 1=Sunday, 2=Monday, etc.
    

To get the number of days in the month:

    let r = cal.range(of: .day, in: .month, for: d)!
    let kDays = r.count

    print("the number of days is ... \(kDays)")
Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
3

With Swift 3, you can choose one of the two following patters in order to retrieve the first and last days of a month.


#1. Using Calendar dateComponents(_:from:), date(from:) and date(byAdding:to:wrappingComponents:) methods

With this pattern, you first get the date of the first day of a month then add a month and remove a day from it in order to get the date of the last day of the month. The Playground code below shows how to set it:

import Foundation

// Set calendar and date 
let calendar = Calendar.current
let date = calendar.date(byAdding: DateComponents(day: -10), to: Date())!

// Get first day of month
let firstDayComponents = calendar.dateComponents([.year, .month], from: date)
let firstDay = calendar.date(from: firstDayComponents)!

// Get last day of month
let lastDayComponents = DateComponents(month: 1, day: -1)
let lastDay = calendar.date(byAdding: lastDayComponents, to: firstDay)!

// Set date formatter
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK")
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .long

// Print results
print(dateFormatter.string(from: date)) // Prints: 22 March 2017 at 18:07:15 CET
print(dateFormatter.string(from: firstDay)) // Prints: 1 March 2017 at 00:00:00 CET
print(dateFormatter.string(from: lastDay)) // Prints: 31 March 2017 at 00:00:00 CEST

#2. Using Calendar range(of:in:for:), dateComponents(_:from:) and date(from:) and methods

With this pattern, you get a range of absolute day values in a month and then retrieve the dates of the first day and last day of the month from it. The Playground code below shows how to set it:

import Foundation

// Set calendar and date
let calendar = Calendar.current
let date = calendar.date(byAdding: DateComponents(day: -10), to: Date())!

// Get range of days in month
let range = calendar.range(of: .day, in: .month, for: date)! // Range(1..<32)

// Get first day of month
var firstDayComponents = calendar.dateComponents([.year, .month], from: date)
firstDayComponents.day = range.lowerBound
let firstDay = calendar.date(from: firstDayComponents)!

// Get last day of month
var lastDayComponents = calendar.dateComponents([.year, .month], from: date)
lastDayComponents.day = range.upperBound - 1
//lastDayComponents.day = range.count // also works
let lastDay = calendar.date(from: lastDayComponents)!

// Set date formatter
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_UK")
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .long

// Print results
print(dateFormatter.string(from: date)) // prints: 22 March 2017 at 18:07:15 CET
print(dateFormatter.string(from: firstDay)) // prints: 1 March 2017 at 00:00:00 CET
print(dateFormatter.string(from: lastDay)) // prints: 31 March 2017 at 00:00:00 CEST
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
2

In swift 3, if you put 0 to day component you can get the last day of the month. There's an example code:

 public func isMoreDays(date: Date, asc: Bool)->Bool{
    //components
    var dayComponents = self.getDateComponents(date: date)
    //asc is true if ascendant or false if descendant
    dayComponents.day = asc ?  0 : 1
    //plus 1 to month 'cos if you set up day to 0 you are going to the previous month
    dayComponents.month = asc ? dayComponents.month! + 1 : dayComponents.month

    //instantiate calendar and get the date
    let calendar : Calendar = NSCalendar.current
    let day = calendar.date(from: dayComponents)

    //date comparison
    if(day?.compare(date) == .orderedSame){
        return false
    }
    return true
}
Alfredo Luco G
  • 876
  • 7
  • 18
  • 1
    While this is "natty", I can't support it. It is undefined, and - merely to test **if it wraps properly through years** - would take a lot of work. Note that today, it is very easy to do, you just use "range" to get the last date of the month. – Fattie Jul 17 '17 at 21:39
  • @Fattie Can you give me an example when you use range to getting the first date of the month? – Alfredo Luco G Jul 17 '17 at 21:54
  • ciao @alfredo, I meant for the Last day of the month: anyway, I put in an answer! I just worry your "0" solution is dangerous, when you wrap you know?! – Fattie Jul 17 '17 at 23:55
1

You can use the following extensions here :

let today = Date()
let startOfMonth = today.beginning(of: .month)
let endOfMonth = today.end(of: .month)
Valy Dia
  • 2,781
  • 2
  • 12
  • 32
0

Without TimeZone influense, Swift 5

public extension Date {
    var startDateOfMonth: Date {
        let components = Calendar.gregorian.dateComponents([.year, .month], from: self)
        guard let date = Calendar.gregorian.date(from: components) else {
            fatalError("Unable to get start date from date")
        }
        return date
    }
    
    var endDateOfMonth: Date {
        guard let date = Calendar.gregorian.date(
            byAdding: DateComponents(month: 1, day: -1),
            to: self.startDateOfMonth
        ) else {
            fatalError("Unable to get end date from date")
        }
        return date
    }
    
    var isStartDateOfMonth: Bool {
        Calendar.current.isDate(self, inSameDayAs: startDateOfMonth)
    }
    
    var isEndDateOfMonth: Bool {
        Calendar.current.isDate(self, inSameDayAs: endDateOfMonth)
    }
}
Dmih
  • 530
  • 6
  • 9