Get number of days between two NSDates when crossing new year - ios

I need to get the number of days between two dates which I get but if I input for example: 2016-12-10 and 2017-01-10 there will be a negative number of days. This is just occur when it´s new year between those two dates.
//Get the number of days between two NSDates
func daysBetween(date: NSDate) -> Int {
let calendar: NSCalendar = NSCalendar.currentCalendar()
let date1 = calendar.startOfDayForDate(self)
let date2 = calendar.startOfDayForDate(date)
let components = calendar.components(.Day, fromDate: date1, toDate: date2, options: [])
return components.day
}
//Using the function
let daysBetween = fromDate.daysBetween(toDate)
//Printing -334 if new year is between fromDate and toDate
print(daysBetween)
How can I modify my daysBetween(date: NSDate) function to prevent this from happening?
Edit:
Please don´t pay attention to why it´s printing exacltly -334. That´s just because fromDate and toDate are different days in month. The problem a wanna solve is why the response is negative and not 31 as it should be.
Solved:
It turned out to be as many of you thought. The fromDate is greater then toDate and causing a negative number. Stupid mistake. Thx guys!

You not need to worry about the days going negative. It's better to know if the first date (for example selected from an input UIDatePicker) is bigger than another. That is handled automatically when you converted back to an NSDate.
If the problem is how to print the days , you can use abs(days).

First compare both dates and then you can get correct positive value
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let startDate:NSDate = dateFormatter.dateFromString("2016-12-10")!
let endDate:NSDate = dateFormatter.dateFromString("2017-01-10")!
let cal = NSCalendar.currentCalendar()
let dayUnit: NSCalendarUnit = .Day
if startDate.compare(endDate) == NSComparisonResult.OrderedDescending{
let components = cal.components(dayUnit, fromDate: endDate, toDate: startDate, options: [])
print(components)
} else {
let components = cal.components(unit, fromDate: startDate, toDate: endDate, options: [])
print(components)
}
As Describe by Leo Dabus
You can do it without comparing the dates also:
let com = cal.components(dayUnit, fromDate: startDate.earlierDate(endDate), toDate: startDate.laterDate(endDate), options: [])
print(com)

Related

How would you set a date to a specific value in swift? [duplicate]

