NSDate day of the year (swift) - ios

How might the day number of the year be found with swift? Is there a simple way that I'm not seeing, or do I have to find the number of seconds from Jan 1 to the current date and divide by the number of seconds in a day?

This is a translation of the answer to How do you calculate the day of the year for a specific date in Objective-C? to Swift.
Swift 2:
let date = NSDate() // now
let cal = NSCalendar.currentCalendar()
let day = cal.ordinalityOfUnit(.Day, inUnit: .Year, forDate: date)
print(day)
Swift 3:
let date = Date() // now
let cal = Calendar.current
let day = cal.ordinality(of: .day, in: .year, for: date)
print(day)
This gives 1 for the first day in the year, and 56 = 31 + 25 for today (Feb 25).
... or do I have to find the number of seconds from Jan 1 to the current date
and divide by the number of seconds in a day
This would be a wrong approach, because a day does not have a fixed
number of seconds (transition from or to Daylight Saving Time).

Swift 3
extension Date {
var dayOfYear: Int {
return Calendar.current.ordinality(of: .day, in: .year, for: self)!
}
}
use like
Date().dayOfYear

Not at all !!! All you have to do is to use NSCalendar to help you do your calendar calculations as follow:
let firstDayOfTheYear = NSCalendar.currentCalendar().dateWithEra(1, year: NSCalendar.currentCalendar().component(.CalendarUnitYear, fromDate: NSDate()), month: 1, day: 1, hour: 0, minute: 0, second: 0, nanosecond: 0)! // "Jan 1, 2015, 12:00 AM"
let daysFromJanFirst = NSCalendar.currentCalendar().components(.CalendarUnitDay, fromDate: firstDayOfTheYear, toDate: NSDate(), options: nil).day // 55
let secondsFromJanFirst = NSCalendar.currentCalendar().components(.CalendarUnitSecond, fromDate: firstDayOfTheYear, toDate: NSDate(), options: nil).second // 4,770,357

You can find the number of days since your date like this:
let date = NSDate() // your date
let days = cal.ordinalityOfUnit(.CalendarUnitDay, inUnit: .CalendarUnitYear, forDate: date)
println(days)

Related

What is reliable way, to calculate day differences without taking time into consideration?

