TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Calculating Dates and Double

Forums > Swift

I have a struct with amount and a date and then do some calculation to find the total amount based on a frequency of the amount

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: "02-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 * 7
        case .weekly:
            // zero until pay day is reached then the amount + (amount * number of payDate since dueDate)
            total += item.amount
        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
            total += item.amount * (1 / 2)
        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
            total += item.amount * (2 / 4)
        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
            // Today date is 18 Mar then there has been three Fridays
            total += item.amount * (3 / 5)
        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
            total += item.amount * (49 / 52)
        }
    }

    return total
}

print(totalItem())

I have used "Magic" numbers but would like it to work out the numbers instead

2      

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())

2      

Thank you so much. I already spent two days and just got a very complicated code just for the .daily. I only done checked that one but works well (So marking as SOLVED). Will do the other when got time. Amended slightly (not your fault) I did say Friday however it might another day! and put it a Date extension

extension Date {
    func daysUntilPayDay(of weekday: Weekday) -> Double {
        let calendar = Calendar.current
        let startOfDate = Calendar.current.startOfDay(for: self)
        let nextPayDay = calendar.nextDate(after: startOfDate, matching: DateComponents.init(weekday: weekday.rawValue), matchingPolicy: .strict)!
        return (DateInterval(start: startOfDate, end: nextPayDay).duration / 86400) + 1
    }

    func forPayDay(of weekday: Weekday, to date: Date) -> Double {
        let calendar = Calendar.current
        var days = 0.0
        calendar.enumerateDates(startingAfter: self, matching: DateComponents.init(weekday: weekday.rawValue), matchingPolicy: .strict) { result, exactMatch, stop in
            if result! > date {
                stop = true
            } else {
                days += 1
            }

        }

        return days
    }
}

So the call is

total += (item.amount * item.dueDate.daysUntilPayDay(of: .friday)) + (item.amount * item.dueDate.forPayDay(of: .friday, to: .now)  * 7)

Have an enum

enum Weekday: Int {
    case sunday = 1
    case monday
    case tuesday
    case wednesday
    case thursday
    case friday
    case saturday
}

3      

Hacking with Swift is sponsored by Superwall.

SPONSORED Superwall lets you build & test paywalls without shipping updates. Run experiments, offer sales, segment users, update locked features and more at the click of button. Best part? It's FREE for up to 250 conversions / mo and the Superwall team builds out 100% custom paywalls – free of charge.

Learn More

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.