I have an NSDate object and I want to set it to an arbitrary time (say, midnight) so that I can use the timeIntervalSince1970 function to retrieve data consistently without worrying about the time when the object is created.
I've tried using an NSCalendar and modifying its components by using some Objective-C methods, like this:
let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
let components: NSDateComponents = cal.components(NSCalendarUnit./* a unit of time */CalendarUnit, fromDate: date)
let newDate: NSDate = cal.dateFromComponents(components)
The problem with the above method is that you can only set one unit of time (/* a unit of time */), so you could only have one of the following be accurate:
Day
Month
Year
Hours
Minutes
Seconds
Is there a way to set hours, minutes, and seconds at the same time and retain the date (day/month/year)?
Your statement
The problem with the above method is that you can only set one unit of
time ...
is not correct. NSCalendarUnit conforms to the RawOptionSetType protocol which
inherits from BitwiseOperationsType. This means that the options can be bitwise
combined with & and |.
In Swift 2 (Xcode 7) this was changed again to be
an OptionSetType which offers a set-like interface, see
for example Error combining NSCalendarUnit with OR (pipe) in Swift 2.0.
Therefore the following compiles and works in iOS 7 and iOS 8:
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
// Swift 1.2:
let components = cal.components(.CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear, fromDate: date)
// Swift 2:
let components = cal.components([.Day , .Month, .Year ], fromDate: date)
let newDate = cal.dateFromComponents(components)
(Note that I have omitted the type annotations for the variables, the Swift compiler
infers the type automatically from the expression on the right hand side of
the assignments.)
Determining the start of the given day (midnight) can also done
with the rangeOfUnit() method (iOS 7 and iOS 8):
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var newDate : NSDate?
// Swift 1.2:
cal.rangeOfUnit(.CalendarUnitDay, startDate: &newDate, interval: nil, forDate: date)
// Swift 2:
cal.rangeOfUnit(.Day, startDate: &newDate, interval: nil, forDate: date)
If your deployment target is iOS 8 then it is even simpler:
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)
Update for Swift 3 (Xcode 8):
let date = Date()
let cal = Calendar(identifier: .gregorian)
let newDate = cal.startOfDay(for: date)
Yes.
You don't need to fiddle with the components of the NSCalendar at all; you can simply call the dateBySettingHour method and use the ofDate parameter with your existing date.
let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
let newDate: NSDate = cal.dateBySettingHour(0, minute: 0, second: 0, ofDate: date, options: NSCalendarOptions())!
For Swift 3:
let date: Date = Date()
let cal: Calendar = Calendar(identifier: .gregorian)
let newDate: Date = cal.date(bySettingHour: 0, minute: 0, second: 0, of: date)!
Then, to get your time since 1970, you can just do
let time: NSTimeInterval = newDate.timeIntervalSince1970
dateBySettingHour was introduced in OS X Mavericks (10.9) and gained iOS support with iOS 8.
Declaration in NSCalendar.h:
/*
This API returns a new NSDate object representing the date calculated by setting hour, minute, and second to a given time.
If no such time exists, the next available time is returned (which could, for example, be in a different day than the nominal target date).
The intent is to return a date on the same day as the original date argument. This may result in a date which is earlier than the given date, of course.
*/
- (NSDate *)dateBySettingHour:(NSInteger)h minute:(NSInteger)m second:(NSInteger)s ofDate:(NSDate *)date options:(NSCalendarOptions)opts NS_AVAILABLE(10_9, 8_0);
Here's an example of how you would do it, without using the dateBySettingHour function (to make sure your code is still compatible with iOS 7 devices):
NSDate* now = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:now];
NSDate* midnightLastNight = [gregorian dateFromComponents:dateComponents];
Yuck.
There is a reason why I prefer coding in C#...
Anyone fancy some readable code..?
DateTime midnightLastNight = DateTime.Today;
;-)
Swift 5+
let date = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date())
Swift iOS 8 and up: People tend to forget that the Calendar and DateFormatter objects have a TimeZone. If you do not set the desired timzone and the default timezone value is not ok for you, then the resulting hours and minutes could be off.
Note: In a real app you could optimize this code some more.
Note: When not caring about timezones, the results could be OK on one device, and bad on an other device just because of different timezone settings.
Note: Be sure to add an existing timezone identifier! This code does not handle a missing or misspelled timezone name.
func dateTodayZeroHour() -> Date {
var cal = Calendar.current
cal.timeZone = TimeZone(identifier: "Europe/Paris")!
return cal.startOfDay(for: Date())
}
You could even extend the language. If the default timezone is fine for you, do not set it.
extension Date {
var midnight: Date {
var cal = Calendar.current
cal.timeZone = TimeZone(identifier: "Europe/Paris")!
return cal.startOfDay(for: self)
}
var midday: Date {
var cal = Calendar.current
cal.timeZone = TimeZone(identifier: "Europe/Paris")!
return cal.date(byAdding: .hour, value: 12, to: self.midnight)!
}
}
And use it like this:
let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: "Europe/Paris")
formatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
let midnight = Date().midnight
let midnightString = formatter.string(from: midnight)
let midday = Date().midday
let middayString = formatter.string(from: midday)
let wheneverMidnight = formatter.date(from: "2018/12/05 08:08:08")!.midnight
let wheneverMidnightString = formatter.string(from: wheneverMidnight)
print("dates: \(midnightString) \(middayString) \(wheneverMidnightString)")
The string conversions and the DateFormatter are needed in our case for some formatting and to move the timezone since the date object in itself does not keep or care about a timezone value.
Watch out! The resulting value could differ because of a timezone offset somewhere in your calculating chain!
Just in case someone is looking for this:
Using SwiftDate you could just do this:
Date().atTime(hour: 0, minute: 0, second: 0)
In my opinion, the solution, which is easiest to verify, but perhaps not the quickest, is to use strings.
func set( hours: Int, minutes: Int, seconds: Int, ofDate date: Date ) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let newDateString = "\(dateFormatter.string(from: date)) \(hours):\(minutes):\(seconds)"
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return dateFormatter.date(from: newDateString)
}
func resetHourMinuteSecond(date: NSDate, hour: Int, minute: Int, second: Int) -> NSDate{
let nsdate = NSCalendar.currentCalendar().dateBySettingHour(hour, minute: minute, second: second, ofDate: date, options: NSCalendarOptions(rawValue: 0))
return nsdate!
}
Use the current calendar to get the start of the day for the current time.
let today = Calendar.current.startOfDay(for: Date())