May I know, what is reliable way, to calculate day differences without taking time into consideration?
A similar question is asked before. However, the highest voted and accepted answer isn't entirely accurate - https://stackoverflow.com/a/28163560/72437
The code is broken, when dealing with Day light saving case. You can run the following code in Playground
Use startOfDay (Broken)
import UIKit
struct LocalDate: Equatable {
let year: Int
let month: Int
let day: Int
}
struct LocalTime: Equatable, Codable {
let hour: Int
let minute: Int
}
extension Date {
var startOfDay: Date {
return Calendar.current.startOfDay(for: self)
}
static func of(localDate: LocalDate, localTime: LocalTime) -> Date {
var dateComponents = DateComponents()
dateComponents.year = localDate.year
dateComponents.month = localDate.month
dateComponents.day = localDate.day
dateComponents.hour = localTime.hour
dateComponents.minute = localTime.minute
dateComponents.second = 0
return Calendar.current.date(from: dateComponents)!
}
func adding(_ component: Calendar.Component, _ value: Int) -> Date {
return Calendar.current.date(byAdding: component, value: value, to: self)!
}
}
// During 22 March 2021, Tehran will advance by 1 hour from 00:00 AM, to 01:00 AM.
let tehranTimeZone = TimeZone(identifier: "Asia/Tehran")!
let oldDefault = NSTimeZone.default
NSTimeZone.default = tehranTimeZone
defer {
NSTimeZone.default = oldDefault
}
// Just a random local time. We will use 'startOfDay' to perform local time resetting.
let localTime = LocalTime(hour: 2, minute: 59)
let localDate1 = LocalDate(year: 2021, month: 3, day: 22)
let localDate2 = LocalDate(year: 2021, month: 3, day: 23)
let date1 = Date.of(localDate: localDate1, localTime: localTime).startOfDay
let date2 = Date.of(localDate: localDate2, localTime: localTime).startOfDay
let components = Calendar.current.dateComponents([.day], from: date1, to: date2)
/*
date1 Monday, March 22, 2021 at 1:00:00 AM Iran Daylight Time
date2 Tuesday, March 23, 2021 at 12:00:00 AM Iran Daylight Time
diff in day is Optional(0)
*/
print("date1 \(date1.description(with: .current))")
print("date2 \(date2.description(with: .current))")
print("diff in day is \(components.day)")
The different of day should be 1, without taking time into consideration. However, due to day light saving, the computed hour difference is 23 hours instead of 24 hours.
We are then getting 0 day difference.
One of the workaround, is using 12:00 (noon) as local time, with an assumption there is no place in this world, where day light saving occurs during 12:00. I am not sure how solid is this assumption. Such assumption seems to be pretty fragile. What if one day government decides to admen day light saving to be at 12:00?
Use 12:00 (Seems to work. But, how solid it is?)
import UIKit
struct LocalDate: Equatable {
let year: Int
let month: Int
let day: Int
}
struct LocalTime: Equatable, Codable {
let hour: Int
let minute: Int
}
extension Date {
var startOfDay: Date {
return Calendar.current.startOfDay(for: self)
}
static func of(localDate: LocalDate, localTime: LocalTime) -> Date {
var dateComponents = DateComponents()
dateComponents.year = localDate.year
dateComponents.month = localDate.month
dateComponents.day = localDate.day
dateComponents.hour = localTime.hour
dateComponents.minute = localTime.minute
dateComponents.second = 0
return Calendar.current.date(from: dateComponents)!
}
func adding(_ component: Calendar.Component, _ value: Int) -> Date {
return Calendar.current.date(byAdding: component, value: value, to: self)!
}
}
// During 22 March 2021, Tehran will advance by 1 hour from 00:00 AM, to 01:00 AM.
let tehranTimeZone = TimeZone(identifier: "Asia/Tehran")!
let oldDefault = NSTimeZone.default
NSTimeZone.default = tehranTimeZone
defer {
NSTimeZone.default = oldDefault
}
// Use noon
let localTime = LocalTime(hour: 12, minute: 00)
let localDate1 = LocalDate(year: 2021, month: 3, day: 22)
let localDate2 = LocalDate(year: 2021, month: 3, day: 23)
let date1 = Date.of(localDate: localDate1, localTime: localTime)
let date2 = Date.of(localDate: localDate2, localTime: localTime)
let components = Calendar.current.dateComponents([.day], from: date1, to: date2)
/*
date1 Monday, March 22, 2021 at 12:00:00 PM Iran Daylight Time
date2 Tuesday, March 23, 2021 at 12:00:00 PM Iran Daylight Time
diff in day is Optional(1)
*/
print("date1 \(date1.description(with: .current))")
print("date2 \(date2.description(with: .current))")
print("diff in day is \(components.day)")
May I know, what is reliable way, to calculate day differences without taking time into consideration?
Date is a precise point in time, hence expressible as a TimeInterval (aka Double) from an exact moment in time (that'll be reference date aka January 1st 2001 00:00 GMT+0).
Thus that same point in time is differently calculated between TimeZones through Calendar: if the TimeZone has daylight savings, then the calendar take it into account.
Therefore when you operate through a Calendar adopting DateComponents you should keep that in mind.
Depending on what you are trying to do in your application it could be useful to just adopt a private Calendar instance set to adopt TimeZone(secondsFromGMT: 0)! for calculating dates as absolutes values.
As in:
extension Calendar {
static let appCal: Self = {
// I'm used to reason with Gregorian calendar
var cal = Calendar(identifier: .gregorian)
// I just need this calendar for executing absolute time calculations
cal.timeZone = TimeZone(secondsFromGMT: 0)!
return cal
}()
}

Find if the current date is between a weekly range

I have the following range:
Weekday Hour:Minute --> Weekday Hour:Minute
Weekday is an integer from 1 (Monday) to 7 (Sunday)
For example, a given range can be the following:
Monday 8:00 --> Friday 18:00
Saturday 10:00 --> Tuesday 10:00
Monday 10:00 --> Monday 20:00 (Just 10 hours on Monday)
Monday 20:00 --> Monday 10:00 (All week except Monday from 10:00 to 20:00)
I'm trying to find if the current date is in the selected range.
I tried multiple ways like creating NSDates from the ranges and comparing them but it still didn't pass all the tests.
You could do something like this:
First create the start and end dates from hour, minute and weekday. If start date results after end date go back to 1 week. Then compare the current date to start and end dates calculated to see if it is in range.
let currentDate = Date()
var startDate = dateBySetting(hour: 8, minute: 0, weekday: 1, of: currentDate)
let endDate = dateBySetting(hour: 9, minute: 0, weekday: 5, of: currentDate)
// If start date results after end date remove a week from the start
if startDate > endDate {
startDate = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: startDate) ?? startDate
}
let dateIsInRange = startDate <= currentDate && currentDate <= endDate
func dateBySetting(hour: Int, minute: Int, weekday: Int, of date: Date) -> Date {
let calendar = Calendar.current
var date = calendar.date(bySettingHour: hour, minute: minute, second: 0, of: date) ?? date
date = calendar.date(bySetting: .weekday, value: weekday, of: date) ?? date
return date
}
It can be done using Calendar's nextDate function.
Eg: here I am checking if my current time falls in between Monday 8:00 --> Friday 18:00
//Monday 8:00 --> Friday 18:00
let currentDate = Date()
let calendar = Calendar.current
let yearComponent = (calendar.dateComponents(in: .current, from: Date()).year)!
let monthComponent = (calendar.dateComponents(in: .current, from: Date()).month)!
let previousTime = calendar.nextDate(after: currentDate,
matching: DateComponents(calendar: calendar, timeZone: .current, year: yearComponent, month: monthComponent, hour: 8, minute: 0, weekday: 2),
matchingPolicy: .strict,
repeatedTimePolicy: .first,
direction: .backward)
print(previousTime)
let nextTime = calendar.nextDate(after: previousTime!,
matching: DateComponents(calendar: calendar, timeZone: .current, year: yearComponent, month: monthComponent, hour: 18, minute: 0, weekday: 6),
matchingPolicy: .strict,
repeatedTimePolicy: .first,
direction: .forward)
print(nextTime)
print(currentDate > previousTime! && currentDate < nextTime!) //true
A solution is to use date components. Your last example Monday 20:00 --> Monday 10:00 is
let startComponents = DateComponents(hour:20, minute:0, weekday:1)
let endComponents = DateComponents(hour:10, minute:0, weekday:1)
Then get the next occurrence of the first components from the current date backward and the second forward
let now = Date()
let startDate = Calendar.current.nextDate(after: now, matching: startComponents, matchingPolicy: .nextTime, direction: .backward)!
let endDate = Calendar.current.nextDate(after: startDate , matching: endComponents, matchingPolicy: .nextTime)!
and create a DateInterval and check if the current date is in the range
let isInRange = DateInterval(start: startDate, end: endDate).contains(now)
extension Date {
func isSameWeek(as date: Date) -> Bool {
let dateComponents = Calendar.current.dateComponents([.day, .weekOfYear, .year], from: date)
let currentDateComponents = Calendar.current.dateComponents([.day, .weekOfYear, .year], from: self)
return dateComponents.year == currentDateComponents.year && dateComponents.weekOfYear == currentDateComponents.weekOfYear
}
}
you can try this extension:-
extension Date{
func timeAgoDisplay() -> String {
let secondsAgo = Int(Date().timeIntervalSince(self))
let minute = 60
let hour = minute * 60
let day = hour * 24
let week = day * 7
let month = week * 4
if secondsAgo < 60 {
return "\(secondsAgo) seconds ago"
}else if secondsAgo < hour {
return "\(secondsAgo/minute) minutes ago"
}else if secondsAgo < day {
return "\(secondsAgo/hour) hours ago"
}else if secondsAgo < week {
return "\(secondsAgo/day) days ago"
}else if secondsAgo < month {
return "\(secondsAgo/week) weeks ago"
}else {
return "\(secondsAgo/month) month(s) ago"
}
}
}

