swiftdatedatepickerrecurrenceswiftdata

In Swift 5, how do I increment a date property by some interval and then run the function with that new future date?


I'm trying to pick a future date based on the current date. I want to use some sort of time interval ie: day, month, year etc.

I wrote code and recurrence function. I get the right number of recurring dates but they are all the same: Date.now See the resulting view below: iPhone preview screenshot

Can someone please tell me about Calendar and how to advance date and time correctly in Apple products?

import SwiftUI
import SwiftData

var payFrequencies = [ "once", "daily", "weekly", "biweekly", "monthly", "quarterly", "semiannually", "annually"]

struct PayPeriodCalculation: View {
    
    enum Recurrence {
        case daily, weekly, biweekly, monthly, quarterly, semiannually, annually
    }
    @Environment(\.dismiss) private var dismiss
    @Environment(\.modelContext) private var context
    
    @State private var budgetPayDate: Date = .now
    @State private var budgetPayPurpose: String = ""
    @State private var budgetPayCounterpart: String = ""
    @State private var budgetPayDirection: String = ""
    @State private var budgetPayAmount: Double = 0.00
    @State private var budgetPayMethod: String = "cash"
    @State private var budgetPayFrequency: String =  "once"
    @State private var budgetPayControl: String = "at will"
    @State private var budgetPayPurposeCategory: String = "miscellaneous"
    @State private var budgetIsBudgetTransaction: Bool = true
    
    var budgetTransaction: Transaction
    
    func saveBudgetTransaction(budgetTransaction: Transaction, context: ModelContext) {
        let trxToSave = Transaction(payDate: budgetPayDate, payPurpose: budgetPayPurpose, payCounterpart: budgetPayCounterpart, payDirection: budgetPayDirection, payAmount: budgetPayAmount, payMethod: budgetPayMethod, payFrequency: budgetPayFrequency, payControl: budgetPayControl, payPurposeCategory: budgetPayPurposeCategory, isBudgetTransaction: budgetIsBudgetTransaction)
        
        context.insert(trxToSave)
    }

func oneDay() -> TimeInterval {
        let oneDay: TimeInterval = 24*60*60
        return oneDay
    }
    
    func oneWeek() -> TimeInterval {
        let oneWeek: TimeInterval = 7*24*60*60
        return oneWeek
    }
    
    func twoWeeks() -> TimeInterval{
        let twoWeeks: TimeInterval = 14*24*60*60
        return twoWeeks
    }
    
    func oneMonth() -> TimeInterval {
        let oneMonth: TimeInterval = 30*24*60*60
        return oneMonth
    }
    
    func oneQuarter() -> TimeInterval {
        let oneQuarter: TimeInterval = 3*30*24*60*60
        return oneQuarter
    }
    
    func halfYear() -> TimeInterval {
        let halfYear: TimeInterval = 6*30*24*60*60
        return halfYear
    }

func thisTrxSave() {
let budgetTransaction = Transaction(payDate: budgetPayDate, payPurpose: budgetPayPurpose, payCounterpart: budgetPayCounterpart, payDirection: budgetPayDirection, payAmount: budgetPayAmount, payMethod: budgetPayMethod, payFrequency: budgetPayFrequency, payControl: budgetPayControl, payPurposeCategory: budgetPayPurposeCategory, isBudgetTransaction: budgetIsBudgetTransaction)
            saveBudgetTransaction(budgetTransaction: budgetTransaction, context: context)
        
    }

func calculateNumberOfBudgetTransactions() -> [Date] {
        var budgetPayDates: [Date] = []
        
        switch budgetPayFrequency {
        case "daily" :
            thisTrxSave()
            budgetPayDates.append(budgetPayDate)
            for _ in 0...363 {
                budgetPayDate = budgetPayDate + oneDay()
                thisTrxSave()
                budgetPayDates.append(budgetPayDate)
            }
        case "weekly" :
            thisTrxSave()
            budgetPayDates.append(budgetPayDate)
            for _ in 0...50 {
                budgetPayDate = budgetPayDate + oneWeek()
                thisTrxSave()
                budgetPayDates.append(budgetPayDate)
            }
        case "biweekly" :
            thisTrxSave()
            budgetPayDates.append(budgetPayDate)
            for _ in 0...24 {
                budgetPayDate = budgetPayDate + twoWeeks()
                thisTrxSave()
                budgetPayDates.append(budgetPayDate)
            }
            
        case "quarterly" :
            thisTrxSave()
            budgetPayDates.append(budgetTransaction.payDate)
            for _ in 0...2 {
                budgetPayDate = budgetPayDate + oneQuarter()
                thisTrxSave()
                budgetPayDates.append(budgetTransaction.payDate)
            }
                
        case "semiannually" :
            thisTrxSave()
            budgetPayDates.append(budgetTransaction.payDate)
                budgetPayDate = budgetPayDate + halfYear()
                thisTrxSave()
                budgetPayDates.append(budgetTransaction.payDate)
            
        case "annually" :
            thisTrxSave()
            budgetPayDates.append(budgetTransaction.payDate)
        
        default :
            // monthly
            thisTrxSave()
            budgetPayDates.append(budgetTransaction.payDate)
            for _ in 0...10 {
                budgetPayDate = budgetPayDate + oneMonth()
                thisTrxSave()
                budgetPayDates.append(budgetTransaction.payDate)
            }
        }
        return budgetPayDates
    }
    
 
    var getDateParts = Date.getDateParts(.now)
    
    var body: some View {
        Form {
            Section {
                Section {
                    Picker("Recur", selection: $budgetPayFrequency){
                        ForEach(payFrequencies, id: \.self) {
                            Text($0)
                        }
                    }.frame(maxWidth: .infinity)
                }
            }
        }
        
        List {
            VStack{
                let budPayDates = calculateNumberOfBudgetTransactions()
                if budgetPayFrequency != "once" {
                    
                    Text("\(budPayDates.count)")
                    
                    ForEach(budPayDates, id: \.self) {payDate in
                        
                        Text("\(payDate)")
                        
                    }
                } else {
                    Text("nothing")
                }
            }
        }
        }
    }
    
struct PayPeriodCalculationContainer: View {
    
    @Query(sort: \Transaction.payPurpose) private var transactions: [Transaction]
    
    // MARK: *BUDGET*
    private var budgetTransactions: [Transaction] {
        transactions.filter{ $0.isBudgetTransaction == true }
    }
    
    var body: some View {
        PayPeriodCalculation(budgetTransaction: budgetTransactions[0])
    }
}

#Preview {
    NavigationStack {
        PayPeriodCalculationContainer()
            .modelContainer(previewContainer)
    }
}


Solution

  • Dates are very hard to work with directly as dates are kept differently around the world. You need to be using Calendar when advancing date and time inside of Apple Frameworks.
    example:

    Calendar.autoupdatingCurrent.date(byAdding: .day, value: 1, to: .now, wrappingComponents: true)

    Check out the docs here Calendar

    The code above creates a new Date by adding 1 day to now using the calendar provided by the system for the users device.