How to compare two date components - ios

I'm working on app like social media app where user post something on wall post data saved to server and return to app I want to get time of post like facebook Instagram etc.(5 hours ago)
the response I get from server is
"2020-03-07T13:15:09"
so first I split miliseconds from my datetime
func date(post: mdlSocialFeedbackView.mdlMemberWallPost) -> String{
let timeDateWithoutMilliseconds = post.CreatedDate!.split(separator: ".")[0]
let date = timeDateWithoutMilliseconds.split(separator: "T")[0]
let time = timeDateWithoutMilliseconds.split(separator: "T")[1]
let formatter = DateFormatter()
let timeDateObject = formatter.timeFromServer(time: String(time))
let dateObject = formatter.dateFromServer(date: String(date))
let calender = Calendar.current
let dateString = calender.dateOfPostOnWall(dateTime: dateObject!)
if let date = dateString{
return date
}else{
let timeString = calender.timeOfPostOnWall(dateTime: timeDateObject!)
return timeString!
}
}
I'm using this function in my UIViewController class to get time from server and return the exact time of post.
Extentions in Calender and DateFormatter classes and also Help me and guide me to format the current time zone
extension DateFormatter{
func dateFromServer(date: String) -> Date?{
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
//formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.timeZone = .current
return formatter.date(from: date)
}
func timeFromServer(time: String) -> Date?{
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
//formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.timeZone = .current
return formatter.date(from: time)
}
}
extension Calendar{
func timeOfPostOnWall(dateTime: Date?)-> String?{
let componentOfTime = self.dateComponents([.hour, .minute, .second], from: dateTime!)
let componentOfCurrentTime = self.dateComponents([.hour, .minute,.second], from: Date())
guard componentOfCurrentTime.hour == componentOfTime.hour
else{
return "about \(componentOfCurrentTime.hour! - componentOfTime.hour!) hours ago."}
guard componentOfCurrentTime.minute == componentOfTime.minute
else{
return "about \(componentOfCurrentTime.minute! - componentOfTime.minute!) minutes ago."
}
guard componentOfCurrentTime.second == componentOfTime.second
else{
return "about \(componentOfCurrentTime.second! - componentOfTime.second!) seconds ago."
}
return nil
}
func dateOfPostOnWall(dateTime: Date?)-> String?{
let componentOfTime = self.dateComponents([.year, .month, .day], from: dateTime!)
let componentOfCurrentTime = self.dateComponents([.year, .month, .day], from: Date())
guard componentOfCurrentTime.year == componentOfTime.year
else{
return "about \(componentOfCurrentTime.year! - componentOfTime.year!) years ago."}
guard componentOfCurrentTime.month == componentOfTime.month
else{
return "about \(componentOfCurrentTime.month! - componentOfTime.month!) months ago."
}
guard componentOfCurrentTime.day == componentOfTime.day
else{
return "about \(componentOfCurrentTime.day! - componentOfTime.day!) days ago."
}
return nil
}
}
now the issue is when ever I post something components of current time return
enter image description here
and response on wall something like this
enter image description here

The cause of this bug seems to be that your server clock is out of sync with the device's time. You should find out which clock has the "correct" time and adjust the other clock. Alternatively, rewrite the server side code to respond with the current time according to the server's clock.
Anyway, you are also reinventing the wheel a lot in your code. The bug could also be caused by the date-and-time handling code you've written yourself. You should instead use the built-in APIs, because they are less likely to have bugs.
The date you receive is in ISO 8601 format, so you can parse it with a ISO8601DateFormatter. Your dateFromServer and timeFromServer methods are quite redundant.
func date(post: mdlSocialFeedbackView.mdlMemberWallPost) -> String{
let dateString = post.CreatedDate!
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withColonSeparatorInTime, .withTime, .withFullDate]
let date = formatter.date(from: dateString)
// to be continued
}
Your way of finding the difference between two dates is questionable to say the least. If I post something on December 31, and I view it on January 1, I would see "about 1 year ago", where I personally would expect "about 1 day ago", or less.
You can use the dateComponents(_:from:to:) method:
// Calendar extension
func estimatedTimeFromNow(date: Date)-> String {
let diff = self.dateComponents([.year, .month, day, .hour, .minute, .second], from: date, to: Date())
if let year = diff.year {
return "about \(year) year(s) ago"
} else if let month = diff.month {
return "about \(month) month(s) ago"
} else if let day = diff.day {
return "about \(day) day(s) ago"
} else if let hour = diff.hour {
return "about \(hour) hour(s) ago"
} else if let minute = diff.minute {
return "about \(minute) minute(s) ago"
} else if let second = diff.second {
return "about \(second) second(s) ago"
} else {
return "just now"
}
}
And in date(post:):
return Calendar.current.estimatedTimeFromNow(date: date)