In the calendar function "date(byAdding: .day, value: , to: )", does the date we give to the function include the current date or not?

I have an app that uses Core Data. I have a function
func showHistory(for days:Int)->Int {
var historyForXDaysInt = Int()
var calendar = Calendar.current
calendar.locale = .current
calendar.timeZone = .current
var startDate:Date?
var endDate = Date()
switch days{
case 1:
startDate = Date()
startDate = calendar.startOfDay(for: startDate!)
default:
startDate = calendar.date(byAdding: .day, value: -days+1, to: Date())
startDate = calendar.startOfDay(for: startDate!)
}
//There is more to this function, where I make the request but I just included the relevant part for my question
}
My question: I am trying to get relevant data from Core Data between two dates via the predicate let predicate = NSPredicate(format: "(completedDate >= %#) AND (completedDate <= %#)", startDate! as NSDate, endDate as NSDate).There is a completedDate property in my core data model. When I make the request I get data for 1 more day than I asked for. I tried solving it with adding 1 to the days variable. For example, if I am asking for 2 days of data (meaning days = 2) I get data for 3 days. What might be the cause? Why does it not work as intended without the +1 ?

check value existence by NSDate as key in dictionary

I have a dictionary like this:
var dic = [NSDate: Int]()
it is used in my iOS to-do app to get the number of finished tasks of a particular date. I only care about the year, month and day sections in NSDate and also want to be able to get the number of tasks in a particular date using this dictionary, how can I do that? thanks.
Instead of storing your date as NSDate in your dictionary you can save it as String so that comparison will be easier. Use following code to store it as a string
func dateFromString(date : NSDate) -> String {
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
return dateFormatter.stringFromDate(date)
}
You can pass NSDate() to above function and it will give you string containing only year, month and date. For retrieving your data from dictionary use following.
func dateFrom(year:Int, month:Int, day:Int) -> String {
let components = NSDateComponents()
components.year = year
components.month = month
components.day = day
let gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian)
let date = gregorian!.dateFromComponents(components)
return dateFromString(date!)
}
You can pass year, month and date to above function and it will return corresponding date in string format. So your dictionary operations will look like
dict[dateFromString(NSDate())] = 1 //for insertion or updation
let numOfTasks = dict[dateFrom(2016, month: 1, day: 15)] //to get task for any particular day
EDIT
If you want to proceed with NSDate as key for your dictionary then you'll have to modify above code as follows. dateFrom will return date with year,month and date of your choice, and time will be some constant value. Time will be set to midnight in your current time zone if you don't set it.
func dateFrom(year:Int, month:Int, day:Int) -> NSDate {
let components = NSDateComponents()
components.year = year
components.month = month
components.day = day
let gregorian = NSCalendar(identifier:NSCalendarIdentifierGregorian)
let date = gregorian!.dateFromComponents(components)
return date!
}
And for getting current date use following so that you store date object with current year, date, month and time to some constant value.
func getCurrentDate()->NSDate {
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Day , .Month , .Year], fromDate: date)
return dateFrom(components.year, month: components.month, day: components.day)
}
Usage will be as follows
dict[getCurrentDate()] = i //for insertion or updation
let numOfTasks = dict[dateFrom(2016, month: 1, day: 15)] //to get task for any particular day

Using NSDate calculations to determine previous and next pay period for bi-weekly pay frequency

