使用NSDate计算来确定双周工资频率的上一个和下一个支付期

时间:2016-01-26 20:24:45

标签: ios swift nsdate

我正在尝试计算用户的指定日期时的下一个和之前的工资天数。

我正在为用户存储两个属性,如下所示:

public var firstPayDay: NSDate {
    get { return (NSDate(dateNumber: self["firstPayDay"] as! NSNumber)) }
    set(date) {
        self["firstPayDay"] = date.dateNumber()
        self.payDayOfWeek = self.firstPayDay.dayOfWeek(zeroBased: true)! 
                                               // day of week from 0 - 6
    }
}
  

首次引入应用时,系统会要求用户提供下一个付款日。这被存储为整数,例如, 20160126在用户对象上,以下方便用于将其转换为NSDate:

convenience init(dateNumber: NSNumber) {
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "yyyyMMdd"
    self.init(timeInterval:0, sinceDate: dateFormatter.dateFromString(dateNumber.stringValue)!)
}
public var payDayOfWeek: Int {
    get { return (self["payDayOfWeek"] as! Int) }
    set(dayOfWeek) { self["payDayOfWeek"] = dayOfWeek }
}
  

设置firstPayDay后,使用工作日的基于0(星期日⇢星期六)的索引更新payDayOfWeek。

我可以使用这种方法很好地获得每周付费期的下一个和之前的付费日期,例如this,但我正在努力为 bi做这个 - 周刊支付期限?

要确定上一个和下一个双周支付天数,我需要从第一个支付日起以两周的增量计算,然后确定指定日期在两者之间的位置。如何在代码中使用两个已知属性firstPayDaypayDayOfWeek

执行此操作

预期产出:

user.firstPayDay = NSDate(fromNumber: 20160101) // January 1st, 2016
print(user.payDayOfWeek) // 5 (Friday, inferred from firstPayDay setter)

// For Today, 20160126
print(user.nextPayDayForDate(20160126)) // should return 20160129, the closest Friday an even two weeks from 20160101 and in the future
print(user.prevPayDayForDate(20160126)) // should return 20160115, the closest Friday an even two weeks from 20160101 and in the past

// For Saturday, 20160130
print(user.nextPayDayForDate(20160130)) // should return 20160212, the closest Friday an even two weeks from 20160101 and in the future
print(user.prevPayDayFordate(20160130)) // should return 20160129, the closest Friday an even two weeks from 20160101 and in the past

正在进行中

internal func nextWeekBasedPayDate(payFrequency: PayFrequency, firstPayDay: NSDate) -> NSDate? {
    let interval: Int = payFrequency == .Weekly ? 7 : 14
    let calendar: NSCalendar = NSCalendar.autoupdatingCurrentCalendar()
    guard
        let date: NSDate = calendar.dateByAddingUnit(.Day, value: interval, toDate: self.previousWeekBasedPayDate(payFrequency, firstPayDay), options: [])! else { return nil }
    return date.at12pm()
}

internal func previousWeekBasedPayDate(payFrequency: PayFrequency, firstPayDay: NSDate) -> NSDate? {
    let interval: Int = payFrequency == .Weekly ? 7 : 14
    let calendar: NSCalendar = NSCalendar.autoupdatingCurrentCalendar()
    guard
        let daysSinceFirstPayDate: Int = calendar.components([ .Day ], fromDate: firstPayDay, toDate: self, options: []).day,
        let daysSincePreviousPayDate: Int = daysSinceFirstPayDate % interval + (daysSinceFirstPayDate < 0 ? interval : 0),
        let date: NSDate = calendar.dateByAddingUnit(.Day, value: -daysSincePreviousPayDate, toDate: self, options: [])! else { return nil }
    return date.at12pm()
}

internal func at12pm() -> NSDate? {
    let cal = NSCalendar.currentCalendar()
    return cal.dateBySettingHour(12, minute: 0, second: 0, ofDate: self, options: [])
}

然后计算一个支付期:

func currentPayPeriod(payFrequency: PayFrequency, firstPayDay: NSDate) -> [NSDate]? {
    var startDate: NSDate
    var endDate: NSDate
    switch payFrequency {
    case .Monthly:
        startDate = self.startOfMonth()!
        endDate = self.endOfMonth()!
    case .Weekly, .BiWeekly:
        startDate = (self.previousPayDay(payFrequency, firstPayDay: firstPayDay))!
        endDate = (self.nextPayDay(payFrequency, firstPayDay: firstPayDay)?.addDays(-1))!
    case .SemiMonthly:
        startDate = self.startOfMonth()!
        endDate = self.middleOfMonth()!
    }
    return [startDate, endDate]
}

这似乎完美无缺:

/*
payPeriod   [NSDate]    2 values    
    [0] __NSTaggedDate *    2016-01-24 20:00:00 UTC 0xe41bc5564c000000
    [1] __NSTaggedDate *    2016-01-30 20:00:00 UTC 0xe41bc5d4dc000000
*/

1 个答案:

答案 0 :(得分:1)

我不明白为什么你把日期存储为数字而不是字符串,因为你不能对它进行数学运算而你只需将其转换为字符串来解析它。所以我只想在这个答案中使用字符串日期。

此外,将日期转换为NSDate而不考虑当天的时间是危险的,因为it tends to produce wrong answers for some days in some time zones。所以让我们告诉解析器使用正午时间作为时间:

extension NSDate {
    class func withYMDString(string: String) -> NSDate {
        let dateFormatter = NSDateFormatter()
        let defaultComponents = NSDateComponents()
        defaultComponents.hour = 12
        defaultComponents.minute = 0
        defaultComponents.second = 0
        dateFormatter.defaultDate = dateFormatter.calendar.dateFromComponents(defaultComponents)
        dateFormatter.dateFormat = "yyyyMMdd"
        return dateFormatter.dateFromString(string)!
    }
}

现在让我们考虑如何找到上一个和下一个付款日期。我们有参考支付日期:

let referencePayDate = NSDate.withYMDString("20160101")

我们有一些感兴趣的日期:

let someDate = NSDate.withYMDString("20151231")

为了找到最接近someDate的支付日期,支付日期的哪一天到期并不重要。付款日期每两周一次,即每十四天一次。因此,我们希望找到距离referencePayDate十四天的倍数的日期,这些日期最接近someDate。我们首先计算从referencePayDatesomeDate的天数:

let calendar = NSCalendar.autoupdatingCurrentCalendar()
let daysSinceReferencePayDate = calendar.components([ .Day ],
    fromDate: referencePayDate, toDate: someDate, options: []).day

然后我们将它向下舍入到最接近的14的倍数,朝向-∞:

let daysSincePriorPayDate = daysSinceReferencePayDate % 14
    + (daysSinceReferencePayDate < 0 ? 14 : 0)

(注意,由于Swift计算余数的方式,我们需要调整负分子。)

由此我们可以计算之前的支付日期:

let priorPayDate = calendar.dateByAddingUnit(.Day, value: -daysSincePriorPayDate,
    toDate: someDate, options: [])!
// Result: Dec 18, 2015, 12:00 PM

下一个付款日期是14天后:

let nextPayDate = calendar.dateByAddingUnit(.Day, value: 14,
    toDate: priorPayDate, options: [])!
// Result: Jan 1, 2016, 12:00 PM