Calculate time period in multiple units in Swift - swift

I want to calculate the diff between two dates (for example '11 oct 2015' and '23 dec 2015') in multiple units in Swift. For these dates the result i want to achieve should be something like '2 months, 11 days'
In java using joda-time library i can make it with the following code:
PeriodType pt = PeriodType.standard()
.withYearsRemoved()
.withWeeksRemoved()
.withHoursRemoved()
.withMinutesRemoved()
.withSecondsRemoved()
.withMillisRemoved();
Period per = new Period(date1, date2, pt);
int months = per.getMonths()
int days = per.getDays()
How can i get the same result in Swift?

The answer is on this page: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/DatesAndTimes/Articles/dtCalendricalCalculations.html#//apple_ref/doc/uid/TP40007836-SW8
In Swift:
func monthsDaysBetweenStartDate(startDate: NSDate, endDate: NSDate) -> (months: Int, days: Int)? {
let gregorian = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
let unitFlags: NSCalendarUnit = [NSCalendarUnit.Month, NSCalendarUnit.Day]
if let components = gregorian?.components(unitFlags, fromDate: startDate, toDate: endDate, options: NSCalendarOptions()) {
return (months: components.month, days: components.day)
}
return nil
}

Related

Find difference between just the time of two dates in seconds

I have 2 dates. I don't care about the date portion, just the time.
How can I compare 2 dates and get the timeinterval between 2 dates?
Should I set the dates to 01-01-2000 and leave the time alone to compare?
Use DateComponents and get the hour, minute, and second of the two dates. At this point you have to assume a full 24 hour, 86400 seconds per day. There's no need to worry about daylight saving or leap seconds or anything since you are doing date independent calculations.
Convert the hours, minutes, and seconds into total seconds of the day for the two dates. Then simply subtract the two totals and you have the difference.
Here's a helpful Date extension:
extension Date {
func secondsSinceMidnight() -> TimeInterval {
let comps = Calendar.current.dateComponents([.hour,.minute,.second], from: self)
return TimeInterval(comps.hour! * 3600 + comps.minute! * 60 + comps.second!)
}
func timeDifference(to date: Date) -> TimeInterval {
return date.secondsSinceMidnight() - self.secondsSinceMidnight()
}
}
Call timeDifference(to:) using your two dates and you will get the difference in seconds ignoring the date portion of the dates.
A negative result means that the to date is closer to midnight.
This is an alternative to rmaddy's solution completely based on DateComponents
extension Date {
func timeComponents() -> DateComponents {
return Calendar.current.dateComponents([.hour,.minute,.second], from: self)
}
func timeDifference(to date: Date) -> Int {
return Calendar.current.dateComponents([.second], from: date.timeComponents(), to: self.timeComponents()).second!
}
}
If you have two dates you can use the method timeIntervalSince(Date).
For instance:
func calculateElapsedTime(from someTime: Date) -> TimeInterval {
let currentTime = Date()
var elapsedTime = currentTime.timeIntervalSince(someTime)
return elapsedTime
}
If you only want to consider the time difference between the two dates, you first have to normalize the date. This can be done in the following cumbersome way:
let currentDate = Date()
let anotherDate = Date(timeInterval: 60, since: currentDate)
let formatter = DateFormatter()
formatter.timeStyle = .short
let currentTime = formatter.string(from: currentDate)
let anotherTime = formatter.string(from: anotherDate)
let currentIntervalTime = formatter.date(from: currentTime)
let anotherIntervalTime = formatter.date(from: anotherTime)
let elapsedTime = anotherIntervalTime?.timeIntervalSince(currentIntervalTime!)

Get unavailable date in a datepicker (Swift)

I have a UIDatePicker which is available for days from today to the next week ( from Date() to Date().day + 7 ).
Suppose that today is 30th day of the month, so the available days to choose are 30th day of this month and first to 6th day of next month.
In this case, I need to change the datePicker month to next month as the user change day from 30 to 1.
But the problem is:
"When I change the day to an invalid one, the .change method give me the least valid day."
Example:
Assumptions:
Today = 28 July.
Choosable dates = 28 July to 3 August.
What I need:
User can not choose the days before 28 July and not after 3 August.
When user wants to change day to 1, 2 or 3 August, the datePicker itself change the month to August and make 1, 2 and 3 available to
be chosen by user.
Main Problem
When user change the day to 1, 2 or 3, I can not get the 1, 2 or 3! and it returns me "28"!
Just set maximumDate property of your UIDatePicker to desired date.
func setupDatePicker(){
var currDateComponents = getDateComponents(fromDate: Date())
var maxDateComponents = currDateComponents
if let date = currDateComponents?.day{
maxDateComponents?.day = date + 7
}
self.datePicker?.maximumDate = getDate(fromDateComponents: maxDateComponents)
}
func getDateComponents(fromDate date: Date?)-> DateComponents?{
let calendar = Calendar(identifier: .gregorian)
var dateComponents: DateComponents? = nil
if(date != nil){
dateComponents = calendar.dateComponents([.day, .month, .year], from: date!)
}
return dateComponents
}
func getDate(fromDateComponents dateComponents:DateComponents?)-> Date?{
let calendar = Calendar(identifier: .gregorian)
if let componentsBasedDate = calendar.date(from: dateComponents!) {
return componentsBasedDate
}
return nil
}
Call this function in viewDidLoad to setup calendar for setting maximum and minimum date:
fileprivate func setupCalendar() {
datePicker.minimumDate = Calendar.current.date(byAdding: .day, value: 0, to: ServerTime.sharedInstance.nowTime)
datePicker.maximumDate = Calendar.current.date(byAdding: .day, value: +7, to: ServerTime.sharedInstance.nowTime)
}
for calculate days between shown date and current date I've used this method:
extension Date {
//Calculate days between two day objects
func daysBetween(_ date: Date) -> Int {
let calendar = NSCalendar.current
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: date)
let date2 = calendar.startOfDay(for: self)
let components = calendar.dateComponents([.day], from: date1, to: date2)
return components.day ?? 0 // This will return the number of day(s) between dates
}
}}
In fact, it is impossible to get date before they chose. And according to unavailability of days in next month before changing the month, I can not use UIDatePicker to handle it.
So Finally I implemented my custom UIPickerView.
as #holex said.
Thank you all for answering.

