Get the difference between two dates in calendar days - swift

can anyone explain how can I get difference between two dates in calendar days, not in whole 24-hour periods. There is a good solution here: Getting the difference between two NSDates in (months/days/hours/minutes/seconds) -- but it doesn't work for me as, for example, it gives the difference between 23:00 today and 1:30 tomorrow as 0 days despite of calendar dates differ already by 1.

Use the normal way to calculate the difference in days with one change - convert both of your dates to midnight.
let d1 = ... // your first date
let d2 = ... // your second date
let cal = Calendar.current
let days = cal.dateComponents([.day], from: cal.startOfDay(for: d1), to: cal.startOfDay(for: d2)).day!
This will give an answer of 1 for "yesterday at 23:00" and "today at 1:30", for example.

Related

Swift - How to create a date object containing just the time

I trying to create a date object just containing the time of 1 second past midnight.
I believe the following should work but it just keeps returning nil.
let dateTime = Date()
let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm:ss"
let time = timeFormatter.date(from: "00:00:01")
print("Time: \(time!)")
Can someone tell me what i'm doing wrong!
Thanks
Let Calendar do the math, this is more reliable, you aren't using the current date (dateTime) anyway.
let midnight = Calendar.current.startOfDay(for: Date())
let oneSecondAfterMidnight = Calendar.current.date(byAdding: .second, value: 1, to: midnight)
This works even if midnight doesn't exist due to daylight saving change.
Date is not a "date" in any meaningful way. It's a specific point in time, independent of any calendar or location. What you want to express is a point on a calendar: "one second" past an arbitrary calendar point we call "midnight." That's done with DateComponents.
var dc = DateComponents()
dc.hour = 0
dc.minute = 0
dc.second = 1
This is the second second of the first minute of the first hour (00:00:01) of an arbitrary day on an arbitrary calendar, which is what you've described.
More precisely, it's "zero hours, zero minutes, and one second," which is only "one second after midnight" if you add it to some "midnight." But beyond that, there is no independent "time" type. Those things only have meaning when applied to a Calendar.
(Keep in mind that due to DST change in some parts of world, such as Iran, there are sometimes two midnights in the same day. So when you ask for this kind of thing, you need to be very clear what you mean. Do you want every second after midnight or just the first one on a given day?)

Is there an elegant way of determining if two dates are separated by a week of year in Swift?

If I have two dates, and I want to know if one of them falls on a week of year (as defined here) just prior to the other, how can I figure this out in Swift?
Assuming I don't care about time, one approach could be:
let calendar = Calendar.current
let startOfDate1 = calendar.startOfDay(for: date1)
let startOfDate2 = calendar.startOfDay(for: date2)
let date1WeekOfYear = calendar.dateComponents([.weekOfYear], from: startOfDate1).weekOfYear!
let date2WeekOfYear = calendar.dateComponents([.weekOfYear], from: startOfDate2).weekOfYear!
if (date1WeekOfYear - date2WeekOfYear) == 1 {
// do some stuff
}
This works except in the case where date1 falls within the first week of the year, and date2 falls within the last week of the prior year. Do I really have to also add in other logic to check the situation where the year components are different, and account for varying lengths of years in weeks (most have 52 weeks, some have 53), or is there a more elegant way to handle this?
Please note that I'm not interested in checking if the two dates are within 7 days of each other. It's possible that the two days are within 2 days of each other, but fall within different weeks of the year.
Thanks in advance.
Here's a way I found:
You do need to get the year, but Calendar can still do the calculation for you. You just need to call a different overload of the method from the one in Rob Napier's answer. You need the overload that accepts DateComponents:
let calendar = Calendar.current
let start = Date(timeIntervalSince1970: 1577375330) // 2019-12-26
let end = Date(timeIntervalSince1970: 1577893730) // 2020-01-01
// remember it's yearForWeekOfYear, not just "year"
let startDateComponents = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: start)
let endDateComponents = calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: end)
let interval = calendar.dateComponents([.weekOfYear], from: startDateComponents, to: endDateComponents).weekOfYear!
print(interval) // 1
My speculation of why this works but the overload taking Dates doesn't:
The overload that takes Dates will first get the difference between the two dates, which represent instants, and then convert that time interval to the specified set of DateComponents. Note that it's probably converting a TimeInterval to DateComponents, which is why it can't calculate week boundaries and such.
The overload that takes DateComponents can calculate week boundaries because that information is given as its parameters.
You can add 7 days to one date (allowing for year roll-over), and determine the difference in week-numbers between the two dates to be zero weeks rather then one week.
Have a look at the 2019 end of year
> var cal = Calendar.current
> let df = DateFormatter(); df.dateFormat = "yyyy-MM-dd"
> var d1 = df.date(from: "2019-12-28")!;
> var w1=cal.component(.weekOfYear, from:d1)
w1: Int = 52
This tells us Dec 28 still fell in week 52.
> var w2=cal.component(.weekOfYear, from: df.date(from: "2019-12-29")!)
w2: Int = 1
And this tells us Dec 29 fell in week 1 of 2020. These two dates are "1 week apart", as humans can easily tell. Determining this by calculation can be a bit harder, even if you use modulo arithmetic: the wrap-around is irregular, at 52 for some years, and 53 for others, as the OP hinted at. (2006, 2012, 2017 and 2023 are all 53-week years)
To determine that they are 1 week apart by calculation, first move up the earlier date by 7 days, i.e. 2019-12-28 plus 7 days:
> var d1_7 = cal.date(byAdding: .day, value:7, to:d1)!;
d1_7: Date = 2020-01-04 08:00:00 UTC
> var w1_7 = cal.component(.weekOfYear,from:d1_7)
w1_7: Int = 1
Its week number is 1, equalling that of 2019-12-29. We conclude that 2019-12-28 and 2019-12-29 are 1 week apart.