How to find out start and end day of the previous week

To find the start and end for the current week I managed to do this:
extension NSDate {
static func week() -> (NSDate, NSDate) {
let calendar = NSCalendar.currentCalendar()
calendar.firstWeekday = 2
var interval = NSTimeInterval(0)
var startOfWeek: NSDate?
var endOfWeek: NSDate!
calendar.rangeOfUnit(.WeekOfMonth, startDate: &startOfWeek, interval: &interval, forDate: NSDate())
endOfWeek = startOfWeek?.dateByAddingTimeInterval(interval - 1)
return (startOfWeek!, endOfWeek)
}
}
How can I update this code to get the start/end of the previous week ?
// Getting first day of the previous week
func getPreviousWeekStartDay() -> Date? {
let gregorian = Calendar(identifier: .gregorian)
let sunday = gregorian.date(from:
gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
return gregorian.date(byAdding: .day, value: -7, to: sunday!)!
}
// Getting last day of previous week
func getPreviousWeekEndDay() -> Date? {
let gregorian = Calendar(identifier: .gregorian)
let sunday = gregorian.date(from: gregorian.dateComponents([.yearForWeekOfYear, .weekOfYear], from: self))
return gregorian.date(byAdding: .day, value: -1, to: sunday!)!
}
You can start counting from a week ago:
let weekInSeconds: Double = 60 * 60 * 24 * 7
calendar.rangeOfUnit(.WeekOfMonth, startDate: &startOfWeek, interval: &interval, forDate: NSDate(timeIntervalSinceNow: -weekInSeconds))
You can now control which week (from now) you want to see, in the past of future, by using multiples of weekInSeconds (positive for future weeks, negative for past weeks, 0 for this week).

Calculate duration between date ios in Years, months and date format

Im new at swift programming and i havent been able successfully find code to find difference between two dates in terms of years , months and days.
I tried the following code but it didnt work
let form = NSDateComponentsFormatter()
form.maximumUnitCount = 2
form.unitsStyle = .Full
let s = form.stringFromTimeInterval( date2.timeIntervalSinceReferenceDate - date1.timeIntervalSinceReferenceDate)
Input
Date1 = "12/March/2015"
Date2 = "1/June/2015"
Output : x years y months z days
Please advice
We can use this function in Swift 2.0
func yearsBetweenDate(startDate: NSDate, endDate: NSDate) -> Int
{
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Year], fromDate: startDate, toDate: endDate, options: [])
return components.year
}
You can return anything like I returned year in this function. This will return number of years between the two dates.
You can just write months,days etc in order to find the difference between the two dates in months and days respectively.
Edit
Swift 3.0 and Above
func yearsBetweenDate(startDate: Date, endDate: Date) -> Int {
let calendar = Calendar.current
let components = calendar.dateComponents([.year], from: startDate, to: endDate)
return components.year!
}
If you need the difference (in years, months, days) numerically then
compute NSDateComponents as in Swift days between two NSDates or Rajan's answer.
If you need the difference as a (localized) string to present it to the user,
then use NSDateComponentsFormatter like this
let form = NSDateComponentsFormatter()
form.maximumUnitCount = 2
form.unitsStyle = .Full
form.allowedUnits = [.Year, .Month, .Day]
let s = form.stringFromDate(date1, toDate: date2)
As already mentioned in the comments, computing the difference
from the pure time interval between the dates cannot give correct
results because most information about the dates is lost.
Update for Swift 3:
let form = DateComponentsFormatter()
form.maximumUnitCount = 2
form.unitsStyle = .full
form.allowedUnits = [.year, .month, .day]
let s = form.string(from: date1, to: date2)
With Swift 5 and iOS 12, you can use one of the 3 solutions below in order to calculate the difference (in years, months, days) between two dates.
#1. Using Calendar's dateComponents(_:from:to:) method
Calendar has a method called dateComponents(_:from:to:). dateComponents(_:from:to:) has the following declaration:
func dateComponents(_ components: Set<Calendar.Component>, from start: DateComponents, to end: DateComponents) -> DateComponents
Returns the difference between two dates specified as DateComponents.
The Playground example below show how to use dateComponents(_:from:to:) in order to compute the difference between two dates:
import Foundation
let calendar = Calendar.current
let startComponents = DateComponents(year: 2010, month: 11, day: 22)
let endComponents = DateComponents(year: 2015, month: 5, day: 1)
let dateComponents = calendar.dateComponents([.year, .month, .day], from: startComponents, to: endComponents)
print(dateComponents) // prints: year: 4 month: 5 day: 9 isLeapMonth: false
#2. Using Calendar's dateComponents(_:from:to:) method
Calendar has a method called dateComponents(_:from:to:). dateComponents(_:from:to:) has the following declaration:
func dateComponents(_ components: Set<Calendar.Component>, from start: Date, to end: Date) -> DateComponents
Returns the difference between two dates.
The Playground example below show how to use dateComponents(_:from:to:) in order to compute the difference between two dates:
import Foundation
let calendar = Calendar.current
let startDate = calendar.date(from: DateComponents(year: 2010, month: 11, day: 22))!
let endDate = calendar.date(from: DateComponents(year: 2015, month: 5, day: 1))!
let dateComponents = calendar.dateComponents([.year, .month, .day], from: startDate, to: endDate)
print(dateComponents) // prints: year: 4 month: 5 day: 9 isLeapMonth: false
#3. Using DateComponentsFormatter's string(from:to:) method
DateComponentsFormatter has a method called string(from:to:). string(from:to:) has the following declaration:
func string(from startDate: Date, to endDate: Date) -> String?
Returns a formatted string based on the time difference between two dates.
The Playground example below show how to use string(from:to:) in order to compute the difference between two dates:
import Foundation
let calendar = Calendar.current
let startDate = calendar.date(from: DateComponents(year: 2010, month: 11, day: 22))!
let endDate = calendar.date(from: DateComponents(year: 2015, month: 5, day: 1))!
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.year, .month, .day]
let string = formatter.string(from: startDate, to: endDate)!
print(string) // prints: 4 years, 5 months, 9 days
Try this one
func calculateDiffInTwoDate (date1: NSDate, date2: NSDate) -> NSInteger {
//var userAge : NSInteger = 0
let calendar : NSCalendar = NSCalendar.currentCalendar()
let unitFlags : NSCalendarUnit = [ .Year , .Month, .Day]
let dateComponentNow : NSDateComponents = calendar.components(unitFlags, fromDate: date2)
let dateComponentBirth : NSDateComponents = calendar.components(unitFlags, fromDate: date1)
if ( (dateComponentNow.month < dateComponentBirth.month) ||
((dateComponentNow.month == dateComponentBirth.month) && (dateComponentNow.day < dateComponentBirth.day))
)
{
return dateComponentNow.year - dateComponentBirth.year - 1
}
else {
return dateComponentNow.year - dateComponentBirth.year
}
}
By This you can get diff between two dates in Years
Why don't you use the inbuild method to find the difference between 2 dates in seconds, and then write a method to convert seconds in terms of years, months and days.
let diff = date1.timeIntervalSinceDate(date2)
//Assigning Dates
let StartDate = datePicker.date
let currentDate = Date()
//Finding Difference of Dates
let components = Set<Calendar.Component>([.day, .month, .year])
let differenceOfDate = Calendar.current.dateComponents(components, from:
StartDate, to: currentDate)
Print(differenceOfDate)