NSDate: Getting values for Tomorrow or Yesterday

I have a piece of code that basically acts in 2 parts:
Part 1: The user sets a Date with a UIDatePicker. For example, the user selects 1 day ahead from the current date. So the selected new date is 5/19/16 instead of 5/18/16.
Part 1 code
let dateFormatter = NSDateFormatter()
dateFormatter.locale = NSLocale.currentLocale()
var dateString = "May-19-2016"
dateFormatter.dateFormat = "MMMM-dd-yyyy"
var due_date = dateFormatter.dateFromString(dateString)!
Part 2: I have created code that counts how many days are left from the selected date to the current date. In this example, somehow my code is saying its 0 days before tomorrow. Here is the code for the second part:
Second Part:
func computeDates(dueDate:NSDate)-> Int {
let currentDate = NSDate()
// Adding days to currentDate
let daysToAdd = 1
// Adding on Unit to the current instance
let calculateDate = NSCalendar.currentCalendar().dateByAddingUnit(NSCalendarUnit.Day, value: daysToAdd, toDate: currentDate, options: NSCalendarOptions.init(rawValue: 0))
// Figure out many days from may 3rd
let cal = NSCalendar.currentCalendar()
let unit = NSCalendarUnit.Day
let components = cal.components(unit, fromDate: currentDate, toDate: dueDate, options: [])
let countLeft = components.day
return countLeft
}
print("Days left: \(computeDates(due_date)) ")
// Tests
let calc_date = computeDates(due_date)
if calc_date <= -1 {
print("Yesterday")
} else if calc_date == 0 {
print("Today")
} else if calc_date > 1 {
print("Tomorrow")
}
In the part 1 example, I used a static date which I use to test this code. In this part, I set May 19, 2016, one day ahead. In the second part below in the if statement, It somehow says I have 0 days left and from what i am trying to do, it's suppose to say 1 day left before tomorrow the 19th.
Another example, If i change the 19th to the 20th, I want it to say "In 2 days" from now.
Now if I revert the day to lets say, the 15th of may (in the past), Then the if statement would say something like Overdue or the past.
How can I solve this?
It would help if you thought of NSDate as a structure that operates with the number of seconds from 2001. It means if you pick a "date", it contains "date and time". All you need to do to calculate the correct number of days between specific "dates" is to truncate a time component.
But if you only need to check whether the provided date is Yesterday, Today, or Tomorrow, NSCalendar has methods for this purpose:
Objective-C:
- (BOOL)isDateInToday:(NSDate *)date NS_AVAILABLE(10_9, 8_0);
- (BOOL)isDateInYesterday:(NSDate *)date NS_AVAILABLE(10_9, 8_0);
- (BOOL)isDateInTomorrow:(NSDate *)date NS_AVAILABLE(10_9, 8_0);
And Swift:
#available(OSX 10.9, *)
public func isDateInYesterday(date: NSDate) -> Bool
#available(OSX 10.9, *)
public func isDateInTomorrow(date: NSDate) -> Bool
#available(OSX 10.9, *)
public func isDateInWeekend(date: NSDate) -> Bool

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: [])!
}

Number to Date - Swift

Getting the day of year is straightforward, e.g.
func dayOfYear(inputDate:NSDate) -> (Int) {
let cal = NSCalendar.currentCalendar()
let returnDay = cal.ordinalityOfUnit(.CalendarUnitDay, inUnit: .CalendarUnitYear, forDate: inputDate)
return returnDay
}
But how do you do the reverse? It would obviously return just the day/month. I can easily write a tedious routine back-calculating but is there a smart way?
Yes, NSCalendar provides a way to coalesce calendar components into a single date object. Take a look at this example I wrote in a Playground:
import UIKit
import Foundation
let inputDate: NSDate = NSDate()
let calendar = NSCalendar.currentCalendar()
let day = calendar.ordinalityOfUnit(.CalendarUnitDay, inUnit: .CalendarUnitYear, forDate: inputDate)
let components = NSDateComponents()
components.day = day
let date = calendar.dateFromComponents(components)
According to the documentation,
When there are insufficient components provided to completely specify an absolute time, a calendar uses default values of its choice. When there is inconsistent information, a calendar may ignore some of the components parameters or the method may return nil. Unnecessary components are ignored (for example, Day takes precedence over Weekday and Weekday ordinals).
Furthermore,
Note that some computations can take a relatively long time to perform.
See the NSCalendar Class Reference for more information.
func dayOfYear(inputDate: NSDate) -> Int {
return NSCalendar.currentCalendar().ordinalityOfUnit(.CalendarUnitDay, inUnit: .CalendarUnitYear, forDate: inputDate)
}
func dateFromDayOfYear(day: Int) -> NSDate {
return NSCalendar.currentCalendar().dateWithEra(1, year: NSCalendar.currentCalendar().component(.CalendarUnitYear, fromDate: NSDate()), month: 1, day: day, hour: 0, minute: 0, second: 0, nanosecond: 0)!
}
let numberOfDays = dayOfYear(NSDate()) // 195
dateFromDayOfYear(numberOfDays) // "Jul 14, 2015, 12:00 AM"