NSCalendar components returning different values - ios

I have 2 dates that I would like to compare. A date in the future, and the current date. When I compare them I want to retrieve their difference in terms of hours, minutes, and seconds. I believe the code I have to do this is correct, however, I get different values returned depending on the components I request.
I am using the following calendar for all examples:
let calendar = NSCalendar.currentCalendar()
A:
let dateComponents = calendar.components([NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Second, NSCalendarUnit.Minute], fromDate: NSDate(), toDate: date, options: [])
B:
let dateComponents2 = calendar.components(.Hour, fromDate: NSDate(), toDate: date, options: [])
So, with the above example, dateComponents.hour does not equal dateComponents2.hour, and dateComponents2.hour returns the correct value.
Even more interesting is the following case:
A:
let dateComponents = calendar.components([NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Second, NSCalendarUnit.Minute], fromDate: NSDate(), toDate: date, options: [])
B:
let dateComponents2 = calendar.components(.Minute, fromDate: NSDate(), toDate: date, options: [])
Now, dateComponents.minute does not equal dateComponents2.minute, but now dateComponents.minute returns the correct value.
Why could this be happening? Does the fact that I am requesting multiple components at the same time affect the return value?
EDIT
Here are some examples using the following dates. The return values are shown as hours, minutes, seconds:
2015-11-29 10:59:00 +0000
2015-11-28 07:57:20 +0000
Using dateComponents:
3, 1, 39
Using dateComponents2 (one for each component):
27, 1621, 97299
The hours value is correct from dateComponents2, and the minutes and seconds values are correct from dateComponents.

Let's imagine that the two NSDate objects are 61 seconds apart, if you get both minute and second at the same time, you get 1 minute and 1 second, but if you just ask for just seconds, you get 61 seconds:
let date1 = NSDate()
let date2 = date1.dateByAddingTimeInterval(61)
let calendar = NSCalendar.currentCalendar()
let components1 = calendar.components([.Minute, .Second], fromDate: date1, toDate: date2, options: [])
let components2 = calendar.components([.Second], fromDate: date1, toDate: date2, options: [])
print("\(components1.minute) minute and \(components1.second) second")
print("\(components2.second) seconds")
That yields:
1 minute and 1 second
61 seconds
Using your dates (and adding days and hours to the calculations):
let dateString1 = "2015-11-28 07:57:20 +0000"
let dateString2 = "2015-11-29 10:59:00 +0000"
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let date1 = formatter.dateFromString(dateString1)!
let date2 = formatter.dateFromString(dateString2)!
let calendar = NSCalendar.currentCalendar()
let components1 = calendar.components([.Day, .Hour, .Minute, .Second], fromDate: date1, toDate: date2, options: [])
let components2 = calendar.components([.Second], fromDate: date1, toDate: date2, options: [])
print("\(components1.day) day, \(components1.hour) hours, \(components1.minute) minute, and \(components1.second) seconds")
print("\(components2.second) seconds")
That yields:
1 day, 3 hours, 1 minute, and 40 seconds
97300 seconds
And if you multiply that out, you'll see that 1 day, 3 hours, 1 minute and 40 seconds is equal to 97300 seconds.
You can also consider using NSDateComponentsFormatter to create a nicely formatted (and localized) string representation of the time elapsed:
let componentsFormatter = NSDateComponentsFormatter()
componentsFormatter.allowedUnits = [.Day, .Hour, .Minute, .Second]
componentsFormatter.unitsStyle = .Full
print(componentsFormatter.stringFromDate(date1, toDate: date2))
// or just show the top two units (because if you're showing days/hours, you probably no longer care about minutes/seconds
componentsFormatter.maximumUnitCount = 2
print(componentsFormatter.stringFromDate(date1, toDate: date2))
// but if you want actual total number of seconds, then change `allowedUnits` to use only that
componentsFormatter.allowedUnits = [.Second]
print(componentsFormatter.stringFromDate(date1, toDate: date2))
That will display three optional strings:
1 day, 3 hours, 1 minute, 40 seconds
1 day, 3 hours
97,300 seconds

Related

dateComponents returns wrong number of days between two dates