Swift: How should I subtract 5 minutes from Datecomponents?

I am working on an app that notifies the user 5 minutes before the departure time. I'm using firebase as my database.
I've tried searching answers from the internet but the solutions are not working.
So here's the code in getting the departure hour and minute:
let hour = dict["DepartureHour"] as! NSNumber
let minute = dict ["DepartureMinute"] as! NSNumber
Putting it in a DateComponent:
var dateComponents = DateComponents()
dateComponents.hour = hour.intValue
dateComponents.minute = minute.intValue
Now I don't know what to do next, like how should I subtract 5 minutes from it. Please help me.
You cannot do this based on the "departure hour" and "departure minute" alone. The only way to get a date "5 minutes before the departure time" is to start with an actual date-time representing the moment of departure. That requires that you know all calendar info: year, month, day, hour, minutes, seconds. When you know that, finding the date-time five minutes before it is trivial.

Swift - Get number of years between two dates in double format similar to YEARFRAC function is MS Excel [duplicate]

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

Picking a date with swift UI automation

In the app I'm testing there is a date picker I'm trying to automate. The wheel defaults to tomorrow and I'm attempting to change it to today's date but 2 minutes from now. Below is the code I'm using to attempt this.
app.pickerWheels.element(boundBy: 0).adjust(toPickerWheelValue: "Today")
app.pickerWheels.element(boundBy: 1).adjust(toPickerWheelValue: "1")
app.pickerWheels.element(boundBy: 2).adjust(toPickerWheelValue: "00")
(In the actual code I'm using variables and not hard coding these string)
This code works for the second and third wheel (hours and minutes) but for the first wheel it won't set the value. The test will fail and not continue past that point.
I have also tried passing today's date instead of just "Today" with the same results.
You can use the DateFormatter class with Date to accomplish this.
// Initialize the date formatter. Set the timeZone and format. I chose hours and minutes.
let dateFormatter: DateFormatter = DateFormatter()
dateFormatter.timeZone = NSTimeZone.local
dateFormatter.dateFormat = "HH:mm"
// Initialize the Date instance using a time interval since now.
let d: Date = Date(timeIntervalSinceNow: 2 * 60)
print("Current Time = \(dateFormatter.string(from: Date())), Two-Minutes-From-Now = \(dateFormatter.string(from: d))")
Output: Current Time = 23:57, Two-Minutes-From-Now = 23:59
A Date is stored as a time interval since January 1st, 1970. You can manipulate the date by adding or subtracting seconds from it. Here, I added 2 * 60 or two 60-second minutes to the current time interval (a large value represented in a double). This points to two minutes in the future.
Now, if you print the date without the formatter, it will just display the current time with no regard to your time zone. So if you want it to be accurate to your time zone, you need to set that in the formatter first. Note that it doesn't change the time, just its representation to you.