Count Days between Dates Swift (Considering what may be tomorrow.) - swift

I have this code:
extension Date {
/// From today to self
/// - Returns: number of days.
func getDaysUntil() -> Int? {
return Calendar.current.dateComponents([.day], from: Date(), to: self).day
}
}
I need everything to be considered in terms of EST. So regardless of which time zone we are in, everything should be regarded as EST.
I am having a problem when comparing today with tomorrow.
So take the following case:
Date() // (now):: June 15.
self (compare to):: June 16.
Regardless the time of day today, I want to say that it's 1 day until the comparison date (self).
So, if it's 11:59pm and the comparison date (self) is 12:01am, I want the result to be 1 day. If it's 12:01am and the comparison date (self) is 11:59pm, I still want the comparison result to be 1.
As it is, no matter what I do, when we get to those extreme edges, the date calculation is off by 1, one way or the other.
Also, this isn't just a today vs. tomorrow issue. The correct count should be the result (considering the today/tomorrow conundrum) for any date.
Is there some fancy trick, or does anyone have any suggestion how we could accomplish this? Thanks!

If you need a default "don't care" time calendrical calculation what you need is to use noon time. Check WWDC session 227 Solutions to Common Date and Time Challenges. Check the "Midnight" part and what is said about why people should NOT use midnight.
extension Calendar {
static let iso8601 = Calendar(identifier: .iso8601)
}
extension Date {
/// From today's noon to self's noon
/// - Returns: number of days.
var daysFromToday: Int {
Calendar.iso8601.dateComponents([.day], from: Date().noon, to: noon).day!
}
/// Noon time
/// - Returns: same date at noon
var noon: Date {
Calendar.iso8601.date(bySettingHour: 12, minute: 0, second: 0, of: self)!
}
}
Usage:
let yesterday = DateComponents(calendar: .iso8601, year: 2022, month: 5, day: 2, hour: 23, minute: 59, second: 59).date!
yesterday.daysFromToday // -1

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 - 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

actual date plus 14 days

i working with swift 3 for osx.
i have this calendar:
this calendar should show the actual date plus 14 days.
Generally it is correct, because 30 - 16 = 14 days between.
this is my code for this:
datePicker.dateValue = Calendar.current.date(byAdding: .day, value: 14, to: Date())!
but at the bottom of my calendar should be visible the days between now and the selected date. but here will be shown 13 days.
why 13 and not 14?
this text label will calculate like this:
txtDiffDays.stringValue = "(\(Calendar.current.dateComponents([.day], from: Date(), to: datePicker.dateValue).day!) Tage)"
where is my mistake?
The problem is that you are calling Date() twice.
When calculating the difference the from date must be the exact same date used when setting the datePicker. Otherwise it's slightly later and the amount of days is less than 14.
Here are some experiments that I did in the playground:
let now = Date()
let date = Calendar.current.date(byAdding: .day, value: 14, to: Date())!
let nowDay = Calendar.current.dateComponents([.day], from: Date()).day
let dateDay = Calendar.current.dateComponents([.day], from: date).day
date.timeIntervalSince1970 - Date().timeIntervalSince1970
date.timeIntervalSince1970 - now.timeIntervalSince1970
let days = Calendar.current.dateComponents([.day], from: Date().addingTimeInterval(-1), to: date).day!
let days2 = Calendar.current.dateComponents([.day], from: now, to: date).day!
Base on the result, I think what happens is that some time has passed between "setting the date picker's value" and "calculating the date difference". During both of these actions you did Date(). Since some time has passed, the date that he second Date() creates is a tiny little bit later than the one created by the first Date() call, as you can see from line 8 and 9 in the playground.
Since the second date is a little later, that makes the date difference a little less than 14 whole days, so dateComponents method rounds it down and you get 13.
To fix this, avoid doing two Date() calls. You can just create a local variable called now and use it throughout the method.
let now = Date()
Alternatively, you can use Date().addingTimeInterval(-1) like I did in playground line 10, but I feel like this is a cheat more than a solution.

nvd3 (d3.js) date format returns incorrect month

My data looks like this:
[{ x="2013-06-01", y=3}, { x="2013-07-01", y=7 }, { x="2013-08-01", y=3 }]
Chart x-axis is formatted as so:
chart.xAxis
.axisLabel('Date')
.tickFormat(function(d) { return d3.time.format('%b %Y')(new Date(d)); })
;
%b returns May, Jun, July respectively for the dates 2013-06-01, 2013-07-01, 2013-08-01
Why is it returning the previous month, and how can I fix it?
EDIT: If the date is formatted as 2013-06-02, it will return the correct month... does someone know what is happening to cause this?
#Amelia is correct it's because of timezone difference and because Date defaults to 24:00:00 if you don't specify a time. So, in case of EDT, which is -4:00, you lose 4 hours which puts you in the previous day (May 31 2013 20:00:00) and because the days in your dates are 01, this puts you in the previous month.
To bypass this you could append a time to your date if that is allowable in your case.
chart.xAxis
.axisLabel('Date')
.tickFormat(function(d) {
d = d.split('-')
// Create new date by using new Date(year, month, day, hour, second, ms)
// Subtracting 1 is necessary since Javascript months are 0 - 11.
return d3.time.format('%b %Y')(new Date(d[0], +d[1] - 1, d[2], 12, 0, 0));
});
Here is a working Fiddle