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"
}
}
}
Related
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
}()
}
I am trying to make a counter that count day 1 from each month and reset it self for next month when it reaches that specific day.
Here's my code:
let date = NSDate()
let calendar = Calendar.current
let currentDate = calendar.date(from: components)
let userCalendar = Calendar.current
// here we set the due date. When the timer is supposed to finish
let competitionDate = NSDateComponents()
competitionDate.year = components.year!
competitionDate.month = components.month!
competitionDate.day = 9
competitionDate.hour = 00
competitionDate.minute = 00
let competitionDay = userCalendar.date(from: competitionDate as DateComponents)!
//here we change the seconds to hours,minutes and days
let CompetitionDayDifference = calendar.dateComponents([.day, .hour, .minute], from: Date().startOfMonth(), to: competitionDay)
//finally, here we set the variable to our remaining time
let daysLeft = CompetitionDayDifference.day
let hoursLeft = CompetitionDayDifference.hour
let minutesLeft = CompetitionDayDifference.minute
print(daysLef)
counter.value = CGFloat(daysLeft!)
The problem with this code is when it reaches day 0 it never reset it self back to count from current date to next month day # 9
You should use Calendar method func nextDate(after date: Date, matching components: DateComponents, matchingPolicy: Calendar.MatchingPolicy, repeatedTimePolicy: Calendar.RepeatedTimePolicy = default, direction: Calendar.SearchDirection = default) -> Date?, pass the day date component to it, use matchingPolicy .strict, repeatedTimePolicy .first and direction .forward to find the next date occurrence. Then you just need to use Calendar method func dateComponents(_ components: Set<Calendar.Component>, from start: Date, to end: Date) -> DateComponents and pass the date components that you want returned (day, hour, minute):
extension Date {
func timeLeftUntil(day: Int) -> (day: Int, hour: Int, minute: Int) {
precondition(1...28 ~= day, "paycheck day \(day) is out of range 1...28")
let nextDate = Calendar.current.nextDate(after: self, matching: DateComponents(day: day), matchingPolicy: .strict, repeatedTimePolicy: .first, direction: .forward)!
let dateComponents = Calendar.current.dateComponents([.day,.hour,.minute], from: self, to: nextDate)
return (dateComponents.day!, dateComponents.hour!, dateComponents.minute!)
}
}
Usage:
Date().timeLeftUntil(day: 25) // (day 23, hour 0, minute 39)
I have a function below (datesCheck) that cycles through an array of dates and firstly removes any entries if there is more than one a day and then checks whether the dates are consecutive within the array, returning the number of consecutive days from the current date.
func datesCheck(_ dateArray: [Date]) -> Int {
let calendar = Calendar.current
let uniqueDates = NSSet(array: dateArray.map { calendar.startOfDay(for: $0) }).sorted {
($0 as AnyObject).timeIntervalSince1970 > ($1 as AnyObject).timeIntervalSince1970
} as! [Date]
var lastDate = calendar.startOfDay(for: Date())
var i = 0
while i < uniqueDates.count && uniqueDates[i].compare(lastDate) == .orderedSame {
lastDate = (calendar as NSCalendar).date(byAdding: .day, value: -1, to: lastDate, options: [])!
i += 1
}
numberOfConsecutiveDays = i
return i
}
This function works well but I want to only apply this to dates that are Monday – Friday, with the consecutive dates checker checking Friday – Monday, effectively ignoring saturday and sunday. I have tried to achieve this using calendar.components but cannot find a way to ignore weekends when checking if the dates are consecutive excluding weekends.
let today = Date()
let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)
let components = calendar!.components([.weekday], fromDate: today)
if components.weekday == 2 {
print("Monday")
} else {
print("Monday")
}
A couple points:
Since you don't need weekends, filter them out
Your function is non-deterministic, since it uses the current time (Date()). The result is theoretically different for every run. I added a second parameter fromDate to make it deterministic.
func datesCheck(_ dates: [Date], fromDate: Date = Date()) -> Int {
let calendar = Calendar.current
let weekdays = dates.map { calendar.startOfDay(for: $0) }
.filter { 2...6 ~= calendar.component(.weekday, from: $0) }
let uniqueDates = Set(weekdays).sorted(by: >)
var i = 0
var lastDate = calendar.startOfDay(for: fromDate)
while i < uniqueDates.count && uniqueDates[i] == lastDate {
switch calendar.component(.weekday, from: uniqueDates[i]) {
case 2:
// When the lastDate is a Monday, the previous weekday is 3 days ago
lastDate = calendar.date(byAdding: .day, value: -3, to: lastDate)!
default:
// Otherwise, it's 1 day ago
lastDate = calendar.date(byAdding: .day, value: -1, to: lastDate)!
}
i += 1
}
return i
}
Test:
let dates = [
DateComponents(calendar: .current, year: 2017, month: 8, day: 29).date!,
DateComponents(calendar: .current, year: 2017, month: 8, day: 28).date!,
DateComponents(calendar: .current, year: 2017, month: 8, day: 25).date!,
DateComponents(calendar: .current, year: 2017, month: 8, day: 24).date!,
DateComponents(calendar: .current, year: 2017, month: 8, day: 22).date!,
DateComponents(calendar: .current, year: 2017, month: 8, day: 21).date!
]
let today = DateComponents(calendar: .current, year: 2017, month: 8, day: 29).date!
print(datesCheck(dates, fromDate: today)) // 4
The code prints 4 since the gap between the 28th and 25th is ignored according to the weekend rule.
I'm trying to calculate the time from now (i.e. Date()) till the next 5pm.
If the current time is 3pm, the output will be 02:00:00. (in HH:MM:SS)
If the current time is 6pm, the output will be 23:00:00. (until the next 5pm!)
How do I do that in Swift 3?
Thanks.
You can use Calendar.nextDate to find the Date of the coming 5pm.
let now = Date()
let calendar = Calendar.current
let components = DateComponents(calendar: calendar, hour: 17) // <- 17:00 = 5pm
let next5pm = calendar.nextDate(after: now, matching: components, matchingPolicy: .nextTime)!
then, just compute the different between next5pm and now using dateComponents(_:from:to:).
let diff = calendar.dateComponents([.hour, .minute, .second], from: now, to: next5pm)
print(diff)
// Example outputs:
// hour: 2 minute: 21 second: 39 isLeapMonth: false
// hour: 23 minute: 20 second: 10 isLeapMonth: false
func tommorrow5() ->Date {
var todayAt5 = Calendar.current.dateComponents([.year,.month,.day,.hour], from: Date() )
todayAt5.hour = 17
let dateToDisplay = Calendar.current.date(from: todayAt5)
return Calendar.current.date(byAdding: .day , value: 1, to: dateToDisplay!)! }
func showTimeDifference()->DateComponents{
var todayAt5 = Calendar.current.dateComponents([.year,.month,.day,.hour], from: Date() )
todayAt5.hour = 17
let dateToDisplay = Calendar.current.date(from: todayAt5)
let now = Date()
switch now.compare(dateToDisplay!) {
case .orderedAscending : // now is earlier than 5pm
return Calendar.current.dateComponents([Calendar.Component.hour, Calendar.Component.minute, Calendar.Component.second], from: Date(), to: dateToDisplay!)
case .orderedDescending : // now is later than 5 pm
return Calendar.current.dateComponents([Calendar.Component.hour, Calendar.Component.minute, Calendar.Component.second], from: Date(), to: tommorrow5())
case .orderedSame : break // now is 5 pm
}
return Calendar.current.dateComponents([Calendar.Component.hour, Calendar.Component.minute, Calendar.Component.second], from: Date(), to: dateToDisplay!)
}
you can use it just like this
it will return time to 5pm today
if its already passed 5pm will return time to 5pm tomorrow
showTimeDifference()
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)