Your date is in ISO 8601. So use an ISO8601DateFormatter!
final class ISO8601DateFormatter: Foundation.ISO8601DateFormatter {
override init() {
super.init()
formatOptions.remove(.withTimeZone)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
ISO8601DateFormatter().date(from: "2020-03-07T13:15:09")

Related

Grouping Array of Objects by Custom Time Intervals

I have an array of posts which I currently group by day. I use the below functions to do so;
private func splitDay(from date: Date) -> Date {
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day], from: date)
return calendar.date(from: components)!
}
private func sectionPosts(posts: [Post]) {
let groups = Dictionary(grouping: posts) { (posts) in
return splitDay(from: Date(timeIntervalSince1970: posts.createdOn))
}
self.sections = groups.map { (date, posts) in
return PostSection(date: date, posts: posts)
}
}
However, i'd like to implement a custom grouping which would be the following;
Today
Yesterday
This Week
This Month
Older
How would I build this into my grouping function? My section struct is like such;
struct PostSection {
var date: Date
var posts: [Post]
}
Since you are trying to group your data into groups such as "today", "yesterday", "this week" and "this month", you should first create a type to represent these groups:
enum PostGroup {
case today
case yesterday
case thisWeek
case thisMonth
case older
case coming // added "coming" group so that the groups cover all possible dates
}
Then your PostSection struct would have a PostGroup property, rather than a Date property:
struct PostSection {
let group: PostGroup
let posts: [Post]
}
Now we just need a function that goes (Post) -> PostGroup that we can pass to Dictionary(grouping:by:). This can be implemented just by comparing the date components of the post date with that of today:
func group(for post: Post) -> PostGroup {
let today = Date()
let calendar = Calendar.current
let postDateComponents = calendar.dateComponents([.year, .month, .day], from: post.createdOn)
let todayDateComponents = calendar.dateComponents([.year, .month, .day], from: today)
if postDateComponents == todayDateComponents {
return .today
}
let daysDifference = calendar.dateComponents([.day], from: postDateComponents, to: todayDateComponents)
if daysDifference.day == 1 {
return .yesterday
}
let postWeekComponents = calendar.dateComponents([.weekOfYear, .yearForWeekOfYear], from: post.createdOn)
let todayWeekComponents = calendar.dateComponents([.weekOfYear, .yearForWeekOfYear], from: today)
if postWeekComponents == todayWeekComponents {
return .thisWeek
}
if postDateComponents.year == todayDateComponents.year &&
postDateComponents.month == todayDateComponents.month {
return .thisMonth
}
if post.createdOn < today {
return .older
} else {
return .coming
}
}
To finish it off:
private func sectionPosts(posts: [Post]) {
let groups = Dictionary(grouping: posts, by: group(for:))
self.sections = groups.map { (group, posts) in
return PostSection(group: group, posts: posts)
}
}
This is not an elegant solution, but it solves the problem, please feel free to optimize this function, specially since date calculations can be slow.
private func preSplitProcess(from date: Date) -> Date {
let calendar = Calendar.current
let isToday = calendar.isDateInToday(date)
if isToday {
return date
} else {
let wasYesterday = calendar.isDateInYesterday(date)
if wasYesterday {
return date
} else {
let bowComponents = calendar.dateComponents([.weekOfYear], from: date)
let beginningOfWeek = calendar.date(from: bowComponents)! // Handle errors, please
if beginningOfWeek < date {
return date
} else {
let bomComponents = calendar.dateComponents([.month], from: date)
let beginningsOfMonth = calendar.date(from: bomComponents)! // Handle errors, please
if beginningsOfMonth < date {
return date
} else {
var components = DateComponents()
components.year = 1970
components.month = 01
components.day = 01
let oldDate = calendar.date(from: components)!
return oldDate
}
}
}
}
}
Add this before you call your splitDay function, since this doesn't reduce the return dates to the same date.

How to get a half of every month from current year in Swift

In my application i have an option to enter the data for every 15days.I have to maintain this for an current year.Please help me to figure out this problem.
For ex: [
"1-1-2018 to 15-1-2018", "16-1-2018 to 31-1-2018",
"1-2-2018 to 15-2-2018", "16-2-2018 to 28-2-2018",
"1-3-2018 to 15-3-2018", "16-3-2018 to 31-3-2018",
"1-4-2018 to 15-4-2018", "16-4-2018 to 30-4-2018",
"1-5-2018 to 15-5-2018", "16-5-2018 to 31-5-2018",
"1-6-2018 to 15-6-2018", "16-6-2018 to 30-6-2018",
"1-7-2018 to 15-7-2018", "16-7-2018 to 31-7-2018",
"1-8-2018 to 15-8-2018", "16-8-2018 to 31-8-2018",
"1-9-2018 to 15-9-2018", "16-9-2018 to 30-9-2018",
"1-10-2018 to 15-10-2018", "16-10-2018 to 31-10-2018",
"1-11-2018 to 15-11-2018", "16-11-2018 to 30-11-2018",
"1-12-2018 to 15-12-2018", "16-12-2018 to 31-12-2018"
]
From Calendar API you can get total number of days for any month in the given date and also first day of the month like below,
extension Calendar {
public func firstDayOfMonth(date: Date) -> Date {
let components = self.dateComponents([.year, .month], from: date)
return self.date(from: components) ?? date
}
public func numberOfDaysInMonthFor(date: Date) -> Int {
let range = self.range(of: .day, in: .month, for: date)
return range?.count ?? 0
}
public func lowerHalfOfMonthFor(date: Date) -> (Date, Date) {
let startDate = self.firstDayOfMonth(date: date)
let endDate = startDate.dateByAppending(day: 14)
return (startDate, endDate)
}
public func upperHalfOfMonthFor(date: Date) -> (Date, Date) {
let firstDayOfMonthDate = self.firstDayOfMonth(date: date)
let totalNoOfDaysInMonth = self.numberOfDaysInMonthFor(date: firstDayOfMonthDate)
let startDate = firstDayOfMonthDate.dateByAppending(day: 15)
let endDate = firstDayOfMonthDate.dateByAppending(day: totalNoOfDaysInMonth - 1)
return (startDate, endDate)
}
}
you can also extend Date to get new date by appending any number of days,
extension Date {
public func dateByAppending(day: Int) -> Date {
let newDate = Calendar.current.date(byAdding: .day, value: day, to: self)
return newDate ?? self
}
public func daysDifference(_ date: Date?) -> Int? {
guard let date = date else { return nil }
return Calendar.current.dateComponents([.day], from: self, to: date).day
}
With the mix of above helper methods, you should be able to achieve the required result like below,
let date = Date()
let lowerHalf = Calendar.current.lowerHalfOfMonthFor(date: date)
let uppperHalf = Calendar.current.upperHalfOfMonthFor(date: date)
Below code will calculate the 15th day if you give the input,
static func getFortnightly(selectedDate : String) -> String?{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yy" //Your date format
if let dateSelected = dateFormatter.date(from: selectedDate) {//according to d
let newDate = Calendar.current.date(byAdding: .weekOfYear, value: 2, to: dateSelected)
let convertedDateToString = dateFormatter.string(from: newDate!)
return convertedDateToString
}
return nil
}
}

