I'm currently trying to build my first app in Swift, and am looking to find the total sum of part of an array relative to the current month. Here is my code:
struct Hour {
var date: String?
var time: String?
init(date: String?, time: String?) {
self.date = date
self.time = time
}
}
let hoursData = [
Hour(date: "Nov 29, 2015", time: "7"),
Hour(date: "Dec 12, 2015", time: "7"),
Hour(date: "Dec 14, 2015", time: "7"),
Hour(date: "Dec 25, 2015", time: "7") ]
I was wondering how I'd go about making a variable which contains the sum of the time data for the current month? Or any month for that matter? Any help you guys can give would be really appreciated.
First I'd rewrite your struct a bit,
both the properties can be immutable.
Instead of a String for date, I use a NSDate, and a double for the duration
struct Hour {
let date: NSDate
let duration: Double
init(date: NSDate, duration: Double) {
self.date = date
self.duration = duration
}
}
I create the hours like
let hoursData = [
Hour(date: { let c = NSDateComponents(); c.day = 29; c.month = 11; c.year = 2015; return NSCalendar.currentCalendar().dateFromComponents(c)!}(), duration: 7),
Hour(date: { let c = NSDateComponents(); c.day = 12; c.month = 12; c.year = 2015; return NSCalendar.currentCalendar().dateFromComponents(c)!}(), duration: 7),
Hour(date: { let c = NSDateComponents(); c.day = 14; c.month = 12; c.year = 2015; return NSCalendar.currentCalendar().dateFromComponents(c)!}(), duration: 7),
Hour(date: { let c = NSDateComponents(); c.day = 15; c.month = 12; c.year = 2015; return NSCalendar.currentCalendar().dateFromComponents(c)!}(), duration: 7)]
if you wonder about this syntax: I use implicit unnamed closures to created the NSDate parameters
now I filter for month and year an use the reduce method to sum up the filtered objects
let today = NSDate()
let totalDuration = hoursData.filter{
let objectsComps = NSCalendar.currentCalendar().components([.Month, .Year], fromDate: $0.date)
let todaysComps = NSCalendar.currentCalendar().components([.Month, .Year], fromDate: today)
return objectsComps.month == todaysComps.month && objectsComps.year == todaysComps.year
}.reduce(0) { (duration, hour) -> Double in
duration + hour.duration
}
Though it is a good answer, I want to point out that Rob's answer has a little flaw: The struct Hour hides the dependency it has that the date string need to be of a certain format. This violates the Dependency Inversion Principle, the D in the SOLID Principles. But this is easy to fix.
Just pass in the date formatter when creating the Hour instances.
struct Hour {
let date: NSDate
let duration: Double
init(date: NSDate, duration: Double) {
self.date = date
self.duration = duration
}
init (dateFormatter:NSDateFormatter, dateString:String, duration:Double) {
self.init(date: dateFormatter.dateFromString(dateString)!, duration:duration)
}
}
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMM d, y"
let hoursData = [
Hour(dateFormatter: dateFormatter, dateString: "Nov 29, 2015", duration: 7),
Hour(dateFormatter: dateFormatter, dateString: "Dec 12, 2015", duration: 7),
Hour(dateFormatter: dateFormatter, dateString: "Dec 14, 2015", duration: 7),
Hour(dateFormatter: dateFormatter, dateString: "Dec 25, 2015", duration: 7),
Hour(dateFormatter: dateFormatter, dateString: "Dec 25, 2017", duration: 7)
]
Now who ever uses Hour can define the format as needed, might be helpful in localized apps.
The filtering and reducing stays the same.
But now we have a new issue: NSDateformatter's dateFromString() might return nil. currently we force unwrap it with !, but this might be bad in a production app.
We should allow proper error handling, by allowing the convenience init to throw errors
enum HourError: ErrorType {
case InvalidDate
}
struct Hour {
let date: NSDate
let duration: Double
init(date: NSDate, duration: Double) {
self.date = date
self.duration = duration
}
init (dateFormatter:NSDateFormatter, dateString:String, duration:Double) throws {
let date = dateFormatter.dateFromString(dateString)
guard date != nil else { throw HourError.InvalidDate}
self.init(date: date!, duration:duration)
}
}
if we use it like
do {
let now = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMM d, y"
let hoursData = [
try Hour(dateFormatter: dateFormatter, dateString: "Nov 29, 2015", duration: 7),
try Hour(dateFormatter: dateFormatter, dateString: "Dec 12, 2015", duration: 7),
try Hour(dateFormatter: dateFormatter, dateString: "Dec 14, 2015", duration: 7),
try Hour(dateFormatter: dateFormatter, dateString: "Dec 25, 2015", duration: 7),
/*⛈*/ try Hour(dateFormatter: dateFormatter, dateString: " 25, 2017", duration: 7)
]
let totalDuration = hoursData.filter{
let objectsComps = NSCalendar.currentCalendar().components([.Month, .Year], fromDate: $0.date)
let todaysComps = NSCalendar.currentCalendar().components([.Month, .Year], fromDate: now)
return objectsComps.month == todaysComps.month && objectsComps.year == todaysComps.year
}.reduce(0) {
$0 + $1.duration
}
print(totalDuration)
} catch HourError.InvalidDate{
print("one or more datestrings must be wrong")
}
the error will be caught.
The full code
I might do something like:
// get prefix for current month
let formatter = NSDateFormatter()
formatter.dateFormat = "MMM"
let monthString = formatter.stringFromDate(NSDate())
// now add up the time values for that month
let results = hoursData.filter { $0.date?.hasPrefix(monthString) ?? false }
.reduce(0) { $0 + (Int($1.time ?? "0") ?? 0) }
Or, if you wanted to add a check for year, too:
let formatter = NSDateFormatter()
formatter.dateFormat = "MMM"
let monthString = formatter.stringFromDate(NSDate())
formatter.dateFormat = "yyyy"
let yearString = formatter.stringFromDate(NSDate())
let results = hoursData.filter { $0.date?.hasPrefix(monthString) ?? false && $0.date?.hasSuffix(yearString) ?? false }
.reduce(0) { $0 + (Int($1.time ?? "0") ?? 0) }
print(results)
Note, in both of the above, since you're using optionals, I'm using ?? to handle when the values are nil (as well as if there was a non-numeric string in time).
Personally, I'd suggest that Hour use NSDate and Float rather than String, not use optionals unless you really need them, and use let instead of var:
struct Hour {
let date: NSDate
let time: Float
init(dateString: String, time: Float) {
let formatter = NSDateFormatter()
formatter.dateFormat = "MMM, d, y"
self.date = formatter.dateFromString(dateString)!
self.time = time
}
}
Then the code becomes:
let hoursData = [
Hour(dateString: "Nov 29, 2015", time: 7),
Hour(dateString: "Dec 12, 2015", time: 7),
Hour(dateString: "Dec 14, 2015", time: 7),
Hour(dateString: "Dec 25, 2015", time: 7),
Hour(dateString: "Dec 25, 2017", time: 7)
]
let calendar = NSCalendar.currentCalendar()
let currentComponents = calendar.components([.Month, .Year], fromDate: NSDate())
let results = hoursData.filter {
let components = calendar.components([.Month, .Year], fromDate: $0.date)
return components.month == currentComponents.month && components.year == currentComponents.year
}.reduce(0) { return $0 + $1.time }
print(results)
There are further optimizations possible (e.g. not repeated instantiate NSDateFormatter), but it illustrates the idea that using NSDate objects lets you use calendrical calculations, rather than looking for substrings.
You could also do it functionally
let sum = hoursData
.filter { $0.date?.hasPrefix("Dec") ?? false } // Only get dates in the correct month
.flatMap { $0.time } // Map to an array of non nil times
.flatMap { Int($0) } // Convert strings to ints
.reduce(0) { $0 + $1 } // Sum up the times
This can be a lot simpler if you store non optional Ints instead of optional Strings. Or you could just use NSDateComponents.
Related
I am trying to build a swiftui app, i am trying to get the hours from the dates, so my dates are: 09.12.2022 09:00-09.13.2022 09:00
I have got it to tell me that there is 24 hours between the dates but i want to show the dates like 09:00,10:00,11:00,12:00,13:00 and so on until it reaches the closing date
this is my code so far. Its returning the hours to build a foreachloop on my screen.
func getHours(times:String)-> Int{
let fullNameArr : [String] = times.components(separatedBy: "-")
print(fullNameArr[0])
print(fullNameArr[1])
// Create Date Formatter
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM.dd.yyyy HH:mm"
let date1 = dateFormatter.date(from: fullNameArr[0])!
let date2 = dateFormatter.date(from: fullNameArr[1])!
let cal = Calendar.current
let components = cal.dateComponents([.hour], from: date1, to: date2)
let diff = components.hour!
for i in stride(from: 0, to: diff, by: 1) {
print(i)
}
return diff
}
What you need is to use Calendar method nextDate(after:) and match component minute equal to zero:
func getHours(times: String)-> [String] {
let components = times.components(separatedBy: "-")
guard components.count == 2,
var start = DateFormatter.custom.date(from: components[0]),
let end = DateFormatter.custom.date(from: components[1]),
start < end else {
return []
}
var dates: [Date] = []
if start.minute == 0 {
dates.append(start)
}
while start < end {
guard let date = Calendar(identifier: .iso8601).nextDate(after: start, matching: DateComponents(minute: 0), matchingPolicy: .strict) else {
continue
}
if date < end { // If you want to include the end date as well just remove this condition
dates.append(date)
}
start = date
}
return dates.map(DateFormatter.time.string)
}
You will need those date formatters
extension DateFormatter {
static let custom: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.calendar = .init(identifier: .iso8601)
dateFormatter.locale = .init(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "MM.dd.yyyy HH:mm"
return dateFormatter
}()
static let time: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.calendar = .init(identifier: .iso8601)
dateFormatter.locale = .init(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "HH:mm"
return dateFormatter
}()
}
And this Date extension:
extension Date {
var minute: Int {
Calendar(identifier: .iso8601).component(.minute, from: self)
}
}
usage:
let times = "09.12.2022 09:00-09.13.2022 09:00"
print(getHours(times: times))
This will print
["09:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00"]
You already know how to get the hour difference:
let endDate = Date()
let startDate = endDate - 24.0 * 60.0 * 60.0
let diffHours = Calendar.current
.dateComponents([.hour], from: startDate, to: endDate)
Now we are going to make a Range of hours from zero to diffHours. Then we are going to map that range of hours to an Array of Date ranging from startDate to endDate Then we are going to map to an Array of hour values. Then we are going to map to an Array of String formatted as 01:00.
let hours = (0...(diffHours.hour ?? 0))
.compactMap { Calendar.current.date(byAdding: .hour, value: $0, to: startDate) }
.map { Calendar.current.component(.hour, from: $0) }
.map { String(format: "%02d:00", $0) }
%d tells the formatter you want an integer
%02d tells the formatter you want two digits with a leading zero
Now let's see what we have:
print("ZZZ range:", (startDate..<endDate).formatted())
print("ZZZ hours:", hours)
Prints:
ZZZ range: 6/17/22, 1:50 PM – 6/18/22, 1:50 PM
ZZZ hours: ["13:00", "14:00", "15:00", "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00"]
So far my solution is to just translate the dates into formatted strings and sort those.
e.g:
extension Date {
// Helper
func formatted(_ format: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: self)
}
}
let sortedDates = myDates.sorted { $0.formatted("MM/dd") < $1.formatted("MM/dd") }
Is there a better way of achieving this without dealing with strings?
You can create a struct MonthDay that has integer values for month/day and also make it conform to Comparable for it to be used within sort logic.
import Foundation
extension Date {
struct MonthDay: Comparable {
let month: Int
let day: Int
init(date: Date) {
let comps = Calendar.current.dateComponents([.month,.day], from: date)
self.month = comps.month ?? 0
self.day = comps.day ?? 0
}
static func <(lhs: MonthDay, rhs: MonthDay) -> Bool {
return (lhs.month < rhs.month || (lhs.month == rhs.month && lhs.day < rhs.day))
}
}
}
With above in place, you can sort easily by reading month/day values from Date like following.
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date1 = dateFormatter.date(from: "2020-07-30")!
let date2 = dateFormatter.date(from: "2021-07-01")!
let date3 = dateFormatter.date(from: "2019-07-28")!
let date4 = dateFormatter.date(from: "2021-06-30")!
let date5 = dateFormatter.date(from: "2016-10-13")!
let dates = [date1, date2, date3, date4, date5]
print(dates.map { dateFormatter.string(from: $0) })
// ["2020-07-30", "2021-07-01", "2019-07-28", "2021-06-30", "2016-10-13"]
let sorted = dates.sorted(by: {
Date.MonthDay(date: $0) < Date.MonthDay(date: $1)
})
print(sorted.map { dateFormatter.string(from: $0) })
// ["2021-06-30", "2021-07-01", "2019-07-28", "2020-07-30", "2016-10-13"]
Here is a solution for Swift 5.5/Xcode 13 beta if anyone wants to try it out.
let monthDay = Date.FormatStyle()
.day(.twoDigits)
.month(.twoDigits)
.locale(Locale(identifier: "en_US_POSIX"))
let sortedDates = myDates
.map { ($0.formatted(monthDay), $0) }
.sorted { $0.0 < $1.0 }
.map(\.1)
Example
let day = 24.0 * 60.0 * 60.0
let month = day * 31
let year = day * 365
let myDates: [Date] = [.now,
.now.advanced(by: (year + day)),
.now.advanced(by: -(year + day)),
.now.advanced(by: -3 * day),
.now.advanced(by: 3 * day)
]
yields
[2021-06-30 09:57:51 +0000, 2020-07-02 09:57:51 +0000, 2021-07-03 09:57:51 +0000, 2022-07-04 09:57:51 +0000, 2021-07-06 09:57:51 +0000]
If you want to avoid making new objects every time you go through the sort block then you could make a Dictionary, with the original Date as the Key and the Day/Month components as the value. This can can then be sorted and returned as an Array of the original dates.
func sortDatesByDayMonth(_ dates: [Date]) -> [Date] {
let calendar = Calendar.current
var datesWithDayMonthOnly = [Date : Date]()
for date in dates {
var dateComponents = DateComponents()
dateComponents = calendar.dateComponents([.day, .month], from: date)
let dayMonthDate = calendar.date(from: dateComponents)
datesWithDayMonthOnly[date] = dayMonthDate
}
return datesWithDayMonthOnly.sorted { $0.1 < $1.1 }.map { $0.key }
}
Or as an Array of Tuples, which would handle the rare case that two dates are precisely the same:
func sortDatesByDayMonth(_ dates: [Date]) -> [Date] {
let calendar = Calendar.current
var datesWithDayMonthOnly = [(date: Date, dayMonthDate: Date)]()
for date in dates {
var dateComponents = DateComponents()
dateComponents = calendar.dateComponents([.day, .month], from: date)
if let dayMonthDate = calendar.date(from: dateComponents) {
datesWithDayMonthOnly.append((date: date, dayMonthDate: dayMonthDate))
}
}
return datesWithDayMonthOnly.sorted { $0.dayMonthDate < $1.dayMonthDate }.map { $0.date }
}
Example:
var dates = [Date]()
for _ in 1...20 {
dates.append(Date(timeIntervalSince1970: Double.random(in: 0...Date().timeIntervalSince1970)))
}
sortDatesByDayMonth(dates)
Example Output:
[2005-01-08 18:26:45 +0000,
1994-01-13 15:13:10 +0000,
1986-01-26 03:48:43 +0000,
1992-02-15 10:40:06 +0000,
2005-03-20 12:33:41 +0000,
1998-04-04 13:23:33 +0000,
1982-04-25 17:21:55 +0000,
1976-05-16 20:59:00 +0000,
1995-05-20 03:53:41 +0000,
2006-07-23 06:50:19 +0000,
2017-08-23 08:16:49 +0000,
1989-08-30 17:47:49 +0000,
1986-09-07 03:05:49 +0000,
1990-09-08 16:09:47 +0000,
2007-10-14 03:46:24 +0000,
1974-10-16 16:06:53 +0000,
1977-11-04 14:15:43 +0000,
1974-11-09 08:55:11 +0000,
2011-12-02 07:23:57 +0000,
1983-12-31 00:22:57 +0000]
let date1 = DateComponents(calendar: .current, year: 2022, month: 07, day: 28, hour: 0, minute: 0).date!
let date2 = Date()
let date3 = DateComponents(calendar: .current, year: 1997, month: 06, day: 22, hour: 0, minute: 0).date!
let date4 = DateComponents(calendar: .current, year: 2000, month: 07, day: 02, hour: 0, minute: 0).date!
let date5 = DateComponents(calendar: .current, year: 1997, month: 07, day: 28, hour: 0, minute: 0).date!
let date6 = DateComponents(calendar: .current, year: 2050, month: 08, day: 28, hour: 0, minute: 0).date!
let calendar = Calendar.current
let components = calendar.dateComponents([.month, .day], from: date1)
let components2 = calendar.dateComponents([.month, .day], from: date2)
let components3 = calendar.dateComponents([.month, .day], from: date3)
let components4 = calendar.dateComponents([.month, .day], from: date4)
let components5 = calendar.dateComponents([.month, .day], from: date5)
let components6 = calendar.dateComponents([.month, .day], from: date6)
var arrDate = [components,components2,components3,components4,components5,components6]
arrDate.sort { lhs, rhs in
if lhs.month! > rhs.month! {
return true
}else if lhs.month! < rhs.month!{
return false
}else if lhs.month! == rhs.month!{
if lhs.day! > rhs.day! {
return true
}else if lhs.day! < rhs.day! {
return false
}else {
return false
}
}
return true
}
print(arrDate)
check it this way :)
For example, I want to take the day numbers of January and the week names corresponding to this day. How can I get this?
I want to show the week's names under the day numbers.
I get the days of the months with the getNumbersOfMonths function.
Part to be noted: The first day of January 2021 is Friday. The first day of February is Monday.
Date Provider
class DateProvider: ObservableObject {
#Published var months: [String] = []
#Published var dateData: DateType = DateType(day: "", date: "", year: "", month: "")
init() {
months = getMonths()
dateData = getCurrentDate()
}
func getCurrentDate() -> DateType {
let date = Date()
var currentDate = Calendar.current
currentDate.locale = NSLocale(localeIdentifier: "tr_TR") as Locale
let dateString = currentDate.component(.day, from: date)
let monthNo = currentDate.component(.month, from: date)
let month = currentDate.monthSymbols[monthNo - 1]
let year = currentDate.component(.year, from: date)
let weekNo = currentDate.component(.weekday, from: date)
let day = currentDate.weekdaySymbols[weekNo - 1]
return DateType(day: "\(day)", date: "\(dateString)", year: "\(year)", month: "\(month)")
}
func getMonths() -> [String] {
let formatter = DateFormatter()
formatter.locale = NSLocale(localeIdentifier: "tr_TR") as Locale
let monthComponents = formatter.shortMonthSymbols ?? []
return monthComponents
}
func getNumbersOfMonths(month: Int, year: Int) -> Int {
let dateComponents = DateComponents(year: year, month: month)
let calendar = Calendar.current
let date = calendar.date(from: dateComponents)!
let range = calendar.range(of: .day, in: .month, for: date)!
let numDays = range.count
return numDays + 1
}
}
ContentView
struct ContentView: View {
#State var data: DateType = DateType(day: "", date: "", year: "", month: "")
#ObservedObject var dateProvider: DateProvider = DateProvider()
var body: some View {
ScrollView {
VStack {
VStack {
Text(self.dateProvider.dateData.day)
Text(self.dateProvider.dateData.date)
Text(self.dateProvider.dateData.month)
Text(self.dateProvider.dateData.year)
VStack {
ForEach(dateProvider.months.indices, id: \.self) { index in
Text("\(dateProvider.months[index])")
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 7), spacing: 10) {
ForEach(1 ..< dateProvider.getNumbersOfMonths(month: index + 1, year: 2021)) { item in
Text("\(item)")
}
}
.padding(.vertical)
}
}
}
}
}
}
}
Model
struct DateType {
var day: String
var date: String
var year: String
var month: String
}
To get the weekday abbreviation, like "Mon," you need to first get a Date consisting of the year, month, and day components.
Get year from self.dateProvider.dateData.year
Get month from index in the first loop (ForEach(dateProvider.months.indices...)
Get day from item in the second loop (ForEach(1 ..< dateProvider.getNumbersOfMonths...)
Then, add this function to your DateProvider class. This assembles a date from year, month, and day, then returns the weekday abbreviation.
func getWeekday(year: Int, month: Int, day: Int) -> String {
var components = DateComponents()
components.year = year
components.month = month
components.day = day
let formatter = DateFormatter()
formatter.dateFormat = "E" /// see https://nsdateformatter.com/
if let date = Calendar.current.date(from: components) {
let string = formatter.string(from: date)
return string
}
return "Mon"
}
Finally, call the getWeekday function, passing in the date components.
ForEach(1 ..< dateProvider.getNumbersOfMonths(month: index + 1, year: 2021)) { item in
VStack {
Text("\(item)")
/// display weekday!
Text("\(dateProvider.getWeekday(year: Int(dateProvider.dateData.year) ?? 2021, month: index, day: item))")
.foregroundColor(.red)
.font(.system(size: 12))
}
}
Result:
I have two dates i.e. 2/02/2016 & 19/03/2018
i am trying to fetch months & year between this dates as
output should be
Feb 2016, Mar 2016 ......and so on.... Jan 2018, Feb 2018, Mar 2018.
Tried month Gap code -
let date1 = DateComponents(calendar: .current, year: 2016, month: 2, day: 2).date!
let date2 = DateComponents(calendar: .current, year: 2018, month: 3, day: 19).date!
let monthGap = Calendar.current.dateComponents([.month], from: date1, to: date2)
print("monthGap is \(String(describing: monthGap.month))")//prints monthGap is 25
Here is a simple method that returns you the date and month in the format as you described above,
func getMonthAndYearBetween(from start: String, to end: String) -> [String] {
let format = DateFormatter()
format.dateFormat = "dd/MM/yyyy"
guard let startDate = format.date(from: start),
let endDate = format.date(from: end) else {
return []
}
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents(Set([.month]), from: startDate, to: endDate)
var allDates: [String] = []
let dateRangeFormatter = DateFormatter()
dateRangeFormatter.dateFormat = "MMM yyyy"
for i in 0 ... components.month! {
guard let date = calendar.date(byAdding: .month, value: i, to: startDate) else {
continue
}
let formattedDate = dateRangeFormatter.string(from: date)
allDates += [formattedDate]
}
return allDates
}
And you call it like,
getMonthAndYearBetween(from: "2/02/2016", to: "19/03/2018")
Swift 5
Based on Sandeep's answer, this function takes Date objects for input & will returns an array of Date objects:
extension Date {
static func getMonthAndYearBetween(from start: Date, to end: Date) -> [Date] {
var allDates: [Date] = []
guard start < end else { return allDates }
let calendar = Calendar.current
let month = calendar.dateComponents([.month], from: start, to: end).month ?? 0
for i in 0...month {
if let date = calendar.date(byAdding: .month, value: i, to: start) {
allDates.append(date)
}
}
return allDates
}
}
I have been researching, but I couldnt find exact solution for my problem. I have been trying to get 1 hour ago from a date. How can I achieve this in swift?
For correct calculations involving NSDate that take into account all edge cases of different calendars (e.g. switching between day saving time) you should use NSCalendar class:
Swift 3+
let earlyDate = Calendar.current.date(
byAdding: .hour,
value: -1,
to: Date())
Older
// Get the date that was 1hr before now
let earlyDate = NSCalendar.currentCalendar().dateByAddingUnit(
.Hour,
value: -1,
toDate: NSDate(),
options: [])
Use this method and paste in your helper class.
Updated for Swift 3 and Xcode 8.3
class func timeAgoSinceDate(_ date:Date,currentDate:Date, numericDates:Bool) -> String {
let calendar = Calendar.current
let now = currentDate
let earliest = (now as NSDate).earlierDate(date)
let latest = (earliest == now) ? date : now
let components:DateComponents = (calendar as NSCalendar).components([NSCalendar.Unit.minute , NSCalendar.Unit.hour , NSCalendar.Unit.day , NSCalendar.Unit.weekOfYear , NSCalendar.Unit.month , NSCalendar.Unit.year , NSCalendar.Unit.second], from: earliest, to: latest, options: NSCalendar.Options())
if (components.year! >= 2) {
return "\(components.year!) years ago"
} else if (components.year! >= 1){
if (numericDates){
return "1 year ago"
} else {
return "Last year"
}
} else if (components.month! >= 2) {
return "\(components.month!) months ago"
} else if (components.month! >= 1){
if (numericDates){
return "1 month ago"
} else {
return "Last month"
}
} else if (components.weekOfYear! >= 2) {
return "\(components.weekOfYear!) weeks ago"
} else if (components.weekOfYear! >= 1){
if (numericDates){
return "1 week ago"
} else {
return "Last week"
}
} else if (components.day! >= 2) {
return "\(components.day!) days ago"
} else if (components.day! >= 1){
if (numericDates){
return "1 day ago"
} else {
return "Yesterday"
}
} else if (components.hour! >= 2) {
return "\(components.hour!) hours ago"
} else if (components.hour! >= 1){
if (numericDates){
return "1 hour ago"
} else {
return "An hour ago"
}
} else if (components.minute! >= 2) {
return "\(components.minute!) minutes ago"
} else if (components.minute! >= 1){
if (numericDates){
return "1 minute ago"
} else {
return "A minute ago"
}
} else if (components.second! >= 3) {
return "\(components.second!) seconds ago"
} else {
return "Just now"
}
}
Use of this method:
var timeAgo:String=AppHelper.timeAgoSinceDate(date, numericDates: true)
Print("\(timeAgo)") // Ex- 1 hour ago
Please read the NSDate class reference.
let oneHourAgo = NSDate.dateWithTimeIntervalSinceNow(-3600)
should do it.
Or, for any NSDate object:
let oneHourBack = myDate.dateByAddingTimeInterval(-3600)
Swift 4:
let oneHourAgo = NSDate(timeIntervalSinceNow: -3600)
According to your needs, you may choose one of the 3 following Swift 5 methods to get one hour ago from a Date instance.
1. date(byAdding:value:to:wrappingComponents:)
Calendar has a method called date(byAdding:value:to:wrappingComponents:). date(byAdding:value:to:wrappingComponents:) has the following declaration:
func date(byAdding component: Calendar.Component, value: Int, to date: Date, wrappingComponents: Bool = default) -> Date?
Returns a new Date representing the date calculated by adding an amount of a specific component to a given date.
The Playground code below shows how to use it:
import Foundation
let now = Date()
let oneHourAgo = Calendar.current.date(byAdding: .hour, value: -1, to: now)
print(now) // 2016-12-19 21:52:04 +0000
print(String(describing: oneHourAgo)) // Optional(2016-12-19 20:52:04 +0000)
2. date(byAdding:to:wrappingComponents:)
Calendar has a method called date(byAdding:to:wrappingComponents:). date(byAdding:value:to:wrappingComponents:) has the following declaration:
func date(byAdding components: DateComponents, to date: Date, wrappingComponents: Bool = default) -> Date?
Returns a new Date representing the date calculated by adding components to a given date.
The Playground code below shows how to use it:
import Foundation
let now = Date()
var components = DateComponents()
components.hour = -1
let oneHourAgo = Calendar.current.date(byAdding: components, to: now)
print(now) // 2016-12-19 21:52:04 +0000
print(String(describing: oneHourAgo)) // Optional(2016-12-19 20:52:04 +0000)
Alternative:
import Foundation
// Get the date that was 1hr before now
let now = Date()
let components = DateComponents(hour: -1)
let oneHourAgo = Calendar.current.date(byAdding: components, to: now)
print(now) // 2016-12-19 21:52:04 +0000
print(String(describing: oneHourAgo)) // Optional(2016-12-19 20:52:04 +0000)
3. addingTimeInterval(_:) (use with caution)
Date has a method called addingTimeInterval(_:). addingTimeInterval(_:) has the following declaration:
func addingTimeInterval(_ timeInterval: TimeInterval) -> Date
Return a new Date by adding a TimeInterval to this Date.
Note that this method comes with a warning:
This only adjusts an absolute value. If you wish to add calendrical concepts like hours, days, months then you must use a Calendar. That will take into account complexities like daylight saving time, months with different numbers of days, and more.
The Playground code below shows how to use it:
import Foundation
let now = Date()
let oneHourAgo = now.addingTimeInterval(-3600)
print(now) // 2016-12-19 21:52:04 +0000
print(oneHourAgo) // 2016-12-19 20:52:04 +0000
If you are using NSDate you can do:
let date = NSDate()
date.dateByAddingTimeInterval(-3600)
It will change the date object to be "1 hour ago".
Swift3:
let now = Date()
let tempCalendar = Calendar.current
let alteredDate = tempCalendar.date(byAdding: .hour, value: -1, to: now)
I achieve time ago functionality by creating extension of Date. Which is given below:
extension Date {
// Returns the number of years
func yearsCount(from date: Date) -> Int {
return Calendar.current.dateComponents([.year], from: date, to: self).year ?? 0
}
// Returns the number of months
func monthsCount(from date: Date) -> Int {
return Calendar.current.dateComponents([.month], from: date, to: self).month ?? 0
}
// Returns the number of weeks
func weeksCount(from date: Date) -> Int {
return Calendar.current.dateComponents([.weekOfMonth], from: date, to: self).weekOfMonth ?? 0
}
// Returns the number of days
func daysCount(from date: Date) -> Int {
return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
}
// Returns the number of hours
func hoursCount(from date: Date) -> Int {
return Calendar.current.dateComponents([.hour], from: date, to: self).hour ?? 0
}
// Returns the number of minutes
func minutesCount(from date: Date) -> Int {
return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
}
// Returns the number of seconds
func secondsCount(from date: Date) -> Int {
return Calendar.current.dateComponents([.second], from: date, to: self).second ?? 0
}
// Returns time ago by checking if the time differences between two dates are in year or months or weeks or days or hours or minutes or seconds
func timeAgo(from date: Date) -> String {
if yearsCount(from: date) > 0 { return "\(yearsCount(from: date))years ago" }
if monthsCount(from: date) > 0 { return "\(monthsCount(from: date))months ago" }
if weeksCount(from: date) > 0 { return "\(weeksCount(from: date))weeks ago" }
if daysCount(from: date) > 0 { return "\(daysCount(from: date))days ago" }
if hoursCount(from: date) > 0 { return "\(hoursCount(from: date))hours ago" }
if minutesCount(from: date) > 0 { return "\(minutesCount(from: date))minutes ago" }
if secondsCount(from: date) > 0 { return "\(secondsCount(from: date))seconds ago" }
return ""
}
}
Then i get the time ago by calculating the difference between current date and specified date:
let timeAgo = Date().timeAgo(from: sender.date)
You can also use an operator
let date = Date()
let anHourAgo = date - TimeInterval(3600.0)
Apple Docs:
https://developer.apple.com/documentation/foundation/date/2293436
for Swift 2:
extension NSDate {
func after(value: Int, calendarUnit:NSCalendarUnit) -> NSDate {
return calendar.dateByAddingUnit(calendarUnit, value: value, toDate: self, options: [])!
}
}
how to use:
let lastHour = NSDate().after(-1, calendarUnit: .Hour)
This works for iOS 13 / Swift 5. Credit goes to Sourabh Sharma
func timeAgoSinceNow(numericDates: Bool = true) -> String {
let calendar = Calendar.current
let now = Date()
let earliest = (now as NSDate).earlierDate(self)
let latest = (earliest == now) ? self : now
let components: DateComponents = (calendar as NSCalendar).components([NSCalendar.Unit.minute,
NSCalendar.Unit.hour,
NSCalendar.Unit.day,
NSCalendar.Unit.weekOfYear,
NSCalendar.Unit.month,
NSCalendar.Unit.year,
NSCalendar.Unit.second],
from: earliest,
to: latest,
options: NSCalendar.Options())
guard
let year = components.year,
let month = components.month,
let weekOfYear = components.weekOfYear,
let day = components.day,
let hour = components.hour,
let minute = components.minute,
let second = components.second
else { return "A while ago"}
if year >= 1 {
return year >= 2 ? "\(year) years ago" : numericDates ? "1 year ago" : "Last year"
} else if month >= 1 {
return month >= 2 ? "\(month) months ago" : numericDates ? "1 month ago" : "Last month"
} else if weekOfYear >= 1 {
return weekOfYear >= 2 ? "\(weekOfYear) weeks ago" : numericDates ? "1 week ago" : "Last week"
} else if day >= 1 {
return day >= 2 ? "\(day) days ago" : numericDates ? "1 day ago" : "Yesterday"
} else if hour >= 1 {
return hour >= 2 ? "\(hour) hours ago" : numericDates ? "1 hour ago" : "An hour ago"
} else if minute >= 1 {
return minute >= 2 ? "\(minute) minutes ago" : numericDates ? "1 minute ago" : "A minute ago"
} else {
return second >= 3 ? "\(second) seconds ago" : "Just now"
}
}
Usage:
var date = Date() // Or any date you wish to convert to text
print("\(date.timeAgoSinceNow())") // "Just Now"
Calendar.current.date( byAdding: .hour, value: -1, to: Date())
In swift 5 You can use
let earlyDate = Calendar.current.date( byAdding: .hour, value: -1, to: Date())
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd HH:mm:ss"
let dateString = df.string(from: earlyDate!)
Out put will Be like bellow
Current DateTime--> 2019-12-20 09:40:08
One Hour Previous Date Time--> 2019-12-20 08:40:08
Details
Xcode 11.4 (11E146), Swift 5.2
Solution
import Foundation
extension Date {
typealias Component = (value: Int, type: Calendar.Component)
init?(bySubtracting components: Component..., calendar: Calendar = Calendar.current, from date: Date) {
guard let date = date.subtract(components, calendar: calendar) else { return nil }
self = date
}
func subtract(_ components: Component..., calendar: Calendar = Calendar.current) -> Date? {
subtract(components, calendar: calendar)
}
func subtract(_ components: [Component], calendar: Calendar = Calendar.current) -> Date? {
components.reduce(self) { (result, component) -> Date? in
guard let date = result else { return nil }
return calendar.date(byAdding: component.type, value: (-1)*component.value, to: date)
}
}
static func beforeNow(difference component: Component..., calendar: Calendar = Calendar.current) -> Date? {
Date().subtract(component, calendar: calendar)
}
static func beforeNow(difference component: [Component], calendar: Calendar = Calendar.current) -> Date? {
Date().subtract(component, calendar: calendar)
}
}
extension Date {
static func - (date: Date, component: Date.Component) -> Date? { date.subtract(component, calendar: Calendar.current) }
static func -= (date: inout Date, component: Date.Component) {
guard let newDate = date.subtract(component, calendar: Calendar.current) else { return }
date = newDate
}
}
Usage
var currentDate = Date()
let date1 = Date(bySubtracting: (30, .day), from: currentDate)
let date2 = Date().subtract((30, .day))
let date3 = Date().subtract([(1, .minute), (2, .hour), (3, .day), (4, .month)])
let component = Date.Component(value: 1, type: .day)
let date4 = Date.beforeNow(difference: component)
let date5 = Date.beforeNow(difference: (1, .minute), (2, .hour), (3, .day), (4, .month))
let date6 = Date.beforeNow(difference: [(1, .minute), (2, .hour), (3, .day), (4, .month)])
let date7 = currentDate - (1, .day)
currentDate -= Date.Component(value: 1, type: .day)
More
Get components (hours, months, days...) from date
Compare days by components
Convert date to formated string date
let now = Date()
let nowMinusTwoAndAHalfHours = now - 2.5*60*60
print(now)
print(nowMinusTwoAndAHalfHours)