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"]
Related
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:
In my app my JSON contains a string with two dates.
My String
"2017-07-22 00:00:00 SSS TO 2017-07-25 00:00:00 SSS"
I want the result in my tableView's cellForRowAt function:
Expected Format:
22 Jul - 25 Jul
my code that i have tried but the finaDate gives me nil. Where am i wrong, please correct me:
let dict1 = fiidiiOverviewArr[section].date
let items = dict1?.components(separatedBy: " TO ")
let date1 = items![0]
let date2 = items![1]
print(date1)
print(date2)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMM '-' dd MMM"
let string = date1 + " - " + date2
let finalDate = dateFormatter.date(from: string)
print(finalDate)
view.Header_date.text = "\(String(describing: finalDate))"
You can create a Date and String extension for this:
DateExtension:
extension Date {
func dateToFormattedString() -> String {
var formatter: DateFormatter?
formatter = DateFormatter()
/// "dd MMM" is your expected format, you can use whatever you want
formatter?.dateFormat = "dd MMM"
var returnValue: String?
returnValue = formatter?.string(from: self)
return "\(returnValue ?? "")"
}
}
StringExtension:
extension String {
func toDate() -> Date {
var formatter: DateFormatter?
formatter = DateFormatter()
formatter?.timeZone = TimeZone(secondsFromGMT: 0)
formatter?.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let date: Date = formatter?.date(from: self) {
return date
} else {
return Date()
}
}
}
And then you should create your trimming function:
func getTrimmedDates(with dates: String) -> [String]{
return dates.replacingOccurrences(of: "SSS", with: "").components(separatedBy: "TO")
}
You should create formatter function:
func getFormattedDates(with dates: [String]) -> String {
let joinedDates = dates.compactMap({ $0.toDate().dateToFormattedString() }).joined(separator: " - ")
return joinedDates
}
USAGE:
let dates = "2017-07-22 00:00:00 SSS TO 2017-07-25 00:00:00 SSS"
let date = getTrimmedDates(with: dates)
let formattedDate = getFormattedDates(with: date)
print(formattedDate) /// You get "22 Jul - 25 Jul"
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'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.