How do I display time/date like Instagram? [duplicate]

I am trying to get the difference between the current date as NSDate() and a date from a PHP time(); call for example: NSDate(timeIntervalSinceReferenceDate: 1417147270). How do I go about getting the difference in time between the two dates. I'd like to have a function that compares the two dates and if(seconds > 60) then it returns minutes, if(minutes > 60) return hours and if(hours > 24) return days and so on.
How should I go about this?
EDIT: The current accepted answer has done exactly what I've wanted to do. I recommend it for easy usage for getting the time between two dates in the form that that PHP time() function uses. If you aren't particularly familiar with PHP, that's the time in seconds from January 1st, 1970. This is beneficial for a backend in PHP. If perhaps you're using a backend like NodeJS you might want to consider some of the other options you'll find below.
Xcode 8.3 • Swift 3.1 or later
You can use Calendar to help you create an extension to do your date calculations as follow:
extension Date {
/// Returns the amount of years from another date
func years(from date: Date) -> Int {
return Calendar.current.dateComponents([.year], from: date, to: self).year ?? 0
}
/// Returns the amount of months from another date
func months(from date: Date) -> Int {
return Calendar.current.dateComponents([.month], from: date, to: self).month ?? 0
}
/// Returns the amount of weeks from another date
func weeks(from date: Date) -> Int {
return Calendar.current.dateComponents([.weekOfMonth], from: date, to: self).weekOfMonth ?? 0
}
/// Returns the amount of days from another date
func days(from date: Date) -> Int {
return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
}
/// Returns the amount of hours from another date
func hours(from date: Date) -> Int {
return Calendar.current.dateComponents([.hour], from: date, to: self).hour ?? 0
}
/// Returns the amount of minutes from another date
func minutes(from date: Date) -> Int {
return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
}
/// Returns the amount of seconds from another date
func seconds(from date: Date) -> Int {
return Calendar.current.dateComponents([.second], from: date, to: self).second ?? 0
}
/// Returns the a custom time interval description from another date
func offset(from date: Date) -> String {
if years(from: date) > 0 { return "\(years(from: date))y" }
if months(from: date) > 0 { return "\(months(from: date))M" }
if weeks(from: date) > 0 { return "\(weeks(from: date))w" }
if days(from: date) > 0 { return "\(days(from: date))d" }
if hours(from: date) > 0 { return "\(hours(from: date))h" }
if minutes(from: date) > 0 { return "\(minutes(from: date))m" }
if seconds(from: date) > 0 { return "\(seconds(from: date))s" }
return ""
}
}
Using Date Components Formatter
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [.second, .minute, .hour, .day, .weekOfMonth, .month, .year]
dateComponentsFormatter.maximumUnitCount = 1
dateComponentsFormatter.unitsStyle = .full
dateComponentsFormatter.string(from: Date(), to: Date(timeIntervalSinceNow: 4000000)) // "1 month"
let date1 = DateComponents(calendar: .current, year: 2014, month: 11, day: 28, hour: 5, minute: 9).date!
let date2 = DateComponents(calendar: .current, year: 2015, month: 8, day: 28, hour: 5, minute: 9).date!
let years = date2.years(from: date1) // 0
let months = date2.months(from: date1) // 9
let weeks = date2.weeks(from: date1) // 39
let days = date2.days(from: date1) // 273
let hours = date2.hours(from: date1) // 6,553
let minutes = date2.minutes(from: date1) // 393,180
let seconds = date2.seconds(from: date1) // 23,590,800
let timeOffset = date2.offset(from: date1) // "9M"
let date3 = DateComponents(calendar: .current, year: 2014, month: 11, day: 28, hour: 5, minute: 9).date!
let date4 = DateComponents(calendar: .current, year: 2015, month: 11, day: 28, hour: 5, minute: 9).date!
let timeOffset2 = date4.offset(from: date3) // "1y"
let date5 = DateComponents(calendar: .current, year: 2017, month: 4, day: 28).date!
let now = Date()
let timeOffset3 = now.offset(from: date5) // "1w"
If someone needs to display all time units e.g "hours minutes seconds" not just "hours". Let's say the time difference between two dates is 1hour 59minutes 20seconds. This function will display "1h 59m 20s".
Here is my Objective-C code:
extension NSDate {
func offsetFrom(date: NSDate) -> String {
let dayHourMinuteSecond: NSCalendarUnit = [.Day, .Hour, .Minute, .Second]
let difference = NSCalendar.currentCalendar().components(dayHourMinuteSecond, fromDate: date, toDate: self, options: [])
let seconds = "\(difference.second)s"
let minutes = "\(difference.minute)m" + " " + seconds
let hours = "\(difference.hour)h" + " " + minutes
let days = "\(difference.day)d" + " " + hours
if difference.day > 0 { return days }
if difference.hour > 0 { return hours }
if difference.minute > 0 { return minutes }
if difference.second > 0 { return seconds }
return ""
}
}
In Swift 3+:
extension Date {
func offsetFrom(date: Date) -> String {
let dayHourMinuteSecond: Set<Calendar.Component> = [.day, .hour, .minute, .second]
let difference = NSCalendar.current.dateComponents(dayHourMinuteSecond, from: date, to: self)
let seconds = "\(difference.second ?? 0)s"
let minutes = "\(difference.minute ?? 0)m" + " " + seconds
let hours = "\(difference.hour ?? 0)h" + " " + minutes
let days = "\(difference.day ?? 0)d" + " " + hours
if let day = difference.day, day > 0 { return days }
if let hour = difference.hour, hour > 0 { return hours }
if let minute = difference.minute, minute > 0 { return minutes }
if let second = difference.second, second > 0 { return seconds }
return ""
}
}
Swift 5.1 • iOS 13
You can use RelativeDateFormatter that has been introduced by Apple in iOS 13.
let exampleDate = Date().addingTimeInterval(-15000)
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
let relativeDate = formatter.localizedString(for: exampleDate, relativeTo: Date())
print(relativeDate) // 4 hours ago
See How to show a relative date and time using RelativeDateTimeFormatter.
You ask:
I'd like to have a function that compares the two dates and if(seconds > 60) then it returns minutes, if(minutes > 60) return hours and if(hours > 24) return days and so on.
I'm assuming that you're trying to build a string representation of the elapsed time between two dates. Rather than writing your own code to do that, Apple already has a class designed to do precisely that. Namely, use DateComponentsFormatter, set allowedUnits to whatever values make sense to your app, set unitsStyle to whatever you want (e.g. .full), and then call string(from:to:).
E.g. in Swift 3:
let previousDate = ...
let now = Date()
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.month, .day, .hour, .minute, .second]
formatter.maximumUnitCount = 2 // often, you don't care about seconds if the elapsed time is in months, so you'll set max unit to whatever is appropriate in your case
let string = formatter.string(from: previousDate, to: now)
This also will localize the string appropriate for the device in question.
Or, in Swift 2.3:
let previousDate = ...
let now = NSDate()
let formatter = NSDateComponentsFormatter()
formatter.unitsStyle = .Full
formatter.allowedUnits = [.Month, .Day, .Hour, .Minute, .Second]
formatter.maximumUnitCount = 2
let string = formatter.stringFromDate(previousDate, toDate: now)
If you're looking for the actual numeric values, just use dateComponents. E.g. in Swift 3:
let components = Calendar.current.dateComponents([.month, .day, .hour, .minute, .second], from: previousDate, to: now)
Or, in Swift 2.3:
let components = NSCalendar.currentCalendar().components([.Month, .Day, .Hour, .Minute, .Second], fromDate: previousDate, toDate: now, options: [])
combined Extension + DateComponentsFormatter from the answer of #leo-dabus
Xcode 8.3 • Swift 3.1
extension DateComponentsFormatter {
func difference(from fromDate: Date, to toDate: Date) -> String? {
self.allowedUnits = [.year,.month,.weekOfMonth,.day]
self.maximumUnitCount = 1
self.unitsStyle = .full
return self.string(from: fromDate, to: toDate)
}
}
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.difference(from: Date(), to: Date(timeIntervalSinceNow: 4000000)) // "1 month"
--> Use this to find time gap between two dates in Swift(With two Strings).
func timeGapBetweenDates(previousDate : String,currentDate : String)
{
let dateString1 = previousDate
let dateString2 = currentDate
let Dateformatter = DateFormatter()
Dateformatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date1 = Dateformatter.date(from: dateString1)
let date2 = Dateformatter.date(from: dateString2)
let distanceBetweenDates: TimeInterval? = date2?.timeIntervalSince(date1!)
let secondsInAnHour: Double = 3600
let minsInAnHour: Double = 60
let secondsInDays: Double = 86400
let secondsInWeek: Double = 604800
let secondsInMonths : Double = 2592000
let secondsInYears : Double = 31104000
let minBetweenDates = Int((distanceBetweenDates! / minsInAnHour))
let hoursBetweenDates = Int((distanceBetweenDates! / secondsInAnHour))
let daysBetweenDates = Int((distanceBetweenDates! / secondsInDays))
let weekBetweenDates = Int((distanceBetweenDates! / secondsInWeek))
let monthsbetweenDates = Int((distanceBetweenDates! / secondsInMonths))
let yearbetweenDates = Int((distanceBetweenDates! / secondsInYears))
let secbetweenDates = Int(distanceBetweenDates!)
if yearbetweenDates > 0
{
print(yearbetweenDates,"years")//0 years
}
else if monthsbetweenDates > 0
{
print(monthsbetweenDates,"months")//0 months
}
else if weekBetweenDates > 0
{
print(weekBetweenDates,"weeks")//0 weeks
}
else if daysBetweenDates > 0
{
print(daysBetweenDates,"days")//5 days
}
else if hoursBetweenDates > 0
{
print(hoursBetweenDates,"hours")//120 hours
}
else if minBetweenDates > 0
{
print(minBetweenDates,"minutes")//7200 minutes
}
else if secbetweenDates > 0
{
print(secbetweenDates,"seconds")//seconds
}
}
Slightly modified code for Swift 3.0
let calendar = NSCalendar.current as NSCalendar
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: startDateTime)
let date2 = calendar.startOfDay(for: endDateTime)
let flags = NSCalendar.Unit.day
let components = calendar.components(flags, from: date1, to: date2, options: [])
return components.day!
I added a "long" version to Leo Dabus's asnwer in case you want to have a string that says something like "2 weeks ago" instead of just "2w"...
extension Date {
func offsetLong(from date: Date) -> String {
if years(from: date) > 0 {
return years(from: date) > 1 ? "\(years(from: date)) years ago" : "\(years(from: date)) year ago"
}
if months(from: date) > 0 {
return months(from: date) > 1 ? "\(months(from: date)) months ago" : "\(months(from: date)) month ago"
}
if weeks(from: date) > 0 {
return weeks(from: date) > 1 ? "\(weeks(from: date)) weeks ago" : "\(weeks(from: date)) week ago"
}
if days(from: date) > 0 {
return days(from: date) > 1 ? "\(days(from: date)) days ago" : "\(days(from: date)) day ago"
}
if hours(from: date) > 0 {
return hours(from: date) > 1 ? "\(hours(from: date)) hours ago" : "\(hours(from: date)) hour ago"
}
if minutes(from: date) > 0 {
return minutes(from: date) > 1 ? "\(minutes(from: date)) minutes ago" : "\(minutes(from: date)) minute ago"
}
if seconds(from: date) > 0 {
return seconds(from: date) > 1 ? "\(seconds(from: date)) seconds ago" : "\(seconds(from: date)) second ago"
}
return ""
}
}
func dateDiff(dateStr:String) -> String {
var f:NSDateFormatter = NSDateFormatter()
f.timeZone = NSTimeZone.localTimeZone()
f.dateFormat = "yyyy-M-dd'T'HH:mm:ss.SSSZZZ"
var now = f.stringFromDate(NSDate())
var startDate = f.dateFromString(dateStr)
var endDate = f.dateFromString(now)
var calendar: NSCalendar = NSCalendar.currentCalendar()
let calendarUnits = NSCalendarUnit.CalendarUnitWeekOfMonth | NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitHour | NSCalendarUnit.CalendarUnitMinute | NSCalendarUnit.CalendarUnitSecond
let dateComponents = calendar.components(calendarUnits, fromDate: startDate!, toDate: endDate!, options: nil)
let weeks = abs(dateComponents.weekOfMonth)
let days = abs(dateComponents.day)
let hours = abs(dateComponents.hour)
let min = abs(dateComponents.minute)
let sec = abs(dateComponents.second)
var timeAgo = ""
if (sec > 0){
if (sec > 1) {
timeAgo = "\(sec) Seconds Ago"
} else {
timeAgo = "\(sec) Second Ago"
}
}
if (min > 0){
if (min > 1) {
timeAgo = "\(min) Minutes Ago"
} else {
timeAgo = "\(min) Minute Ago"
}
}
if(hours > 0){
if (hours > 1) {
timeAgo = "\(hours) Hours Ago"
} else {
timeAgo = "\(hours) Hour Ago"
}
}
if (days > 0) {
if (days > 1) {
timeAgo = "\(days) Days Ago"
} else {
timeAgo = "\(days) Day Ago"
}
}
if(weeks > 0){
if (weeks > 1) {
timeAgo = "\(weeks) Weeks Ago"
} else {
timeAgo = "\(weeks) Week Ago"
}
}
print("timeAgo is===> \(timeAgo)")
return timeAgo;
}
With Swift 3, according to your needs, you may choose one of the two following ways to solve your problem.
1. Display the difference between two dates to the user
You can use a DateComponentsFormatter to create strings for your app’s interface. DateComponentsFormatter has a maximumUnitCount property with the following declaration:
var maximumUnitCount: Int { get set }
Use this property to limit the number of units displayed in the resulting string. For example, with this property set to 2, instead of “1h 10m, 30s”, the resulting string would be “1h 10m”. Use this property when you are constrained for space or want to round up values to the nearest large unit.
By setting maximumUnitCount's value to 1, you are guaranteed to display the difference in only one DateComponentsFormatter's unit (years, months, days, hours or minutes).
The Playground code below shows how to display the difference between two dates:
import Foundation
let oldDate = Date(timeIntervalSinceReferenceDate: -16200)
let newDate = Date(timeIntervalSinceReferenceDate: 0)
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [NSCalendar.Unit.year, .month, .day, .hour, .minute]
dateComponentsFormatter.maximumUnitCount = 1
dateComponentsFormatter.unitsStyle = DateComponentsFormatter.UnitsStyle.full
let timeDifference = dateComponentsFormatter.string(from: oldDate, to: newDate)
print(String(reflecting: timeDifference)) // prints Optional("5 hours")
Note that DateComponentsFormatter rounds up the result. Therefore, a difference of 4 hours and 30 minutes will be displayed as 5 hours.
If you need to repeat this operation, you can refactor your code:
import Foundation
struct Formatters {
static let dateComponentsFormatter: DateComponentsFormatter = {
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [NSCalendar.Unit.year, .month, .day, .hour, .minute]
dateComponentsFormatter.maximumUnitCount = 1
dateComponentsFormatter.unitsStyle = DateComponentsFormatter.UnitsStyle.full
return dateComponentsFormatter
}()
}
extension Date {
func offset(from: Date) -> String? {
return Formatters.dateComponentsFormatter.string(from: oldDate, to: self)
}
}
let oldDate = Date(timeIntervalSinceReferenceDate: -16200)
let newDate = Date(timeIntervalSinceReferenceDate: 0)
let timeDifference = newDate.offset(from: oldDate)
print(String(reflecting: timeDifference)) // prints Optional("5 hours")
2. Get the difference between two dates without formatting
If you don't need to display with formatting the difference between two dates to the user, you can use Calendar. Calendar has a method dateComponents(_:from:to:) that 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 code below that uses dateComponents(_:from:to:) shows how to retrieve the difference between two dates by returning the difference in only one type of Calendar.Component (years, months, days, hours or minutes).
import Foundation
let oldDate = Date(timeIntervalSinceReferenceDate: -16200)
let newDate = Date(timeIntervalSinceReferenceDate: 0)
let descendingOrderedComponents = [Calendar.Component.year, .month, .day, .hour, .minute]
let dateComponents = Calendar.current.dateComponents(Set(descendingOrderedComponents), from: oldDate, to: newDate)
let arrayOfTuples = descendingOrderedComponents.map { ($0, dateComponents.value(for: $0)) }
for (component, value) in arrayOfTuples {
if let value = value, value > 0 {
print(component, value) // prints hour 4
break
}
}
If you need to repeat this operation, you can refactor your code:
import Foundation
extension Date {
func offset(from: Date) -> (Calendar.Component, Int)? {
let descendingOrderedComponents = [Calendar.Component.year, .month, .day, .hour, .minute]
let dateComponents = Calendar.current.dateComponents(Set(descendingOrderedComponents), from: from, to: self)
let arrayOfTuples = descendingOrderedComponents.map { ($0, dateComponents.value(for: $0)) }
for (component, value) in arrayOfTuples {
if let value = value, value > 0 {
return (component, value)
}
}
return nil
}
}
let oldDate = Date(timeIntervalSinceReferenceDate: -16200)
let newDate = Date(timeIntervalSinceReferenceDate: 0)
if let (component, value) = newDate.offset(from: oldDate) {
print(component, value) // prints hour 4
}
If your purpose is to get the exact day number between two dates, you can work around this issue like this:
// Assuming that firstDate and secondDate are defined
// ...
var calendar: NSCalendar = NSCalendar.currentCalendar()
// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDayForDate(firstDate)
let date2 = calendar.startOfDayForDate(secondDate)
let flags = NSCalendarUnit.DayCalendarUnit
let components = calendar.components(flags, fromDate: date1, toDate: date2, options: nil)
components.day // This will return the number of day(s) between dates
In Swift 2.2
/// Returns the amount of years from another date
func years(fromdate: NSDate) -> Int {
return NSCalendar.currentCalendar().components([.Year], fromDate: fromdate, toDate: NSDate(), options: []).year ?? 0
}
/// Returns the amount of months from another date
func months(fromdate: NSDate) -> Int {
return NSCalendar.currentCalendar().components([.Month], fromDate: fromdate, toDate: NSDate(), options: []).month ?? 0
}
/// Returns the amount of weeks from another date
func weeks(fromdate: NSDate) -> Int {
return NSCalendar.currentCalendar().components([.WeekOfYear], fromDate: fromdate, toDate: NSDate(), options: []).weekOfYear ?? 0
}
/// Returns the amount of days from another date
func days(fromdate: NSDate) -> Int {
return NSCalendar.currentCalendar().components([.Day], fromDate: fromdate, toDate: NSDate(), options: []).day ?? 0
}
/// Returns the amount of hours from another date
func hours(fromdate: NSDate) -> Int {
return NSCalendar.currentCalendar().components([.Hour], fromDate: fromdate, toDate: NSDate(), options: []).hour ?? 0
}
/// Returns the amount of minutes from another date
func minutes(fromdate: NSDate) -> Int {
return NSCalendar.currentCalendar().components([.Minute], fromDate: fromdate, toDate: NSDate(), options: []).minute ?? 0
}
/// Returns the amount of seconds from another date
func seconds(fromdate: NSDate) -> Int {
return NSCalendar.currentCalendar().components(.Second, fromDate: fromdate, toDate: NSDate(), options: []).second ?? 0
}
A small addition to Leo Dabus' answer to provide the plural versions and be more human readable.
Swift 3
extension Date {
/// Returns the amount of years from another date
func years(from date: Date) -> Int {
return Calendar.current.dateComponents([.year], from: date, to: self).year ?? 0
}
/// Returns the amount of months from another date
func months(from date: Date) -> Int {
return Calendar.current.dateComponents([.month], from: date, to: self).month ?? 0
}
/// Returns the amount of weeks from another date
func weeks(from date: Date) -> Int {
return Calendar.current.dateComponents([.weekOfMonth], from: date, to: self).weekOfMonth ?? 0
}
/// Returns the amount of days from another date
func days(from date: Date) -> Int {
return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
}
/// Returns the amount of hours from another date
func hours(from date: Date) -> Int {
return Calendar.current.dateComponents([.hour], from: date, to: self).hour ?? 0
}
/// Returns the amount of minutes from another date
func minutes(from date: Date) -> Int {
return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
}
/// Returns the amount of seconds from another date
func seconds(from date: Date) -> Int {
return Calendar.current.dateComponents([.second], from: date, to: self).second ?? 0
}
/// Returns the a custom time interval description from another date
func offset(from date: Date) -> String {
if years(from: date) == 1 { return "\(years(from: date)) year" } else if years(from: date) > 1 { return "\(years(from: date)) years" }
if months(from: date) == 1 { return "\(months(from: date)) month" } else if months(from: date) > 1 { return "\(months(from: date)) month" }
if weeks(from: date) == 1 { return "\(weeks(from: date)) week" } else if weeks(from: date) > 1 { return "\(weeks(from: date)) weeks" }
if days(from: date) == 1 { return "\(days(from: date)) day" } else if days(from: date) > 1 { return "\(days(from: date)) days" }
if hours(from: date) == 1 { return "\(hours(from: date)) hour" } else if hours(from: date) > 1 { return "\(hours(from: date)) hours" }
if minutes(from: date) == 1 { return "\(minutes(from: date)) minute" } else if minutes(from: date) > 1 { return "\(minutes(from: date)) minutes" }
return ""
}
}
Swift 5
func dateDiff(dateStr:String) -> String {
let f:DateFormatter = DateFormatter()
f.timeZone = NSTimeZone.local
f.dateFormat = "yyyy-M-dd'T'HH:mm:ss.SSSZZZ"
let now = f.string(from: NSDate() as Date)
let startDate = f.date(from: dateStr)
let endDate = f.date(from: now)
var _: NSCalendar = NSCalendar.current as NSCalendar
let dateComponents = Calendar.current.dateComponents([ .weekOfMonth, .day , .hour , .minute , .second], from: startDate!, to: endDate!)
let weeks = abs(dateComponents.weekOfMonth!)
let days = abs(dateComponents.day!)
let hours = abs(dateComponents.hour!)
let min = abs(dateComponents.minute!)
let sec = abs(dateComponents.second!)
var timeAgo = ""
if (sec > 0){
if (sec > 1) {
timeAgo = "\(sec) Seconds Ago"
} else {
timeAgo = "\(sec) Second Ago"
}
}
if (min > 0){
if (min > 1) {
timeAgo = "\(min) Minutes Ago"
} else {
timeAgo = "\(min) Minute Ago"
}
}
if(hours > 0){
if (hours > 1) {
timeAgo = "\(hours) Hours Ago"
} else {
timeAgo = "\(hours) Hour Ago"
}
}
if (days > 0) {
if (days > 1) {
timeAgo = "\(days) Days Ago"
} else {
timeAgo = "\(days) Day Ago"
}
}
if(weeks > 0){
if (weeks > 1) {
timeAgo = "\(weeks) Weeks Ago"
} else {
timeAgo = "\(weeks) Week Ago"
}
}
print("timeAgo is===> \(timeAgo)")
return timeAgo;
}
This is the shorter version: Basically I try to get the difference between the post timestamp with the Date() now.
// MARK: - UPDATE Time Stamp
static func updateTimeStampPost(postTimeStamp: Date?, _ completion: (_ finalString: String?) -> Void) {
// date in the current state
let date = Date()
let dateComponentFormatter = DateComponentsFormatter()
// change the styling date, wether second minute or hour
dateComponentFormatter.unitsStyle = .abbreviated
dateComponentFormatter.allowedUnits = [.second, .minute, .hour, .day, .weekOfMonth]
dateComponentFormatter.maximumUnitCount = 1
// return the date new format as a string in the completion
completion(dateComponentFormatter.string(from: postTimeStamp!, to: date))
}
Here is my answer for the Swift 3 answers above. This is current as of Nov 2016, Xcode release was 8.2 Beta (8C23). Used some of both Sagar and Emin suggestions above and sometimes had to let Xcode autocomplete to suggest the syntax. It seemed like the syntax really changed to this beta version. buyDate I got from a DatePicker:
let calendar = NSCalendar.current as NSCalendar
let currentDate = Date()
let date1 = calendar.startOfDay(for: buyDate!)
let date2 = calendar.startOfDay(for: currentDate)
let flags = NSCalendar.Unit.day
let components = calendar.components(flags, from: date1, to: date2)
NSLog(" day= \(components.day)")
For XCode Version 8.3.3 & Swift 3.0:
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
var beginDate = "2017-08-24 12:00:00"
var endDate = "2017-09-07 12:00:00"
let startDateTime = dateFormatter.date(from: beginDate) //according to date format your date string
print(startDateTime ?? "") //Convert String to Date
let endDateTime = dateFormatter.date(from: endDate) //according to date format your date string
print(endDateTime ?? "") //Convert String to Date
let dateComponentsFormatter = DateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [NSCalendar.Unit.minute,NSCalendar.Unit.hour,NSCalendar.Unit.day]
let interval = endDateTime!.timeIntervalSince(startDateTime!)
var diff = dateComponentsFormatter.string(from: interval)!
print(diff)
var day_i = 0
var hour_i = 0
var min_i = 0
if (diff.contains("d"))
{
let day = diff.substring(to: (diff.range(of: "d")?.lowerBound)!)
day_i = Int(day)!
print ("day --> \(day_i)")
diff = diff.substring(from:(diff.range(of : " ")?.upperBound )!)
print(diff)
}
let hour = diff.substring(to: (diff.range(of : ":")?.lowerBound )!)
hour_i = Int(hour)!
print ("hour --> \(hour_i)")
let min = diff.substring(from: (diff.range(of : ":")?.upperBound )!)
min_i = Int(min)!
print ("min --> \(min_i)")
Use this code:
let registrationDateString = "2008-10-06 00:00:00"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
if let registrationDate = dateFormatter.date(from: registrationDateString) {
let currentDate = Date()
let dateDifference = Calendar.current.dateComponents([.day, .month, .year],
from: registrationDate,
to: currentDate)
print("--------------------- Result: \(dateDifference.year ?? 0) years \(dateDifference.month ?? 0) months and \(dateDifference.day ?? 0) days")
} else {
print("--------------------- No result")
}
Output is: Result: 10 years 1 months and 18 days
import Foundation
extension DateComponents {
func dateComponentsToTimeString() -> String {
var hour = "\(self.hour!)"
var minute = "\(self.minute!)"
var second = "\(self.second!)"
if self.hour! < 10 { hour = "0" + hour }
if self.minute! < 10 { minute = "0" + minute }
if self.second! < 10 { second = "0" + second }
let str = "\(hour):\(minute):\(second)"
return str
}
}
extension Date {
func offset(from date: Date)-> DateComponents {
let components = Set<Calendar.Component>([.second, .minute, .hour, .day, .month, .year])
let differenceOfDate = Calendar.current.dateComponents(components, from: date, to: self)
return differenceOfDate
}
}
Use:
var durationString: String {
return self.endTime.offset(from: self.startTime).dateComponentsToTimeString()
}
Some addition in jose920405 answer to make it compatible with Swift 3.0 and above
func getDateTimeDiff(dateStr:String) -> String {
let formatter : DateFormatter = DateFormatter()
formatter.timeZone = NSTimeZone.local
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let now = formatter.string(from: NSDate() as Date)
let startDate = formatter.date(from: dateStr)
let endDate = formatter.date(from: now)
// *** create calendar object ***
var calendar = NSCalendar.current
// *** Get components using current Local & Timezone ***
print(calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: startDate!))
// *** define calendar components to use as well Timezone to UTC ***
let unitFlags = Set<Calendar.Component>([.year, .month, .day, .hour, .minute, .second])
calendar.timeZone = TimeZone(identifier: "UTC")!
let dateComponents = calendar.dateComponents(unitFlags, from: startDate!, to: endDate!)
// *** Get Individual components from date ***
let years = dateComponents.year!
let months = dateComponents.month!
let days = dateComponents.day!
let hours = dateComponents.hour!
let minutes = dateComponents.minute!
let seconds = dateComponents.second!
var timeAgo = ""
if (seconds > 0){
if seconds < 2 {
timeAgo = "Second Ago"
}
else{
timeAgo = "\(seconds) Second Ago"
}
}
if (minutes > 0){
if minutes < 2 {
timeAgo = "Minute Ago"
}
else{
timeAgo = "\(minutes) Minutes Ago"
}
}
if(hours > 0){
if hours < 2 {
timeAgo = "Hour Ago"
}
else{
timeAgo = "\(hours) Hours Ago"
}
}
if (days > 0) {
if days < 2 {
timeAgo = "Day Ago"
}
else{
timeAgo = "\(days) Days Ago"
}
}
if(months > 0){
if months < 2 {
timeAgo = "Month Ago"
}
else{
timeAgo = "\(months) Months Ago"
}
}
if(years > 0){
if years < 2 {
timeAgo = "Year Ago"
}
else{
timeAgo = "\(years) Years Ago"
}
}
DLog("timeAgo is ===> \(timeAgo)")
return timeAgo;
}

