使用 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 }
}

设置第一个发薪日时,使用工作日的 0 开始(星期日 ⇢ 星期六)索引更新每周的 payDayOfWeek。

我可以使用这种方法很好地获得像这样的每周支付期的下一个和上一个发薪日,但我正在努力为每两周的支付期做到这一点?

为了确定上一个和下一个每两周发薪日,我需要从第一个发薪日开始以两周为增量进行计算,然后确定指定日期在两者之间的位置。我将如何在代码中使用两个已知属性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
*/

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

此外,在不考虑一天中的时间的情况下将日期转换为NSDate是危险的,因为它往往会在某些时区的某些日子产生错误的答案。因此,让我们告诉解析器使用中午作为一天中的时间:

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的日期。我们首先计算从 referencePayDate 天到 someDate 的天数:

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

最新更新