Setting calendar unit month in swift increase the year - swift

The following code was supposed to set the current year to its first day:
import UIKit
private func getYearStartDate() -> NSDate {
let calendar = NSCalendar.currentCalendar()
var date = calendar.dateBySettingHour(0, minute: 0, second: 0, ofDate: NSDate(), options: [])
date = calendar.dateBySettingUnit(.Month, value: 1, ofDate: date!, options: [])
date = calendar.dateBySettingUnit(.Day, value: 1, ofDate: date!, options: [])
return date!
}
print("startdate=\(getYearStartDate())")
print("enddate=\(NSDate())")
But when I inspect it, the value is set to the first day of the NEXT year.
My result:
startdate=2017-01-01 02:00:00 +0000
enddate=2016-09-06 13:44:06 +0000
Result I expect:
startdate=2016-01-01 02:00:00 +0000
enddate=2016-09-06 13:44:06 +0000

The method nextDateAfterDate:matchingComponents:options: of NSCalendar is more suitable for that purpose, it can also search backwards for the past January 1st:
let calendar = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.month = 1
components.day = 1
let yearStartDate = calendar.nextDateAfterDate(NSDate(),
matchingComponents: components,
options: [.MatchNextTime, .SearchBackwards])

Related

What's wrong with my usage of `enumerateDates`?

I'm trying to use enumerateDates(startingAfter:matching:matchingPolicy:repeatedTimePolicy:direction:using:) to list all first days of months between two dates. However, I find it didn't return the last date as what I expected.
Here's the code snippet. I set the endDate as a random time on 2023/1/1. I thought it should also include 2023/1/1 in its output, but seems it ignored it. Is it an time zone issue because I'm in UTC+8?
let calendar = Calendar.current
let startDate = calendar.date(from: DateComponents(year: 2022, month: 10, day: 2))!
let endDate = calendar.date(from: DateComponents(year: 2023, month: 1, day: 1, hour: 12))!
let firstDayOfMonth = DateComponents(day: 1)
calendar.enumerateDates(startingAfter: endDate, matching: firstDayOfMonth, matchingPolicy: .strict, direction: .backward) { result, exactMatch, stop in
if let date = result {
if date < startDate {
stop = true
} else {
print(date)
}
}
}
The output:
// I expect a `2022-12-31 16:00:00 +0000` here!
2022-11-30 16:00:00 +0000
2022-10-31 16:00:00 +0000

Find dates in current month corresponding to a given weekday

I am currently making a scheduling feature for my application. I have a view where a user is able to select what days of the week they will be available for. Now, I am looking for a way to generate an array of dates for the given weekdays for the upcoming month.
In other words, if the user selects that they will be available on Monday, the function needs to return all dates that satisfy the given predicament for the month ahead (4 date objects).
Here's what I have tried but it doesn't seem to work:
var masterSchedule = [WorkDay(weekDay: 1, startTime: 8, endTime: 16, busyHours: []), WorkDay(weekDay: 4, startTime: 8, endTime: 16, busyHours: [])]
func getDates() {
for workDay in masterSchedule {
var components = DateComponents()
components.weekday = workDay.weekDay
components.year = 2021
components.month = 8
let date = Calendar.current.date(from: components) ?? Date()
availableDays.append(date)
}
}
For some reason, the output returns 2 same dates of 2021-07-31 23:00:00 +0000.
Can someone help me?
Here's an example that prints all the Mondays in October
import UIKit
import SwiftUI
import PlaygroundSupport
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.dateStyle = .short
let firstOfOctober = formatter.date(from: "10/1/2021")!
if let interval = calendar.dateInterval(of: .month, for: firstOfOctober) {
let mondays = DateComponents(weekday: 2)
calendar.enumerateDates(startingAfter: firstOfOctober,
matching: mondays, matchingPolicy: .previousTimePreservingSmallerComponents) {
date, exactMatch, stopLooking in
if let date = date {
if date < interval.end {
print(formatter.string(from: date))
} else {
stopLooking = true
}
}
}
}

How to change the hours and minutes in an existing Date object in Swift?

