26

I'm trying to get Monday's date of the current week. This is treated as the first day of the week in my table view. I also need to get Sunday's of the current week. This is treated as the last day of the week in my table view.

Current attempt:

let date = NSDate()
let calendar = NSCalendar.currentCalendar()
calendar.firstWeekday = 1
//attempt to changefirstday

let dateFormatter = NSDateFormatter()
let theDateFormat = NSDateFormatterStyle.ShortStyle
let theTimeFormat = NSDateFormatterStyle.ShortStyle
dateFormatter.dateStyle = theDateFormat
dateFormatter.timeStyle = theTimeFormat

let currentDateComponents = calendar.components([.YearForWeekOfYear, .WeekOfYear ], fromDate: date)
let startOfWeek = calendar.dateFromComponents(currentDateComponents)
print("startOfWeek is \(startOfWeek)")
let stringDate = dateFormatter.stringFromDate(startOfWeek!)
print("string date is \(stringDate)") //This is returning Sunday's date
user2363025
  • 6,365
  • 19
  • 48
  • 89
  • 1
    You can use calendar ISO8601 where the first weekday is monday and don't change firstWeekday to 1 (sunday) let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierISO8601)! – Leo Dabus Oct 28 '15 at 17:31
  • Your question is unclear to me. If you set `calendar.firstWeekday = 1` then Sunday is defined as the first day of the week, so your result is to be expected. – Martin R Oct 28 '15 at 19:03

8 Answers8

93

I wrote Date extensions to get Date for certain weekday and here is how easy it is to use with Swift 5,

Date.today()                                  // Oct 15, 2019 at 9:21 AM
Date.today().next(.monday)                    // Oct 21, 2019 at 9:21 AM
Date.today().next(.sunday)                    //  Oct 20, 2019 at 9:21 AM


Date.today().previous(.sunday)                // Oct 13, 2019 at 9:21 AM
Date.today().previous(.monday)                // Oct 14, 2019 at 9:21 AM

Date.today().previous(.thursday)              // Oct 10, 2019 at 9:21 AM
Date.today().next(.thursday)                  // Oct 17, 2019 at 9:21 AM
Date.today().previous(.thursday,
                      considerToday: true)    // Oct 10, 2019 at 9:21 AM


Date.today().next(.monday)
            .next(.sunday)
            .next(.thursday)                  // Oct 31, 2019 at 9:21 AM

And here is Date extension for that,

extension Date {

  static func today() -> Date {
      return Date()
  }

  func next(_ weekday: Weekday, considerToday: Bool = false) -> Date {
    return get(.next,
               weekday,
               considerToday: considerToday)
  }

  func previous(_ weekday: Weekday, considerToday: Bool = false) -> Date {
    return get(.previous,
               weekday,
               considerToday: considerToday)
  }

  func get(_ direction: SearchDirection,
           _ weekDay: Weekday,
           considerToday consider: Bool = false) -> Date {

    let dayName = weekDay.rawValue

    let weekdaysName = getWeekDaysInEnglish().map { $0.lowercased() }

    assert(weekdaysName.contains(dayName), "weekday symbol should be in form \(weekdaysName)")

    let searchWeekdayIndex = weekdaysName.firstIndex(of: dayName)! + 1

    let calendar = Calendar(identifier: .gregorian)

    if consider && calendar.component(.weekday, from: self) == searchWeekdayIndex {
      return self
    }

    var nextDateComponent = calendar.dateComponents([.hour, .minute, .second], from: self)
    nextDateComponent.weekday = searchWeekdayIndex

    let date = calendar.nextDate(after: self,
                                 matching: nextDateComponent,
                                 matchingPolicy: .nextTime,
                                 direction: direction.calendarSearchDirection)

    return date!
  }

}

// MARK: Helper methods
extension Date {
  func getWeekDaysInEnglish() -> [String] {
    var calendar = Calendar(identifier: .gregorian)
    calendar.locale = Locale(identifier: "en_US_POSIX")
    return calendar.weekdaySymbols
  }

  enum Weekday: String {
    case monday, tuesday, wednesday, thursday, friday, saturday, sunday
  }

  enum SearchDirection {
    case next
    case previous

