You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 5中基于Calendar正确递增日期及实现重复事务的问题求助

问题分析与解决方案

你的代码生成的重复事务日期全部为当前时间,核心原因有两个:

  1. 部分周期分支(季度、半年、月度)错误地将原始budgetTransaction.payDate添加到结果数组,而非更新后的日期;
  2. 用固定秒数计算月/季度等时间间隔不严谨,且不符合Apple生态推荐的日期处理规范,应该用Calendar框架来处理日期推进。

Apple平台正确的日期推进方法

在iOS/macOS等Apple平台,处理日期增减必须使用Calendar类,它会自动处理不同月份天数、闰年、夏令时等边界情况,示例代码如下:

let calendar = Calendar.current

// 推进1天
if let nextDay = calendar.date(byAdding: .day, value: 1, to: Date.now) {
    print(nextDay)
}

// 推进1个月
if let nextMonth = calendar.date(byAdding: .month, value: 1, to: Date.now) {
    print(nextMonth)
}

// 推进1季度(3个月)
if let nextQuarter = calendar.date(byAdding: .month, value: 3, to: Date.now) {
    print(nextQuarter)
}

代码修复完整方案

1. 移除冗余方法,改用Calendar处理日期

删除原来的oneDay、oneWeek等固定秒数计算方法,新增基于Calendar的日期推进逻辑,同时避免直接修改@State变量引发不必要的视图刷新。

2. 修复事务生成逻辑

替换错误的calculateNumberOfBudgetTransactions方法,修正日期添加错误,同时优化事务保存逻辑:

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
    @State private var generatedDates: [Date] = []
    
    private let calendar = Calendar.current
    var budgetTransaction: Transaction
    
    func calculateNumberOfBudgetTransactions() -> [Date] {
        var budgetPayDates: [Date] = []
        var currentDate = budgetPayDate
        
        // 封装事务保存逻辑
        func saveTransaction(for date: Date) {
            let trx = Transaction(
                payDate: date,
                payPurpose: budgetPayPurpose,
                payCounterpart: budgetPayCounterpart,
                payDirection: budgetPayDirection,
                payAmount: budgetPayAmount,
                payMethod: budgetPayMethod,
                payFrequency: budgetPayFrequency,
                payControl: budgetPayControl,
                payPurposeCategory: budgetPayPurposeCategory,
                isBudgetTransaction: budgetIsBudgetTransaction
            )
            context.insert(trx)
            budgetPayDates.append(date)
        }
        
        // 保存初始事务
        saveTransaction(for: currentDate)
        
        switch budgetPayFrequency {
        case "daily":
            for _ in 0..<364 {
                guard let nextDate = calendar.date(byAdding: .day, value: 1, to: currentDate) else { break }
                currentDate = nextDate
                saveTransaction(for: currentDate)
            }
        case "weekly":
            for _ in 0..<51 {
                guard let nextDate = calendar.date(byAdding: .weekOfYear, value: 1, to: currentDate) else { break }
                currentDate = nextDate
                saveTransaction(for: currentDate)
            }
        case "biweekly":
            for _ in 0..<24 {
                guard let nextDate = calendar.date(byAdding: .weekOfYear, value: 2, to: currentDate) else { break }
                currentDate = nextDate
                saveTransaction(for: currentDate)
            }
        case "quarterly":
            for _ in 0..<3 {
                guard let nextDate = calendar.date(byAdding: .month, value: 3, to: currentDate) else { break }
                currentDate = nextDate
                saveTransaction(for: currentDate)
            }
        case "semiannually":
            guard let nextDate = calendar.date(byAdding: .month, value: 6, to: currentDate) else { break }
            currentDate = nextDate
            saveTransaction(for: currentDate)
        case "annually":
            // 仅保存初始日期
            break
        default: // monthly
            for _ in 0..<11 {
                guard let nextDate = calendar.date(byAdding: .month, value: 1, to: currentDate) else { break }
                currentDate = nextDate
                saveTransaction(for: currentDate)
            }
        }
        
        return budgetPayDates
    }
    
    var body: some View {
        Form {
            Section {
                Picker("Recur", selection: $budgetPayFrequency){
                    ForEach(payFrequencies, id: \.self) {
                        Text($0)
                    }
                }
                .frame(maxWidth: .infinity)
                .onChange(of: budgetPayFrequency) { _ in
                    // 仅在频率变化时重新计算,避免视图频繁刷新
                    generatedDates = budgetPayFrequency != "once" ? calculateNumberOfBudgetTransactions() : []
                }
            }
        }
        
        List {
            VStack{
                if budgetPayFrequency != "once" {
                    Text("\(generatedDates.count)")
                    ForEach(generatedDates, id: \.self) { payDate in
                        Text("\(payDate)")
                    }
                } else {
                    Text("nothing")
                }
            }
        }
    }
}

struct PayPeriodCalculationContainer: View {
    
    @Query(sort: \Transaction.payPurpose) private var transactions: [Transaction]
    
    private var budgetTransactions: [Transaction] {
        transactions.filter{ $0.isBudgetTransaction == true }
    }
    
    var body: some View {
        PayPeriodCalculation(budgetTransaction: budgetTransactions[0])
    }
}

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

3. 关键优化点

  • 使用临时变量currentDate存储当前推进的日期,避免直接修改@State变量budgetPayDate;
  • 所有周期分支统一使用Calendar计算日期,保证精度和兼容性;
  • 将事务生成逻辑放到Picker的onChange回调中,避免视图每次刷新都重新生成事务;
  • 修复了季度、半年、月度分支中错误添加原始日期的问题。

内容的提问来源于stack exchange,提问作者Roman Potapov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 15:33:10