I'm trying to calculate the next and previous pay days for a user, when given a specified date.
I'm storing two properties for the user like so:
public var firstPayDay: NSDate {
get { return (NSDate(dateNumber: self["firstPayDay"] as! NSNumber)) }
set(date) {
self["firstPayDay"] = date.dateNumber()
self.payDayOfWeek = self.firstPayDay.dayOfWeek(zeroBased: true)!
// day of week from 0 - 6
}
}
When first introduced to the app, the user is asked to provide their next pay day. This is stored as an integer, e.g. 20160126 on the user object, and the following convenience is used to convert it to NSDate:
convenience init(dateNumber: NSNumber) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyyMMdd"
self.init(timeInterval:0, sinceDate: dateFormatter.dateFromString(dateNumber.stringValue)!)
}
public var payDayOfWeek: Int {
get { return (self["payDayOfWeek"] as! Int) }
set(dayOfWeek) { self["payDayOfWeek"] = dayOfWeek }
}
When firstPayDay is set, payDayOfWeek is updated using the 0-based (Sunday ⇢ Saturday) index of the weekday.
I can use this method just fine to get the next and previous pay days for weekly pay periods like this, but I'm struggling how to do this for bi-weekly pay periods?
To determine the previous and next bi-weekly pay days, I would need to calculate in increments of two weeks from the first pay day, then determine where the specified day fits in between the two. How would I do that in code with the two known properties firstPayDay and payDayOfWeek?
Expected output:
user.firstPayDay = NSDate(fromNumber: 20160101) // January 1st, 2016
print(user.payDayOfWeek) // 5 (Friday, inferred from firstPayDay setter)
// For Today, 20160126
print(user.nextPayDayForDate(20160126)) // should return 20160129, the closest Friday an even two weeks from 20160101 and in the future
print(user.prevPayDayForDate(20160126)) // should return 20160115, the closest Friday an even two weeks from 20160101 and in the past
// For Saturday, 20160130
print(user.nextPayDayForDate(20160130)) // should return 20160212, the closest Friday an even two weeks from 20160101 and in the future
print(user.prevPayDayFordate(20160130)) // should return 20160129, the closest Friday an even two weeks from 20160101 and in the past
Work in Progress
internal func nextWeekBasedPayDate(payFrequency: PayFrequency, firstPayDay: NSDate) -> NSDate? {
let interval: Int = payFrequency == .Weekly ? 7 : 14
let calendar: NSCalendar = NSCalendar.autoupdatingCurrentCalendar()
guard
let date: NSDate = calendar.dateByAddingUnit(.Day, value: interval, toDate: self.previousWeekBasedPayDate(payFrequency, firstPayDay), options: [])! else { return nil }
return date.at12pm()
}
internal func previousWeekBasedPayDate(payFrequency: PayFrequency, firstPayDay: NSDate) -> NSDate? {
let interval: Int = payFrequency == .Weekly ? 7 : 14
let calendar: NSCalendar = NSCalendar.autoupdatingCurrentCalendar()
guard
let daysSinceFirstPayDate: Int = calendar.components([ .Day ], fromDate: firstPayDay, toDate: self, options: []).day,
let daysSincePreviousPayDate: Int = daysSinceFirstPayDate % interval + (daysSinceFirstPayDate < 0 ? interval : 0),
let date: NSDate = calendar.dateByAddingUnit(.Day, value: -daysSincePreviousPayDate, toDate: self, options: [])! else { return nil }
return date.at12pm()
}
internal func at12pm() -> NSDate? {
let cal = NSCalendar.currentCalendar()
return cal.dateBySettingHour(12, minute: 0, second: 0, ofDate: self, options: [])
}
Then to calculate a pay period:
func currentPayPeriod(payFrequency: PayFrequency, firstPayDay: NSDate) -> [NSDate]? {
var startDate: NSDate
var endDate: NSDate
switch payFrequency {
case .Monthly:
startDate = self.startOfMonth()!
endDate = self.endOfMonth()!
case .Weekly, .BiWeekly:
startDate = (self.previousPayDay(payFrequency, firstPayDay: firstPayDay))!
endDate = (self.nextPayDay(payFrequency, firstPayDay: firstPayDay)?.addDays(-1))!
case .SemiMonthly:
startDate = self.startOfMonth()!
endDate = self.middleOfMonth()!
}
return [startDate, endDate]
}
This seems to work perfectly:
/*
payPeriod [NSDate] 2 values
[0] __NSTaggedDate * 2016-01-24 20:00:00 UTC 0xe41bc5564c000000
[1] __NSTaggedDate * 2016-01-30 20:00:00 UTC 0xe41bc5d4dc000000
*/
I don't understand why you'd store the date as a number instead of a string, since you can't do math on it and you just have to convert it to a string to parse it anyway. So I'm just going to use string dates in this answer.
Also, converting a date to an NSDate without considering the time within the day is dangerous, because it tends to produce wrong answers for some days in some time zones. So let's tell the parser to use noon as the time within the day:
extension NSDate {
class func withYMDString(string: String) -> NSDate {
let dateFormatter = NSDateFormatter()
let defaultComponents = NSDateComponents()
defaultComponents.hour = 12
defaultComponents.minute = 0
defaultComponents.second = 0
dateFormatter.defaultDate = dateFormatter.calendar.dateFromComponents(defaultComponents)
dateFormatter.dateFormat = "yyyyMMdd"
return dateFormatter.dateFromString(string)!
}
}
Now let's consider how to find the prior and next pay dates. We have a reference pay date:
let referencePayDate = NSDate.withYMDString("20160101")
And we have some date of interest:
let someDate = NSDate.withYMDString("20151231")
For the purpose of finding the nearest pay dates to someDate, it doesn't really matter what day of week the pay dates fall on. A pay date comes every two weeks, which is every fourteen days. So we want to find those dates which are a multiple of fourteen days from referencePayDate, and which are nearest to someDate. We start by computing the number of days from referencePayDate to someDate:
let calendar = NSCalendar.autoupdatingCurrentCalendar()
let daysSinceReferencePayDate = calendar.components([ .Day ],
fromDate: referencePayDate, toDate: someDate, options: []).day
Then we round it down to the nearest multiple of 14, toward -∞:
let daysSincePriorPayDate = daysSinceReferencePayDate % 14
+ (daysSinceReferencePayDate < 0 ? 14 : 0)
(Note that we need to adjust for a negative numerator because of the way Swift computes the remainder.)
From that we can compute the prior pay date:
let priorPayDate = calendar.dateByAddingUnit(.Day, value: -daysSincePriorPayDate,
toDate: someDate, options: [])!
// Result: Dec 18, 2015, 12:00 PM
The next pay date is 14 days later:
let nextPayDate = calendar.dateByAddingUnit(.Day, value: 14,
toDate: priorPayDate, options: [])!
// Result: Jan 1, 2016, 12:00 PM

Pretty Date Intervals in Swift. NSCalendar components

I'm trying to print a prettier version of a timeIntervalSinceDate e.g '10 days, 6 hours...'. But i'm failing to get NSCalendar components to work correctly.
I get an "Extra argument 'toDate' in call" error with the following:
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EE, dd MMM y hh:mm a zzz"
var dateString = "Wed, 08 Jun 2011 11:58 pm EDT"
var date: NSDate = dateFormatter.dateFromString(dateString)!
var now = NSDate()
let calendar: NSCalendar = NSCalendar()
let components = calendar.components(unitFlags: .CalendarUnitDay,
fromDate: date,
toDate: now,
options: 0)
components.day
The 'toDate' feature is documented, so I'm not sure why I get the error?
Any help appreciated.
There are two errors:
The first parameter in a method does not have an external parameter name,
so you must not specify unitFlags:.
For "no options", specify NSCalendarOptions(0) or simply nil.
Together:
let components = calendar.components(.CalendarUnitDay,
fromDate: date, toDate: now, options: nil)
Update for Swift 2: NS_OPTIONS are now imported as conforming
to OptionSetType, and "no options" can be specified as the empty set:
let components = calendar.components(.Day,
fromDate: date, toDate: now, options: [])

Resources