How can I get the exact difference (in decimal) between 2 values of NSDate.
Eg. Jan 15 2016 to Jul 15 2017 = 1.5 Years.
I can use something like:
NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitYear, fromDate: date1, toDate: date1, options: nil).year
but this gives me absolute values. i.e. for above example it would give me 1 Year. Is it possible to get exact values correct to at least a few decimal places?
The terms you've used here are misleading. When you say "absolute" you mean "integral." And when you say "exact" you mean "within some desired precision."
Let's say the precision you wanted was 2 decimal places, so we'd need to measure a year to 1%. That's larger than a day, so tracking days is sufficient. If you needed more precision, then you could expand this technique, but if you push it too far, "year" gets more tricky, and you have to start asking what you mean by "a year."
Avoid asking this question when you can. Many answers here say things like "there are 365.25 days in a year." But try adding "365.25 * 24 hours" to "right now" and see if you get "the same date and time next year." While it may seem correct "on average," it is actually wrong 100% of the time for calendar dates. (It works out here because it's within 1%, but so would 365, 366, or even 363.)
We avoid this madness by saying "1% is close enough for this problem."
// What calendar do you *really* mean here? The user's current calendar,
// or the Gregorian calendar? The below code should work for any calendar,
// because every calendar's year is made up of some number of days, but it's
// worth considering if you really mean (and are testing) arbitrary calendars.
// If you mean "Gregorian," then use NSCalendar(identifier: NSCalendarIdentifierGregorian)!
let calendar = NSCalendar.currentCalendar()
// Determine how many integral days are between the dates
let diff = calendar.components(.Day, fromDate: date1, toDate: date2, options: [])
// Determine how many days are in a year. If you really meant "Gregorian" above, and
// so used calendarWithIdentifer rather than currentCalendar, you can estimate 365 here.
// Being within one day is inside the noise floor of 1%.
// Yes, this is harder than you'd think. This is based on MartinR's code: http://stackoverflow.com/a/16812482/97337
var startOfYear: NSDate? = nil
var lengthOfYear = NSTimeInterval(0)
calendar.rangeOfUnit(.Year, startDate: &startOfYear, interval: &lengthOfYear, forDate: date1)
let endOfYear = startOfYear!.dateByAddingTimeInterval(lengthOfYear)
let daysInYear = calendar.components(.Day, fromDate: startOfYear!, toDate: endOfYear, options: []).day
// Divide
let fracDiff = Double(diff.day) / Double(daysInYear)
That said, in most cases you shouldn't be doing this. Since iOS 8, the preferred tool is NSDateComponentsFormatter. You won't get this precise format (i.e. fractional years), but you'll get a nicely localized result that takes most issues into account across different cultures.
let formatter = NSDateComponentsFormatter()
formatter.unitsStyle = .Full
formatter.includesApproximationPhrase = true
formatter.allowedUnits = [.Year, .Month]
formatter.allowsFractionalUnits = true
formatter.stringFromDate(date1, toDate: date2)
// About 1 year, 6 months
Since you mentioned that your goal is something you can display to users as a meaningful indication of the time between two dates, you might find it easier to use NSDateComponentsFormatter. For example:
let dateStr1 = "Jan 15 2016"
let dateStr2 = "Jul 15 2017"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMM dd yyyy"
if let date1 = dateFormatter.dateFromString(dateStr1),
let date2 = dateFormatter.dateFromString(dateStr2) {
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [.Year, .Month]
dateComponentsFormatter.unitsStyle = .Full
let difference = dateComponentsFormatter.stringFromDate(date1, toDate: date2)
}
This gives you a string that reads "1 year, 6 months". It's not exactly what you specified as your goal, but it's a clear indication for users and avoids a lot of complexity. There's a property on NSDateComponentsFormatter called allowsFractionalUnits that's supposed to lead to results like "1.5 years", but it doesn't seem to work right now. (Even if you limit the allowedUnits to only .Year, you still don't get a fractional year. I'm off to file a bug with Apple...). You can tweak allowedUnits to get whatever granularity you like, and use includesApproximationPhrase to have the class add a localized version of "About..." to the resulting string if it's not precise. If you have some flexibility in your final format, this would be a really good solution.
There isn't a perfect answer to this question. Different years are slightly different lengths. You have to make some assumptions.
If you assume 365.2425 days per year, with each day having 24 hours, then the calculation is trivial:
let secondsPerYear: NSTimeInterval = NSTimeInterval(365.2425 * 24 * 60 * 60)
let secondsBetweenDates =
date2.timeIntervalSinceReferenceDate - date1.timeIntervalSinceReferenceDate;
let yearsBetweenDates = secondsBetweenDates / secondPerYear
But there are lots of edge cases and weirdness to deal with. Because of leap years, some years have 365 days, and some have 366. Then there's leap seconds.
If you get rid of months in #CodeDifferent's answer then you'll get an answer that allows for leap days between the dates.
But, as Code Different pointed out, his answer as written actually gives answers that seem more accurate, even though they are not. (A difference of 3 months will always yield .25 years, and will ignore longer/shorter months. Is that the right thing to do? Depends on your goal and your assumptions.)
According to NASA, there are 365.2422 days per year on average. Here, I round that up to 365.25 days per year:
let components = NSCalendar.currentCalendar().components([.Year, .Month, .Day], fromDate: fromDate, toDate: toDate, options: [])
var totalYears = Double(components.year)
totalYears += Double(components.month) / 12.0
totalYears += Double(components.day) / 365.25
Obviously, this depends on your assumptions. If you want to count of leap days between fromDate and toDate, it will be more complicated.
Some sample outputs:
From date To date Total Years
------------ ------------ ------------
Jan 15, 2016 Jul 15, 2017 1.5
Jan 15, 2016 Apr 14, 2016 0.25
Jan 15, 2016 Aug 15, 2017 1.5833
Jan 15, 2016 Jan 14, 2018 1.9988
Related
I now that there is a thread that discussing about minimum and maximum date time in here Minimum and maximum date in UIDatePicker
but I am a beginner now in programming, and I have little bit confused with date and time object. I have tried but I can't set up my date time picker properly. So I want to set my date time picker to have minimum and maximum limited time.
the minimum is Now, and the maximum is 1 month from now. what code do I have to use? Thanks in advance
https://i.stack.imgur.com/RxXUX.png
You can try
self.datePicker.minimumDate = Date()
self.datePicker.maximumDate = Calendar.current.date(byAdding: .day, value: 30 , to:Date())!
or
self.datePicker.maximumDate = Calendar.current.date(byAdding: .month, value: 1 , to:Date())!
I recommend to use the date math skills of Calendar. It calculates the same day in the next month. If the day does not exist (for example Feb 30) it returns the next closest available date.
let now = Date()
let calendar = Calendar.current
let dayComponents = calendar.dateComponents([.day], from: now)
let maximumDate = calendar.nextDate(after: now, matching: dayComponents, matchingPolicy: .nextTime)
To get a Date exactly one month in the future from now (taking into account different month lengths), you can use Date.date(byAdding:value:to:), e.g. as in this answer. You'd take the result and set it as maximumDate of your picker.
I'm using DateComponentsFormatter to achieve something like that: "1 hour, 30 min". This is my code for that:
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .short
formatter.allowedUnits = [.hour, .minute]
return formatter.string(from: 90)!
This should return 1 hour and 30 minutes but my output is "1 min". Is something wrong with my code or is it iOS bug?
The DataComponentsFormatter string(from: TimeInterval) method that you are using expects the time interval in seconds.
So you are asking the formatter to format 90 seconds, not 90 minutes.
This will solve your issue:
return formatter.string(from: 90 * 60)! // 90 minutes
When ever in doubt about such things, read the documentation. The documentation shows the following for the parameter description:
The time interval, measured in seconds. The value must be a finite number. Negative numbers are treated as positive numbers when creating the string.
I would like to get the time interval from the present date and time until a certain date in the future. All I could manage to do is get the interval but with a minus in front of all, because i used this:
let elapsedTime = NSDate().timeIntervalSinceDate(GeneralUtils.dateFromString(endDate))
let formatter = NSDateComponentsFormatter()
formatter.unitsStyle = .Abbreviated
let countdown = formatter.stringFromTimeInterval(elapsedTime)
timerLabel.text! = "\(countdown!)"
How could I fix it to show me the positive interval?
Now it shows me something like this "-1d 3h 23m 10s"
Just use abs() so the time interval is always positive. (Just me being insane)
Of course Russell points out you could just swap the dates around...
let elapsedTime = GeneralUtils.dateFromString(endDate).timeIntervalSinceNow
By default, the date picker shows 1 - January - 2014.
Can user pick as
1 - 1 - 2014 ?
How? Thanks!
You can't change the date picker to that format. Apple provides you the following datepicker modes, each of them has its own format:
UIDatePickerModeTime
The date picker displays hours, minutes, and (optionally) an AM/PM designation. The exact items shown and their order depend upon the locale set. An example of this mode is [ 6 | 53 | PM ].
UIDatePickerModeDate
The date picker displays months, days of the month, and years. The exact order of
these items depends on the locale setting. An example of this mode is
[ November | 15 | 2007 ].
UIDatePickerModeDateAndTime
The date picker displays dates (as unified day of the week, month, and day of
the month values) plus hours, minutes, and (optionally) an AM/PM
designation. The exact order and format of these items depends on the
locale set. An example of this mode is [ Wed Nov 15 | 6 | 53 | PM ].
UIDatePickerModeCountDownTimer
The date picker displays hour and minute values, for example [ 1 | 53 ].
The application must set a timer to fire at the proper interval and
set the date picker as the seconds tick down.
If you want any other format, you must implement your own datePicker.
#Luis is right, if you want the month in numbers you can get. below code will not change how UIDatePicker shows month. but will change your selected month in number. and you can pick as 1 - 1 - 2014.
NSDateFormatter *date = [[NSDateFormatter alloc]init];
[date setDateFormat:#"dd-M-YYYY"];
YourDatepicker.datePickerMode = UIDatePickerModeDate;
NSString *date1 = [date stringFromDate:YourDatepicker.date];
date1 will give you the 1 - 1 - 2014.
func datePickerValueChanged(sender:UIDatePicker) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MMM-yyyy"
let dateString = dateFormatter.stringFromDate(sender.date)
departureDateTextField.text = dateString
}
Background:
When formatting an NSTimeInterval using an NSDateComponentsFormatter, it's often handy to use the allowedUnits property to round the resulting string to a relevant time unit.
For example, I may not be interested in getting time to the second, so setting the allowedUnits to minutes gets an appropriately rounded output string:
let duration: NSTimeInterval = 3665.0 // 1 hour, 1 minute and 5 seconds
let durationFormatter = NSDateComponentsFormatter()
durationFormatter.unitsStyle = .Full
durationFormatter.zeroFormattingBehavior = .DropAll
durationFormatter.allowedUnits = .Minute
let formattedDuration = durationFormatter.stringFromTimeInterval(duration)
formattedDuration will be "61 Minutes".
The problem:
How would you go about setting allowedUnits to .Hour and .Minute so the formatted duration would be "1 hour, 1 minute" instead?
The NSDateComponentFormatter Class Reference doesn't really cover how to do this.
As suggested by Leo, you can simply provide the allowedUnits in an array like so:
durationFormatter.allowedUnits = [.Hour, .Minute]
The solution I've come up with is to create a NSCalendarUnit by adding the raw values of the desired allowed units together:
durationFormatter.allowedUnits = NSCalendarUnit(rawValue: NSCalendarUnit.Hour.rawValue + NSCalendarUnit.Minute.rawValue)
I't be interested to know if there's a better syntax or alternative way to do this.