I have two dates. startDate and endDate which are one day apart. From the print function I get:
startDate: 2023-01-01 05:07:33 +0000
endDate: 2023-01-01 17:08:04 +0000
Of course this is UTC so the day is inaccurate but if I get the Calendar.Component .day for each date then I get 1 and 2 respectively which is what I expect.
I am trying to calculate the number of days between these two dates using:
Calendar.current.dateComponents([.day], from: startDate, to: endDate).day
However this is returning 0. What am I don't wrong here? From what I understand since this function is using the Calendar then it should ignore the UTC format and return 1.
The function you use calculates the difference in full days between the two dates and not if they are on different days
This can be easily verified with the below example
let endDate = Date.now
let firstDate = Calendar.current.date(byAdding: .hour, value: -23, to: endDate)!
print(Calendar.current.dateComponents([.day], from: firstDate, to: endDate).day!)
let secondDate = Calendar.current.date(byAdding: .hour, value: -24, to: endDate)!
print(Calendar.current.dateComponents([.day], from: secondDate, to: endDate).day!)
which prints
0
1
The same goes for an hour where -59 minutes returns 0 and -60 returns 1

How to calculate days difference between two NSDate objects in different time zones?

I have two NSDate which are initiated from UTC dates.
Lets call them (A & B)
I know that the A represents a day in China and B represents a day in USA. (I know the time zones.) How can I calculate the difference in days between the two...?
I have been using the following method which is obviously incorrect.
class func daysDifferenceIn(firstDate: NSDate, firstTimeZone: String, secondDate: NSDate, secondTimeZone: String) -> Int {
objc_sync_enter(self)
let firstDateComponents = NSCalendar.CommonCalendar.componentsInTimeZone(NSTimeZone(name: firstTimeZone)!, fromDate: firstDate)
let secondDateComponents = NSCalendar.CommonCalendar.componentsInTimeZone(NSTimeZone(name: secondTimeZone)!, fromDate: secondDate)
NSCalendar.CommonCalendar.timeZone = NSTimeZone(name: firstTimeZone)!
let firstCalDate = NSCalendar.CommonCalendar.dateFromComponents(firstDateComponents)
NSCalendar.CommonCalendar.timeZone = NSTimeZone(name: secondTimeZone)!
let secondCalDate = NSCalendar.CommonCalendar.dateFromComponents(secondDateComponents)
objc_sync_exit(self)
return firstCalDate!.numberOfDaysUntilDateTime(secondCalDate!)
}
func numberOfDaysUntilDateTime(toDateTime: NSDate, inTimeZone timeZone: NSTimeZone? = nil) -> Int {
let calendar = NSCalendar.CommonCalendar
if let timeZone = timeZone {
calendar.timeZone = timeZone
}
var fromDate: NSDate?, toDate: NSDate?
calendar.rangeOfUnit(.Day, startDate: &fromDate, interval: nil, forDate: self)
calendar.rangeOfUnit(.Day, startDate: &toDate, interval: nil, forDate: toDateTime)
let difference = calendar.components(.Day, fromDate: fromDate!, toDate: toDate!, options: [])
return difference.day
}
I can manually subtract day components from firstDateComponents and secondDateComponents which I don't want to do as I have to look for edge cases of 31 and 28 and so on.
Any help is appreciated. Thanks.
UPDATE
First date is 2017-02-10 16:15:00 +0000
Second date is 2017-02-11 03:20:00 +0000 Both are UTC.
firstTimeZone String "Asia/Shanghai"
secondTimeZone String "America/Los_Angeles"
So the day difference is -1 Day. Basically I am implementing flight status and you can see the following link as the flight lands 1 day prior to take day. As it flies from West to East.
https://www.google.co.in/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=ua+890
Also a description of Date components.
Printing description of firstDateComponents:
<NSDateComponents: 0x600000142310>
Calendar: <CFCalendar 0x60000088b6d0 [0x10c5d3df0]>{identifier = 'gregorian'}
TimeZone: Asia/Shanghai (GMT+8) offset 28800
Era: 1
Calendar Year: 2017
Month: 2
Leap month: no
Day: 11
Hour: 0
Minute: 15
Second: 0
Nanosecond: 0
Quarter: 0
Year for Week of Year: 2017
Week of Year: 6
Week of Month: 2
Weekday: 7
Weekday Ordinal: 2
Printing description of secondDateComponents:
<NSDateComponents: 0x60000014b8f0>
Calendar: <CFCalendar 0x60000049b620 [0x10c5d3df0]>{identifier = 'gregorian'}
TimeZone: America/Los_Angeles (PST) offset -28800
Era: 1
Calendar Year: 2017
Month: 2
Leap month: no
Day: 10
Hour: 19
Minute: 20
Second: 0
Nanosecond: 0
Quarter: 0
Year for Week of Year: 2017
Week of Year: 6
Week of Month: 2
Weekday: 6
Weekday Ordinal: 2
This is an odd case. You're looking for the difference in calendar dates between two Dates when those dates are evaluated in a specific time zone.
I did some playing, and came up with code that works for dates that fall in the same year:
let date = Date()
guard let nycTimeZone = TimeZone(abbreviation: "EST"),
let nzTimeZone = TimeZone(abbreviation: "NZDT") else {
fatalError()
}
var nycCalendar = Calendar(identifier: .gregorian)
nycCalendar.timeZone = nycTimeZone
var nzCalendar = Calendar(identifier: .gregorian)
nzCalendar.timeZone = nzTimeZone
let now = Date()
let nycDayOfYear = nycCalendar.ordinality(of: .day, in: .year, for: now)
var nzDayOfYear = nzCalendar.ordinality(of: .day, in: .year, for: now)
I'm using New York and Aukland, NZ as my time zones because as of the time of this writing, those zones are on different dates.
As of now (~12:00 PM on Feb 11, 2017 in US Eastern Standard Time (UTC - 5) the code above gives
nycDayOfYear = 42
and
nzDayOfYear = 43
It would take some work to make that calculation work across year boundaries.
Curiously, the following code:
var nzDayOfEra = nzCalendar.ordinality(of: .day, in: .era, for: now)
let nycDayOfEra = nycCalendar.ordinality(of: .day, in: .era, for: now)
Gives the same value for both NZ and NYC. I'm not sure why.
EDIT:
Ok, I did some experimenting and got code that works. What I do is to convert both dates to month/day/year date components using a calendar set to the local time zone for each time. Then I use a method dateComponents(_:from:to:) to calculate the difference between those 2 DateComponents, in days:
import UIKit
let date = Date()
guard let nycTimeZone = TimeZone(abbreviation: "EST"),
let nzTimeZone = TimeZone(abbreviation: "NZDT") else {
fatalError()
}
var nycCalendar = Calendar(identifier: .gregorian)
nycCalendar.timeZone = nycTimeZone
var nzCalendar = Calendar(identifier: .gregorian)
nzCalendar.timeZone = nzTimeZone
let now = Date()
let nycDateComponents = nycCalendar.dateComponents([.month, .day, .year], from: now)
let nzDateComponents = nzCalendar.dateComponents([.month, .day, .year], from: now)
let difference = Calendar.current.dateComponents([.day],
from: nycDateComponents,
to: nzDateComponents)
let daysDifference = difference.days
As of this writing that gives a daysDifference of 1. Since we're using the dateComponents(_:from:to:) function, it takes care of the math to calculate the number of days difference between the 2 month/day/year DateComponents.
A NSDate represents a moment in time. It has no time zone. Only string representations of dates have time zone information.
If you have the dates, just take the number of days between them. Don't worry about time zones.
Usually:
let difference = calendar.components(.Day, fromDate: self, toDate: toDate!, options: [])
return difference.day
should be enough.
Have you tried using let interval = laterDate.timeIntervalSinceDate(earlierDate)? This will return the difference between the two dates in seconds.

Get number of days between two NSDates when crossing new year

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)