    var calendarSearchDirection: Calendar.SearchDirection {
      switch self {
      case .next:
        return .forward
      case .previous:
        return .backward
      }
    }
  }
}
Sandeep
  • 20,908
  • 7
  • 66
  • 106
  • and what I change in order to get the Sunday at the end of this week (1st Nov in this week's case)? – user2363025 Oct 29 '15 at 08:29
  • right now I can use get(.Previous, "Monday") to get the monday of the current week and get(.Next, "Sunday", considerToday: true) to successfully get the sunday of the current week. If the week were to start on a Monday and end on a Sunday. This is what I need, but I also need to get the following Monday and following Sunday. So I can use your get(.Next, "Monday") to get next week's Monday but I can't get the Sunday of the following week. Can this be done? – user2363025 Nov 03 '15 at 12:52
  • To get the exact time not only 12:00AM, please see my answer. – Muhammad Umair May 28 '16 at 18:17
  • 1
    If I have `2018-04-02` and ask for the previous Sunday, it gives me `2018-03-25` instead `2018-04-01`. – pableiros Jun 21 '18 at 18:18
  • How do you see the date? Logging to XCode console? See the timezone, it might be that it gives correct date for the original timezone and not the one that is printed with. – Sandeep Jun 21 '18 at 18:35
  • @Sandeep: Can you check with Xcode 11 as it's failing somehow due to apple bug. – Abhishek Thapliyal Oct 14 '19 at 10:18
  • @AbhishekThapliyal Please look at the changes, it should work fine with new Swift now. Also made some changes to preserve hours, minutes and seconds. – Sandeep Oct 15 '19 at 03:39
  • @Sandeep Reference to your post, Can I ask one question related to date extension ? – Newbie Feb 17 '20 at 09:28
39

You can use calendar ISO8601 where the first weekday is Monday:

Swift 5.2 or later

extension Calendar {
    static let iso8601 = Calendar(identifier: .iso8601)
    static let iso8601UTC: Calendar = {
        var calendar = Calendar(identifier: .iso8601)
        calendar.timeZone = TimeZone(identifier: "UTC")!
        return calendar
    }()
}

let monday =
    Calendar.iso8601.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: Date()).date!  // "Nov 9, 2020 at 12:00 AM"
print(monday.description(with: .current))   // "Monday, November 9, 2020 at 12:00:00 AM Brasilia Standard Time\n"
let mondayUTC =
    Calendar.iso8601UTC.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: Date()).date!  // "Nov 8, 2020 at 9:00 PM" TimeZone -03:00
print(mondayUTC)   // "2020-11-09 00:00:00 +0000\n"

Implemented as a Date computer property extension:

extension Date {
    var mondayOfTheSameWeek: Date {
        Calendar.iso8601.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: self).date!
    }
    var mondayOfTheSameWeekAtUTC: Date {
        Calendar.iso8601UTC.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: self).date!
    }
}

let mondayOfTheSameWeek = Date().mondayOfTheSameWeek   // "Nov 9, 2020 at 12:00 AM"
print(mondayOfTheSameWeek.description(with: .current)) // "Monday, November 9, 2020 at 12:00:00 AM Brasilia Standard Time\n"
let mondayOfTheSameWeekAtUTC = Date().mondayOfTheSameWeekAtUTC  // "Nov 8, 2020 at 9:00 PM"
print(mondayOfTheSameWeekAtUTC) // "2020-11-09 00:00:00 +0000\n"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 2
    Perfect Answer, Small correction with timezone. `var comp: DateComponents = Calendar(identifier: .iso8601).dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date()) comp.timeZone = TimeZone(secondsFromGMT: 0) let mondayDate = Calendar(identifier: .iso8601).date(from: comp)! print("Monday \(mondayDate)")` – Suresh Durishetti Mar 23 '18 at 10:59
22

Here's a simplified version of Sandeep's answer.

Usage:

Date().next(.monday)
Date().next(.monday, considerToday: true)
Date().next(.monday, direction: .backward)

Extension:

public func next(_ weekday: Weekday,
                 direction: Calendar.SearchDirection = .forward,
                 considerToday: Bool = false) -> Date
{
    let calendar = Calendar(identifier: .gregorian)
    let components = DateComponents(weekday: weekday.rawValue)

    if considerToday &&
        calendar.component(.weekday, from: self) == weekday.rawValue
    {
        return self
    }

    return calendar.nextDate(after: self,
                             matching: components,
                             matchingPolicy: .nextTime,
                             direction: direction)!
}

public enum Weekday: Int {
    case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday
}
John
  • 964
  • 8
  • 21
8

Here is the extension I created, first it finds sunday and then it adds one day

extension Date {  
    var startOfWeek: Date? {
        let gregorian = Calendar(identifier: .gregorian)
        guard let sunday = gregorian.date(from: gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self)) else { return nil }
        return gregorian.date(byAdding: .day, value: 1, to: sunday)
    }
}
Neorcisa
  • 124
  • 2
  • 9
  • Nice extension, but shouldn't the value added be 2 as `.firstWeekday`? If I am using the value 1, it delivers Sunday's date...`return gregorian.date(byAdding: .day, value: 2, to: sunday)` – Alessign Aug 09 '17 at 12:29
  • 2
    I'm not sure why you would get Sunday. I just tried and got a Monday http://swift.sandbox.bluemix.net/#/repl/598b2f8308bc28326a66e302 – Neorcisa Aug 09 '17 at 15:53
  • I think you should use gregorian.firstWeekday instead of 1 or 2 – ElOjcar Jun 02 '18 at 08:38
  • 1
    The problem with this is: If it is Sunday, this extension will return tomorrows date. If you are considering Sunday to be the last day, then you should get the previous monday, not the next monday. – Slaknation Nov 04 '19 at 20:34
