使用 NSDate 计算来确定双周支付频率的上一个和下一个支付周期
Using NSDate calculations to determine previous and next pay period for bi-weekly pay frequency
我正在尝试计算给定日期后用户的下一个和上一个发薪日。
我像这样为用户存储两个属性:
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
}
}
When first introduced to the app, the user is asked to provide their next pay day. This is stored as an integer, e.g. 20160126 on the user object, and the following convenience is used to convert it to 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 }
}
When firstPayDay is set, payDayOfWeek is updated using the 0-based (Sunday ⇢ Saturday) index of the weekday.
我可以使用这种方法很好地获得 每周 支付周期的下一个和前一个支付日,例如 ,但我正在努力如何做到这一点双周 支付周期?
要确定前一个和下一个 双周 发薪日,我需要从第一个发薪日开始以两周为增量进行计算,然后确定指定日期的位置在两者之间。我如何在具有两个已知属性 firstPayDay
和 payDayOfWeek
的代码中做到这一点?
预期输出:
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
而不考虑一天中的时间是危险的,因为 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
的日期。我们首先计算从 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
我正在尝试计算给定日期后用户的下一个和上一个发薪日。
我像这样为用户存储两个属性:
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
}
}
When first introduced to the app, the user is asked to provide their next pay day. This is stored as an integer, e.g. 20160126 on the user object, and the following convenience is used to convert it to 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 }
}
When firstPayDay is set, payDayOfWeek is updated using the 0-based (Sunday ⇢ Saturday) index of the weekday.
我可以使用这种方法很好地获得 每周 支付周期的下一个和前一个支付日,例如
要确定前一个和下一个 双周 发薪日,我需要从第一个发薪日开始以两周为增量进行计算,然后确定指定日期的位置在两者之间。我如何在具有两个已知属性 firstPayDay
和 payDayOfWeek
的代码中做到这一点?
预期输出:
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
而不考虑一天中的时间是危险的,因为 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
的日期。我们首先计算从 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