how to get NSDate of a specific next day and time

I have some events for which I need to calculate NSDates.
For example I'm trying to get the next Monday at 8:00 AM.
So I tried some stuff but nothing works:
1.
let nextMonday = NSCalendar.currentCalendar().dateBySettingUnit(NSCalendarUnit.Weekday, value: 2, ofDate: startDate, options: NSCalendarOptions.MatchNextTime)
let nextMondayEight = NSCalendar.currentCalendar().dateBySettingUnit(NSCalendarUnit.Hour, value: 8, ofDate: nextMonday!, options: NSCalendarOptions.MatchNextTime)
I get:
2016-04-12 05:00:00 +0000
That's Tuesday at 8:00 (the time difference is my local time GMT -3).
2.
let unitFlags: NSCalendarUnit = [.Day, .Month, .Year]
let comp = NSCalendar.currentCalendar().components(unitFlags, fromDate: NSDate())
comp.timeZone = NSTimeZone.localTimeZone()
comp.weekday = 1
comp.hour = 8
comp.minute = 0
comp.second = 0
let compDate = NSCalendar.currentCalendar().dateFromComponents(comp)
print("time: \(compDate!)")
I get:
2016-04-11 05:00:00 +0000
That's today at 8:00 and not next Monday at 8:00.
Any suggestions?
Thanks
NSCalendar has a method nextDateAfterDate:matchingComponents:options for this kind of date math.
let calendar = NSCalendar.currentCalendar()
let components = NSDateComponents()
components.hour = 8 // 8:00
components.weekday = 2 // Monday in Gregorian Calendar
let nextMondayEightOClock = calendar.nextDateAfterDate(NSDate(), matchingComponents: components, options: .MatchStrictly)

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