3

Try to use:

calendar.firstWeekday = 2

Edit

To be more specific: NSCalendar.currentCalendar() returns user calendar. According to docs:

The returned calendar is formed from the settings for the current user’s chosen system locale overlaid with any custom settings the user has specified in System Preferences.

If you want always Monday as first day, I think you should use:

let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
calendar!.firstWeekday = 2
zuziaka
  • 575
  • 3
  • 10
2

Swift 4 Solution

I have figured out according to my requirement, where I have find out dates for following.

1. Today

2. Tomorrow 

3. This Week 

4. This Weekend 

5. Next Week 

6. Next Weekend

So, I have created Date Extension to get Dates of Current Week and Next Week.

CODE

extension Date {

    func getWeekDates() -> (thisWeek:[Date],nextWeek:[Date]) {
        var tuple: (thisWeek:[Date],nextWeek:[Date])
        var arrThisWeek: [Date] = []
        for i in 0..<7 {
            arrThisWeek.append(Calendar.current.date(byAdding: .day, value: i, to: startOfWeek)!)
        }
        var arrNextWeek: [Date] = []
        for i in 1...7 {
            arrNextWeek.append(Calendar.current.date(byAdding: .day, value: i, to: arrThisWeek.last!)!)
        }
        tuple = (thisWeek: arrThisWeek,nextWeek: arrNextWeek)
        return tuple
    }

    var tomorrow: Date {
        return Calendar.current.date(byAdding: .day, value: 1, to: noon)!
    }
    var noon: Date {
        return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
    }

    var startOfWeek: Date {
        let gregorian = Calendar(identifier: .gregorian)
        let sunday = gregorian.date(from: gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
        return gregorian.date(byAdding: .day, value: 1, to: sunday!)!
    }

    func toDate(format: String) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = format
        return formatter.string(from: self)
    }
}

USAGE:

let arrWeekDates = Date().getWeekDates() // Get dates of Current and Next week.
let dateFormat = "MMM dd" // Date format
let thisMon = arrWeekDates.thisWeek.first!.toDate(format: dateFormat)
let thisSat = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 2].toDate(format: dateFormat)
let thisSun = arrWeekDates.thisWeek[arrWeekDates.thisWeek.count - 1].toDate(format: dateFormat)

let nextMon = arrWeekDates.nextWeek.first!.toDate(format: dateFormat)
let nextSat = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 2].toDate(format: dateFormat)
let nextSun = arrWeekDates.nextWeek[arrWeekDates.nextWeek.count - 1].toDate(format: dateFormat)

print("Today: \(Date().toDate(format: dateFormat))") // Sep 26
print("Tomorrow: \(Date().tomorrow.toDate(format: dateFormat))") // Sep 27
print("This Week: \(thisMon) - \(thisSun)") // Sep 24 - Sep 30
print("This Weekend: \(thisSat) - \(thisSun)") // Sep 29 - Sep 30
print("Next Week: \(nextMon) - \(nextSun)") // Oct 01 - Oct 07
print("Next Weekend: \(nextSat) - \(nextSun)") // Oct 06 - Oct 07

You can modify Extension according to your need.

Thanks!

Rashesh Bosamiya
  • 609
  • 1
  • 9
  • 13
1

Addition to @Saneep answer

If you would like to get exact dateTime as per given/current date (lets say you wanted to convert Monday's dateTime -> 23-05-2016 12:00:00 to 23-05-2016 05:35:17) then try this:

func convertDate(date: NSDate, toGivendate: NSDate) -> NSDate {
    let calendar = NSCalendar.currentCalendar()
    let comp = calendar.components([.Year, .Month, .Day, .Hour, .Minute, .Second], fromDate: toGivendate)
    let hour = comp.hour
    let minute = comp.minute
    let second = comp.second

    let dateComp = calendar.components([.Year, .Month, .Day], fromDate: date)
    let year = dateComp.year
    let month = dateComp.month
    let day = dateComp.day

    let components = NSDateComponents()
    components.year = year
    components.month = month
    components.day = day
    components.hour = hour
    components.minute = minute
    components.second = second

    let newConvertedDate = calendar.dateFromComponents(components)

    return newConvertedDate!
}
Muhammad Umair
  • 1,664
  • 2
  • 20
  • 28
-1

simple code (remember to take better care of the optionals):

let now = Date()

var calendar = Calendar(identifier: .gregorian)

let timeZone = TimeZone(identifier: "UTC")!

let desiredWeekDay = 2

let weekDay = calendar.component(.weekday, from: now)
var weekDayDate = calendar.date(bySetting: .weekday, value: desiredWeekDay, of: now)!

/// Swift will give back the closest day matching the value above so we need to manipulate it to be always included at cuurent week.
if weekDayDate > now, weekDay > desiredWeekDay {
    weekDayDate = weekDayDate - 7*24*60*60
}

print(weekDayDate)
Roi Zakai
  • 252
  • 3
  • 3