Hi everyone I'm working with 'Calendar' and I'm trying to find the right way and an elegant way to get the Tuesday of the next week only if today's date is a Sunday or Monday.
For example, if today is Sunday I would like to show the next available date on the following Tuesday
For now I have done this but I wanted to know if there is a right and more elegant way (i don't know if using DateInterval would be better)
enum WeekdaysRef: Int { case Dom = 1, Lun, Mar, Mer, Gio, Ven, Sab }
extension Date {
func startDate(using calendar: Calendar = .current) -> Date {
let sunday = calendar.component(.weekday, from: self) == WeekdaysRef.Dom.rawValue
let monday = calendar.component(.weekday, from: self) == WeekdaysRef.Lun.rawValue
return sunday ? today(adding: 2) : Monday ? today(adding: 1) : self
}
func today(adding: Int, _ calendar: Calendar = .current) -> Date {
calendar.date(byAdding: .day, value: adding, to: self)!
}
}
You don't need a custom enum for this, check if the week day is 1 or 2 and then return next Tuesday
func nextTuesday(after date: Date, using calendar: Calendar = .current) -> Date? {
let weekday = calendar.component(.weekday, from: date)
return weekday > 2 ? nil : calendar.date(byAdding: .day, value: weekday % 2 + 1, to: date)
}
Note that I use a standalone function here and return nil if the in date isn't Sunday or Monday but this could easily be changed to using self in an extension and/or returning self or the given date instead of nil
Related
I am able to next 15 days date from current date, but when i tried to give custom date instead of current date, it not working.
let date = sharedAppDelegate.dueDate
let givenDate = Utility.convertStringToDate(format: "yyyy-MM-dd",
dateString: date)
let futureDate = (givenDate as NSCalendar).date(byAdding: dateComponents, to: Date()) ?? Date()
Third line gives error like Date? is not convertible to NSCalendar.
Expected result:- futureDate should be next 15 date from givenDate
A Date is not a Calendar
You can use the Calendar to do calculations or comparisons on Date objects, in your case you want to use the Calendar to add x number of days to your starting date
Try
let startDate = Date()
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: startDate)
print(tomorrow)
OUTPUT:
Optional(2019-06-01 07:14:48 +0000)
You can extend Date class like this.
extension Date {
func dateByAddingDays(dateNum:Int) -> Date {
return Calendar.gregorian.date(byAdding: .day, value: dateNum, to: self)!
}
}
Now create date object
let today = Date()
let dayAfterTomorrow = today.dateByAddingDays(dateNum:2)
debugPrint(dayAfterTomorrow)
I've seen many approaches how to compute the difference between two dates in terms of a particular date component, e.g. in days, hours, months etc. (see this answer on Stackoverflow):
Calendar.current.dateComponents([.hour], from: fromDate, to: toDate).hour
Calendar.current.dateComponents([.day], from: fromDate, to: toDate).day
Calendar.current.dateComponents([.month], from: fromDate, to: toDate).month
What I haven't seen is how to make calculations with the actual Date objects. Something like
func computeNewDate(from fromDate: Date, to toDate: Date) -> Date
let delta = toDate - fromDate
let today = Date()
if delta < 0 {
return today
} else {
return today + delta
}
}
I have seen the DateInterval type introduced in iOS 10 but according to the documentation
[it] does not support reverse intervals i.e. intervals where the duration is less than 0 and the end date occurs earlier in time than the start date.
That makes it inherently difficult to calculate with dates – especially when you don't know which one is the earlier date.
Is there any clean and neat approach to compute time differences between Dates directly (and adding them to Date instances again) without computing with their timeIntervalSinceReferenceDate?
I ended up creating a custom operator for Date:
extension Date {
static func - (lhs: Date, rhs: Date) -> TimeInterval {
return lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate
}
}
With this operator I can now compute the difference between two dates on a more abstract level without caring about timeIntervalSinceReferenceDate or what exactly the reference date is – and without losing precision, for example:
let delta = toDate - fromDate
Obviously, I didn't change much, but for me it's a lot more readable and consequent: Swift has the + operator already implemented for a Date and a TimeInterval:
/// Returns a `Date` with a specified amount of time added to it.
public static func + (lhs: Date, rhs: TimeInterval) -> Date
So it's already supporting
Date + TimeInterval = Date
Consequently, it should also support
Date - Date = TimeInterval
in my opinion and that's what I added with the simple implementation of the - operator. Now I can simply write the example function exactly as mentioned in my question:
func computeNewDate(from fromDate: Date, to toDate: Date) -> Date
let delta = toDate - fromDate // `Date` - `Date` = `TimeInterval`
let today = Date()
if delta < 0 {
return today
} else {
return today + delta // `Date` + `TimeInterval` = `Date`
}
}
It might very well be that this has some downsides that I'm not aware of at this moment and I'd love to hear your thoughts on this.
You can extension with custom operator, and return tuples
extension Date {
static func -(recent: Date, previous: Date) -> (month: Int?, day: Int?, hour: Int?, minute: Int?, second: Int?) {
let day = Calendar.current.dateComponents([.day], from: previous, to: recent).day
let month = Calendar.current.dateComponents([.month], from: previous, to: recent).month
let hour = Calendar.current.dateComponents([.hour], from: previous, to: recent).hour
let minute = Calendar.current.dateComponents([.minute], from: previous, to: recent).minute
let second = Calendar.current.dateComponents([.second], from: previous, to: recent).second
return (month: month, day: day, hour: hour, minute: minute, second: second)
}
}
Using:
let interval = Date() - updatedDate
print(interval.day)
print(interval.month)
print(interval.hour)
Simply toDate.timeIntervalSince(fromDate).
To reimplement your function without adding any extension:
func computeNewDate(from fromDate: Date, to toDate: Date) -> Date {
let delta = toDate.timeIntervalSince(fromDate)
let today = Date()
if delta < 0 {
return today
} else {
return today.addingTimeInterval(delta)
}
}
You can use :
let delta = fromDate.distance(to: toDate)
I found a builtin solution to calculate the difference between 2 dates.
let delta = toDate.timeIntervalSince(fromDate)
How about something like…
func computeNewDate(from fromDate: Date, to toDate: Date) -> Date {
let delta = Calendar.current.dateComponents([.second], from: fromDate, to: toDate).second!
return Calendar.current.date(byAdding: .second, value: delta, to: Date())!
}
I'm setting a date to a certain birthdayTextField like so
#objc func birthdayDatePickerValueChanged(sender: UIDatePicker) {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
birthdayTextField.text = formatter.string(from: sender.date)
}
Now this textfield value is stored in coredata in a string attribute. There can be many such birthday dates stored in coredata. Now when I fetch these dates from the database, I want to show in a tableview only those dates which come in the following month.
How can it be achieved...?
This is a solution using the powerful date math abilities of Calendar together with DateComponents in a Date extension.
It calculates the first day of next month with nextDate(after:matching:matchingPolicy:) looking for day == 1
It compares the given date with the first date of next month to the month granularity with compare(:to:toGranularity:).
extension Date {
func isDateInNextMonth() -> Bool {
let calendar = Calendar.current
let nextMonth = calendar.nextDate(after: Date(), matching: DateComponents(day:1), matchingPolicy: .nextTime)!
return calendar.compare(self, to: nextMonth, toGranularity: .month) == .orderedSame
}
}
Use it simply in your method
sender.date.isDateInNextMonth()
Or – more versatile – according to the other isDateIn... methods as extension of Calendar
extension Calendar {
func isDateInNextMonth(_ date : Date) -> Bool {
let nextMonth = self.nextDate(after: Date(), matching: DateComponents(day:1), matchingPolicy: .nextTime)!
return self.compare(date, to: nextMonth, toGranularity: .month) == .orderedSame
}
}
and use it
Calendar.current.isDateInNextMonth(sender.date)
Edit:
If you want to check if the date is in the next 30 days it's still easier
extension Calendar {
func isDateInNextThirtyDays(_ date : Date) -> Bool {
return self.dateComponents([.month], from: Date(), to:date).month! < 1
}
}
As today is Friday, which is 6 according to NSCalendar. I can get this by using the following
Calendar.current.component(.weekday, from: Date())
How do I get weekday component of Saturday last week, which should be 7?
If I do Calendar.current.component(.weekday, from: Date()) - 6 . I am getting 0 which is not valid component.
Try this, you have to get the date first then subtract again from it:
var dayComp = DateComponents(day: -6)
let date = Calendar.current.date(byAdding: dayComp, to: Date())
Calendar.current.component(.weekday, from: date!)
For that first you need to get that date using calendar.date(byAdding:value:to:) and then get day number from it.
extension Date {
func getDateFor(days:Int) -> Date? {
return Calendar.current.date(byAdding: .day, value: days, to: Date())
}
}
Now simply use thus function and get your days.
if let date = Date().getDateFor(days: -6) {
print(Calendar.current.component(.weekday, from: date))
}
you can use calendar and date components and put everything in a Date extension, something like:
(Swift 4.1)
extension Date {
func removing(minutes: Int) -> Date? {
let result = Calendar.current.date(byAdding: .minute, value: -(minutes), to: self)
return result
}
}
I'm trying to work out how to decide if a given timestamp occurs today, or +1 / -1 days. Essentially, I'd like to do something like this (Pseudocode)
IF days_from_today(timestamp) == -1 RETURN 'Yesterday'
ELSE IF days_from_today(timestamp) == 0 RETURN 'Today'
ELSE IF days_from_today(timestamp) == 1 RETURN 'Tomorrow'
ELSE IF days_from_today(timestamp) < 1 RETURN days_from_today(timestamp) + ' days ago'
ELSE RETURN 'In ' + days_from_today(timestamp) + ' ago'
Crucially though, it needs to be in Swift and I'm struggling with the NSDate / NSCalendar objects. I started with working out the time difference like this:
let calendar = NSCalendar.currentCalendar()
let date = NSDate(timeIntervalSince1970: Double(timestamp))
let timeDifference = calendar.components([.Second,.Minute,.Day,.Hour],
fromDate: date, toDate: NSDate(), options: NSCalendarOptions())
However comparing in this way isn't easy, because the .Day is different depending on the time of day and the timestamp. In PHP I'd just use mktime to create a new date, based on the start of the day (i.e. mktime(0,0,0)), but I'm not sure of the easiest way to do that in Swift.
Does anybody have a good idea on how to approach this? Perhaps an extension to NSDate or something similar would be best?
Swift 3/4/5:
Calendar.current.isDateInToday(yourDate)
Calendar.current.isDateInYesterday(yourDate)
Calendar.current.isDateInTomorrow(yourDate)
Additionally:
Calendar.current.isDateInWeekend(yourDate)
Note that for some countries weekend may be different than Saturday-Sunday, it depends on the calendar.
You can also use autoupdatingCurrent instead of current calendar, which will track user updates. You use it the same way:
Calendar.autoupdatingCurrent.isDateInToday(yourDate)
Calendar is a type alias for the NSCalendar.
Calendar has methods for all three cases
func isDateInYesterday(_ date: Date) -> Bool
func isDateInToday(_ date: Date) -> Bool
func isDateInTomorrow(_ date: Date) -> Bool
To calculate the days earlier than yesterday use
func dateComponents(_ components: Set<Calendar.Component>,
from start: Date,
to end: Date) -> DateComponents
pass [.day] to components and get the day property from the result.
This is a function which considers also is in for earlier and later dates by stripping the time part (Swift 3+).
func dayDifference(from interval : TimeInterval) -> String
{
let calendar = Calendar.current
let date = Date(timeIntervalSince1970: interval)
if calendar.isDateInYesterday(date) { return "Yesterday" }
else if calendar.isDateInToday(date) { return "Today" }
else if calendar.isDateInTomorrow(date) { return "Tomorrow" }
else {
let startOfNow = calendar.startOfDay(for: Date())
let startOfTimeStamp = calendar.startOfDay(for: date)
let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
let day = components.day!
if day < 1 { return "\(-day) days ago" }
else { return "In \(day) days" }
}
}
Alternatively you could use DateFormatter for Yesterday, Today and Tomorrow to get localized strings for free
func dayDifference(from interval : TimeInterval) -> String
{
let calendar = Calendar.current
let date = Date(timeIntervalSince1970: interval)
let startOfNow = calendar.startOfDay(for: Date())
let startOfTimeStamp = calendar.startOfDay(for: date)
let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
let day = components.day!
if abs(day) < 2 {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
formatter.doesRelativeDateFormatting = true
return formatter.string(from: date)
} else if day > 1 {
return "In \(day) days"
} else {
return "\(-day) days ago"
}
}
Update:
In macOS 10.15 / iOS 13 RelativeDateTimeFormatter was introduced to return (localized) strings relative to a specific date.
Swift 4 update:
let calendar = Calendar.current
let date = Date()
calendar.isDateInYesterday(date)
calendar.isDateInToday(date)
calendar.isDateInTomorrow(date)
NSCalender has new methods that you can use directly.
NSCalendar.currentCalendar().isDateInTomorrow(NSDate())//Replace NSDate() with your date
NSCalendar.currentCalendar().isDateInYesterday()
NSCalendar.currentCalendar().isDateInTomorrow()
Hope this helps
On Swift 5 and iOS 13 use the RelativeDateTimeFormatter,
let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .named
formatter.localizedString(from: DateComponents(day: -1)) // "yesterday"
formatter.localizedString(from: DateComponents(day: 1)) // "Tomorrow"
formatter.localizedString(from: DateComponents(hour: 2)) // "in 2 hours"
formatter.localizedString(from: DateComponents(minute: 45)) // "in 45 minutes"
1)According to your example you want to receive labels "Yesterday", "Today" and etc. iOS can do this by default:
https://developer.apple.com/documentation/foundation/nsdateformatter/1415848-doesrelativedateformatting?language=objc
2)If you want to compute your custom label when iOS don't add these labels by itself then alternatively you can use 2 DateFormatter objects with both doesRelativeDateFormatting == true and doesRelativeDateFormatting == false and compare if their result date strings are the same or different