I need to compare two Date object to get the day difference between them, for example: 10/10 compares with today 10/7 will be 3, but the Date object returned to me from server is not aligned with the current time. There will be a few minutes difference which results in 10/10 being 2 days ahead of 10/7 because of the delay
I found a line of code that can give me a Date object of the current time, but I want to convert an existing Date object from somewhere else, how do I do it?
let today = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date())!
e.g. 2020-10-08 16:08:57.259580+0000 I want it to be 2020-10-08 00:00:00 +0000 something like this
Don’t use midnight. Just parse your date string first. For calendrical calculations you should always use noon. First create a custom date format to parse your date string properly.
extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSSxx"
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
}
Then create a helper to convert your date to noon time and another one to calculate the days between two dates and set them to noon:
extension Date {
var noon: Date {
Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
func days(from date: Date) -> Int {
Calendar.current.dateComponents([.day], from: date.noon, to: noon).day!
}
var daysFromToday: Int { days(from: Date()) }
}
Playground testing:
let dateString = "2020-10-08 16:08:57.259580+0000"
let now = Date() // "Oct 8, 2020 at 5:56 AM"
print(Formatter.iso8601.string(from: now)) // "2020-10-08 08:56:46.179000+0000\n"
if let date = Formatter.iso8601.date(from: dateString) { // "Oct 8, 2020 at 1:08 PM"
let days = Date().days(from: date) // 0
}
let dateString = "2020-10-10 16:08:57.259580+0000"
if let date = Formatter.iso8601.date(from: dateString) {
let days = date.daysFromToday // 2
}

Generating the start and end of the month. Swift 2

I have the following code:
func rangeOfPeriod(period: NSCalendarUnit, date: NSDate) -> (NSDate, NSDate) {
let calendar = NSCalendar.currentCalendar()
var startDate: NSDate? = nil
var duration: NSTimeInterval = 0
calendar.rangeOfUnit(period, startDate: &startDate, interval: &duration, forDate: date)
let endDate = startDate!.dateByAddingTimeInterval(duration - 1)
return (startDate!, endDate)
}
When i print the following lines:
print("Day test = \(rangeOfPeriod(.Day, date: NSDate()))")
print("Week test = \(rangeOfPeriod(.WeekOfYear, date: NSDate()))")
print("month test = \(rangeOfPeriod(.Month, date: NSDate()))")
print("Year test = \(rangeOfPeriod(.Year, date: NSDate()))")
All of them work as expected apart from month.
I get 'month test = (2016-03-01 00:00:00 +0000, 2016-03-31 22:59:59 +0000)' as a result and it seems to be missing a hour. For the time for all the others i get '2016-03-26 23:59:59 +0000'.
Any help would be appreciated
Maybe there is a confusion about the interval parameter in the rangeOfUnit method.
From the documentation
func rangeOfUnit(_ unit: NSCalendarUnit,
startDate datep: AutoreleasingUnsafeMutablePointer<NSDate?>,
interval tip: UnsafeMutablePointer<NSTimeInterval>,
forDate date: NSDate) -> Bool
.....
tip : Upon return, contains the duration (as NSTimeInterval) of the calendar unit unit that
contains the date date
For this month (March 2016) due to Daylight Saving Time change the duration is
31 * 86400.0 - 3600.0 = 2674800.0
That means there is an hour missing, but it's not specified when.
To get the end of the month this is more accurate
func endOfThisMonth() -> NSDate
{
let calendar = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.day = 1
let startOfNextMonth = calendar.nextDateAfterDate(NSDate(), matchingComponents: components, options: .MatchNextTime)!
return calendar.dateByAddingUnit(.Second, value: -1, toDate: startOfNextMonth, options: [])!
}

How do I find the beginning of the week from an NSDate?

I'm implementing a calendar view, and I'd like it to start at the beginning of the week containing a particular date. Eg. If the target date is Monday, Feb 29, 2016, and the current calendar is set to start on Sunday, I'd like my view to start with Sunday, February 28.
This seems like it should be straightforward:
let calendar = NSCalendar.currentCalendar()
let firstDate = calendar.nextDateAfterDate(targetDate,
matchingUnit: .Weekday,
value: calendar.firstWeekday,
options: .SearchBackwards)
But this fails with:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Exactly one option from the set {NSCalendarMatchPreviousTimePreservingSmallerUnits, NSCalendarMatchNextTimePreservingSmallerUnits, NSCalendarMatchNextTime} must be specified.'
I can get basically what I want with:
let firstDate = calendar.nextDateAfterDate(firstDate,
matchingUnit: .Weekday,
value: calendar.firstWeekday,
options: .MatchPreviousTimePreservingSmallerUnits)?
.dateByAddingTimeInterval(-7 * 84600)
But it seems like a bad practice, since sometimes the number of seconds in a day isn't 86400.
Is there a better way?
you can use Calendar method date(from: DateComponents) passing [.yearForWeekOfYear, .weekOfYear] components from any date it will return the first day of the week from the calendar used. So if you would like to get Sunday just use Gregorian calendar. If you would like to get the Monday as the first day of the week you can use Calendar .iso8601 as you can see in this answer
Xcode 12 • Swift 5.3 or later (works with previous Swift versions as well)
extension Calendar {
static let gregorian = Calendar(identifier: .gregorian)
}
extension Date {
func startOfWeek(using calendar: Calendar = .gregorian) -> Date {
calendar.dateComponents([.calendar, .yearForWeekOfYear, .weekOfYear], from: self).date!
}
}
usage:
Date().startOfWeek() // "Sep 20, 2020 at 12:00 AM"
If you would like to get the beginning of week at a particular timezone you just need to use a custom calendar:
var gregorianUTC = Calendar.gregorian
gregorianUTC.timeZone = TimeZone(identifier: "UTC")!
print(Date().startOfWeek(using: gregorianUTC)) // "2020-09-20 00:00:00 +0000\n"
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!
You can implement this as Date class extension or something. It should returns something like 2020-01-06 00:00:00 +0000
Xcode 11.3 Swift 5
func firstDayOfWeek() -> Date {
var c = Calendar(identifier: .iso8601)
c.timeZone = TimeZone(secondsFromGMT: 0)!
print(
c.date(from: c.dateComponents([.weekOfYear, .yearForWeekOfYear], from: Date()))!
)
}
The Calendar has a mechanism for finding date at the start of a given time interval (say week of year, or month) that contains a given date:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = dateFormatter.date(from: "2017-01-07")
if let date = date {
let calendar = Calendar(identifier: .gregorian)
var startDate : Date = Date()
var interval : TimeInterval = 0
if calendar.dateInterval(of: .weekOfYear, start: &startDate, interval: &interval, for: date) {
print("Start of week is \(startDate)")
// prints "Start of week is 2017-01-01 06:00:00 +0000"
}
}
In order to get the user's locale settings respected correctly, you should use the user's Calendar firstWeekday property in the DateComponents. This is what I usually use:
// MARK: first day of week
extension Date {
/**
Finds the first day of the week the subject date falls into.
- Parameter calendar: The calendar to use. Defaults to the user's current calendar.
- Returns: The `Date` of the first day of the week into which the subject date falls.
`startOfWeek()` respects the user's locale settings, i.e. will automatically use Sunday/Monday/etc. as first
weekday based on the user's region and locale settings.
*/
func startOfWeek(using calendar: Calendar = .current) -> Date? {
var components = calendar.dateComponents([.weekday, .year, .month, .weekOfYear], from: self)
components.weekday = calendar.firstWeekday
return calendar.date(from: components)
}
}
Basically use
NSCalender
and
dateByAddingComponents
. For solving of you're problem try to use this code sample:
let cal = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.weekOfYear -= 1
if let date = cal.dateByAddingComponents(components, toDate: NSDate(), options: NSCalendarOptions(0)) {
var beginningOfWeek: NSDate?
var weekDuration = NSTimeInterval()
if cal.rangeOfUnit(.CalendarUnitWeekOfYear, startDate: &beginningOfWeek, interval: &weekDuration, forDate: date) {
print(beginningOfWeek)
}
}
I had problems with all previous solutions, since they do not take into account user's calendar setting. Next code will be taking into account that.
extension Date {
var startOfWeek: Date? {
let calendar = Calendar.current
var components: DateComponents? = calendar.dateComponents([.weekday, .year, .month, .day], from: self)
var modifiedComponent = components
modifiedComponent?.day = (components?.day ?? 0) - ((components?.weekday ?? 0) - 1)
return calendar.date(from: modifiedComponent!)
}
var endOfWeek: Date? {
let calendar = Calendar.current
var components: DateComponents? = calendar.dateComponents([.weekday, .year, .month, .day], from: self)
var modifiedComponent = components
modifiedComponent?.day = (components?.day ?? 0) + (7 - (components?.weekday ?? 0))
modifiedComponent?.hour = 23
modifiedComponent?.minute = 59
modifiedComponent?.second = 59
return calendar.date(from: modifiedComponent!)
}
}