Days between 2 NSDates in a calendar year swift

I'm building a birthday reminder app. I want the user to be able to see how many days it is until somebody's next birthday.
Let's say I have an NSDate() = 2015-06-30 07:21:47 +0000
And the NSDate for the birthday = 1985-08-29 12:00:00 +0000
How would I get the number of days until the next birthday? I've used something like this which gives a negative number of days since the date's actual beginning (which are in the thousands with birthdates). Then with that I would add 365 to the numerical difference until it was positive but it still returns a wonky number. I'm assuming due to leap years or something.
Is there a method to this I can implement? Somehow equalize the year components so that it's always comparing from the next birthday and not the original birthdate?
Edit:
Here is the function I am using:
func daysBetween(date1: NSDate, date2: NSDate) -> Int {
var calendar: NSCalendar = NSCalendar.currentCalendar()
let date1 = calendar.startOfDayForDate(date1)
let date2 = calendar.startOfDayForDate(date2)
let flags = NSCalendarUnit.CalendarUnitDay
let components = calendar.components(flags, fromDate: date1, toDate: date2, options: nil)
return components.day
}
With the example dates I posted I would use it like this:
// sampleDate = 1985-08-29 12:00:00 +0000
daysBetween(sampleDate, date2: NSDate())
// --> 10897
Whether positive or negative, the number is in the thousands. I'm looking to equalize that into number of days to the next calendar birthday.
What you need is to compute the next occurrence of the (day and month
component of the) birthday after today:
let cal = NSCalendar.currentCalendar()
let today = cal.startOfDayForDate(NSDate())
let dayAndMonth = cal.components(.CalendarUnitDay | .CalendarUnitMonth,
fromDate: birthday)
let nextBirthDay = cal.nextDateAfterDate(today,
matchingComponents: dayAndMonth,
options: .MatchNextTimePreservingSmallerUnits)!
Remarks:
The purpose of the MatchNextTimePreservingSmallerUnits option is
that if the birthday is on February 29 (in the Gregorian calendar), its next occurrence will be
computed as March 1 if the year is not a leap year.
You might need to check first if the birthday is today, as it seems
that nextDateAfterDate() would return the next birthday in that case.
Then you can compute the difference in days as usual:
let diff = cal.components(.CalendarUnitDay,
fromDate: today,
toDate: nextBirthDay,
options: nil)
println(diff.day)
Update for Swift 2.2 (Xcode 7.3):
let cal = NSCalendar.currentCalendar()
let today = cal.startOfDayForDate(NSDate())
let dayAndMonth = cal.components([.Day, .Month],
fromDate: birthday)
let nextBirthDay = cal.nextDateAfterDate(today,
matchingComponents: dayAndMonth,
options: .MatchNextTimePreservingSmallerUnits)!
let diff = cal.components(.Day,
fromDate: today,
toDate: nextBirthDay,
options: [])
print(diff.day)
Update for Swift 3 (Xcode 8 GM):
let cal = Calendar.current
let today = cal.startOfDay(for: Date())
let dayAndMonth = cal.dateComponents([.day, .month], from: birthday)
let nextBirthDay = cal.nextDate(after: today, matching: dayAndMonth,
matchingPolicy: .nextTimePreservingSmallerComponents)!
let diff = cal.dateComponents([.day], from: today, to: nextBirthDay)
print(diff.day!)
Btw, in case anyone needs here is the #Aaron function in Swift 3:
func daysBetween(date1: Date, date2: Date) -> Int {
let calendar = Calendar.current
let date1 = calendar.startOfDay(for: date1)
let date2 = calendar.startOfDay(for: date2)
let components = calendar.dateComponents([Calendar.Component.day], from: date1, to: date2)
return components.day ?? 0
}

Resources