Hi, Hopefully im not too late, i managed to work out the numbers and found an error on .monthly
, between 3 Mar and 3 Apr there are four Fridays so there has been two Fridays up to 18 Mar, besides that everything should work as expected.
enum Frequency: String, CaseIterable, Equatable {
case daily, weekly, fortnightly, fourWeekly, monthly, yearly
}
struct ExpenseItem: Identifiable, Hashable {
var id = UUID()
var name: String
var amount: Double
var dueDate: Date
var frequency: Frequency
var note: String?
}
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let dailyDate = formatter.date(from: "17-03-1923")!
let weeklyDate = formatter.date(from: "24-03-1923")!
let fortnightDate = formatter.date(from: "24-03-1923")!
let fourWeeklyDate = formatter.date(from: "31-03-1923")!
let hwsDate = formatter.date(from: "03-04-1923")!
let devDate = formatter.date(from: "12-04-1923")!
var expenseItems = [
ExpenseItem(name: "Daily", amount: 3, dueDate: dailyDate, frequency: .daily),
ExpenseItem(name: "Weekly", amount: 10.45, dueDate: weeklyDate, frequency: .weekly),
ExpenseItem(name: "Fortnightly", amount: 12.5, dueDate: fortnightDate, frequency: .fortnightly),
ExpenseItem(name: "Four weekly", amount: 100.45, dueDate: fourWeeklyDate, frequency: .fourWeekly),
ExpenseItem(name: "Hacking with Swift +", amount: 20, dueDate: hwsDate, frequency: .monthly),
ExpenseItem(name: "Developer Fee", amount: 100, dueDate: devDate, frequency: .yearly)
]
// today = 18/03/23 will be using Date.now
let now = formatter.date(from: "18-03-1923")!
func totalItem() -> Double {
let calendar = Calendar.current
var total = 0.0
for item in expenseItems {
switch item.frequency {
case .daily:
// amount times number of days to next pay day(Friday) + if not paid add following weeks
// total = amount * (days to payDate) + (amount * 7 * number of payDate since dueDate)
total += item.amount * daysUntilFriday(from: item.dueDate) + (item.amount * 7 * fridaysFrom(item.dueDate, to: now))
case .weekly:
// zero until pay day is reached then the amount + (amount * number of payDate since dueDate)
total += item.amount + (item.amount * fridaysFrom(item.dueDate, to: now))
case .fortnightly:
// the number day of week (eg Fridays) from `dueDate` minus two weeks ago divides number of a day of week (eg Fridays) in a two period
// eg dueDate is 24 Mar then between 24 Mar and 10 Mar there two Fridays
// Today date is 18 Mar then there has been one Fridays
let twoWeeksAgo = calendar.date(byAdding: Calendar.Component.weekOfMonth, value: -2, to: item.dueDate)
total += item.amount * (fridaysFrom(twoWeeksAgo!, to: now) / fridaysFrom(twoWeeksAgo!, to: item.dueDate))
case .fourWeekly:
// the number day of week (eg Fridays) from `dueDate` minus four weeks ago divides number of a day of week (eg Fridays) in a four week
// eg dueDate is 31 Mar then between 31 Mar and 10 Mar there four Fridays
// Today date is 18 Mar then there has been two Fridays
let fourWeeksAgo = calendar.date(byAdding: Calendar.Component.weekOfMonth, value: -4, to: item.dueDate)
total += item.amount * (fridaysFrom(fourWeeksAgo!, to: now) / fridaysFrom(fourWeeksAgo!, to: item.dueDate))
case .monthly:
// the number day of week (eg Fridays) from `dueDate` minus month ago divides number of a day of week (eg Fridays) in a month
// eg dueDate is 3 Apr then between 3 Mar and 3 Apr there five Fridays <-- between 3 Mar and 3 Apr there are four Fridays
// Today date is 18 Mar then there has been three Fridays <-- there has been two Fridays
let aMonthAgo = calendar.date(byAdding: Calendar.Component.month, value: -1, to: item.dueDate)
total += item.amount * (fridaysFrom(aMonthAgo!, to: now) / fridaysFrom(aMonthAgo!, to: item.dueDate))
case .yearly:
// the number day of week (eg Fridays) from `dueDate` minus year ago divides number of a day of week (eg Fridays) in a year
// eg dueDate is 3 Apr then between 3 Mar and 3 Apr there five Fridays
// Today date is 18 Mar then there has been three Fridays
let aYearAgo = calendar.date(byAdding: Calendar.Component.year, value: -1, to: item.dueDate)
total += item.amount * (fridaysFrom(aYearAgo!, to: now) / fridaysFrom(aYearAgo!, to: item.dueDate))
}
}
return total
}
func daysUntilFriday(from date: Date) -> Double {
let calendar = Calendar.current
let startOfDate = Calendar.current.startOfDay(for: date)
let nextFriday = calendar.nextDate(after: startOfDate, matching: DateComponents.init(weekday: 6), matchingPolicy: .strict)!
return (DateInterval(start: startOfDate, end: nextFriday).duration / 86400) + 1
}
func fridaysFrom(_ date1: Date, to date2: Date) -> Double {
let calendar = Calendar.current
var fridays = 0.0
calendar.enumerateDates(startingAfter: date1, matching: DateComponents.init(weekday: 6), matchingPolicy: .strict) { result, exactMatch, stop in
if result! > date2 {
stop = true
} else {
fridays += 1
}
}
return fridays
}
print(totalItem())