Time converter "071953Z" (ZULU TO CURRENT TIME)

I'm using an api that gives me the following time format:
"071953Z"
07 is the day
19 is the hours
53 are the minutes
Z is Zulu time
As you can see, there is no current month and year, i also want that to be added but i dont know how.
Could you help me to convert this to the current time? Thanks!
I think your best bet is to store the partial-datetime in a DateComponents. Then you can add a month and year to it and use Foundation's calendar API to do what you want:
extension DateComponents {
static func from(str: String) -> DateComponents? {
guard str.characters.count == 7 else {
return nil
}
var components = DateComponents()
components.timeZone = TimeZone(secondsFromGMT: 0)
components.calendar = Calendar(identifier: .gregorian)
let ranges = [0,2,4].map {
return str.index(str.startIndex, offsetBy: $0)..<str.index(str.startIndex, offsetBy: $0 + 2)
}
if let day = Int(str[ranges[0]]) {
components.day = day
} else {
return nil
}
if let hour = Int(str[ranges[1]]), hour < 24 {
components.hour = hour
} else {
return nil
}
if let minute = Int(str[ranges[2]]), minute < 60 {
components.minute = minute
} else {
return nil
}
return components
}
}
// This formatter is to convert the date to your local time. Configure to taste
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .medium
if var components = DateComponents.from(str: "071953Z") {
components.year = 2017
components.month = 4
if let date = components.date {
print(formatter.string(from: date))
} else {
print("\(components) does not make a valid date")
}
}

Swift - check if a timestamp is yesterday, today, tomorrow, or X days ago

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

Resources