给定过去的 NSDate 和重复间隔,计算从现在开始的下一个事件

Calculate the next event from now given an NSDate in the past and a repeat interval

如何通过重复一定的时间间隔从过去的日期计算出比现在晚的第一个日期?例如:

// Date in the past
NSDate *pastDate = [NSDate my_dateWithString:@"11/09/2001"];

// Time interval
NSTimeInterval repeatInterval = 14 * 24 * 60 * 60; // 2 weeks

// Current date
NSDate *now = [NSDate date]; // 23/07/2015

// Start calculating next date ->
NSDate *nextDate = /* interval added to past date */
// Is result later than now?
// No? Calculate again
// Abracadabra

NSLog(@"nexDate = %@",nextDate); // 28/07/2015

我不想使用迭代。我担心计算一个案例,例如过去一年的开始日期和一天的重复间隔。

这是我的解决方案,但它有迭代。

  NSDateComponents *twoWeeksDateComponents = [NSDateComponents new];
  twoWeeksDateComponents.weekOfMonth = 2;

  NSDate *date = self.picker.date;
  NSCalendar *calendar = [NSCalendar currentCalendar];

  NSDate *now = [NSDate date];
  NSDate *nextDate = date;
  NSComparisonResult result = [nextDate compare:now];
  while (result == NSOrderedAscending) {
    nextDate = [calendar dateByAddingComponents:kTwoWeeksDateComponents
                                         toDate:nextDate
                                        options:NSCalendarMatchNextTime];
    result = [nextDate compare:now];
  }
  NSLog(@"nexDate = %@",nextDate); // 28/07/2015

你确实可以不用迭代来做到这一点,而是使用一点算术。您正在寻找严格大于另一个值 n 的某个因子 f 的最接近倍数。日历计算有点复杂,但仍然只是算术(NSCalendar 当然会为您完成所有繁重的工作——例如闰年)。

NSDate * pastDate = ...;
NSDate * now = [NSDate date];

从用户那里获取您喜欢的重复间隔并构造一个 NSDateComponents 表示它:

NSCalendarUnit repeatUnit = NSCalendarUnitWeekOfYear;
NSUInteger repeatInterval = 2;

NSDateComponents * repeatComps = [NSDateComponents new];
[repeatComps setValue:repeatInterval forComponent:repeatUnit];

给定重复单位,找到两个日期之间出现的该单位的数量,作为另一个日期组件对象:

NSCalendar * cal = [NSCalendar currentCalendar];
NSDateComponents * deltaComps = [cal components:repeatUnit
                                       fromDate:pastDate
                                         toDate:now
                                        options:0];
NSInteger deltaVal = [deltaComps valueForComponent:repeatUnit];

现在是算术位。将增量向下舍入到最接近的重复间隔倍数。然后添加该舍入值(使用适当的单位)将产生等于或早于现在的日期。

现在,作为 jawboxer ,对于 NSCalendarUnitMonthrepeatUnit,如果您使用该值计算日期然后再添加一个重复间隔(如这个答案的原始版本确实如此)。相反,立即将重复次数加一;然后日历正确处理月份增量。

NSInteger repeatsJustPastNow = 1 + deltaVal - (deltaVal % repeatInterval);

NSDateComponents * toNowComps = [NSDateComponents new];
[toNowComps setValue:repeatsJustPastNow
        forComponent:repeatUnit];

NSDate * nextDate;
nextDate = [cal dateByAddingComponents:toNowComps
                                toDate:pastDate
                               options:0];

Jawboxer 的 Swift version of this corrected code is on Github.

编辑:正如 Josh 所指出的,这不考虑闰年。因此,这是一个很好的实施示例,它似乎可以在短期测试中工作,但其中存在漏洞。它也将在日志中关闭几个小时,因为 dateFromString: 将补偿时区,而日志将其记录为格林威治标准时间。


NSDateFormatter* format = [[NSDateFormatter alloc] init];
format.dateFormat = @"yyyy-MM-dd 'at' HH:mm";
NSDate *pastDate = [format dateFromString:  @"2001-09-11 at 00:00"];

NSTimeInterval repeatInterval = 14 * 24 * 60 * 60; // 2 weeks

NSDate *nowDate = [NSDate date];

NSTimeInterval timeFromPastDate = [nowDate timeIntervalSinceDate: pastDate];
NSTimeInterval modulus = timeFromPastDate / repeatInterval;
modulus -= floor(modulus);

NSDate *nextDate = [NSDate dateWithTimeInterval: repeatInterval * (1 - modulus) sinceDate: nowDate];

NSLog(@"nexDate = %@",nextDate); // 28th/July/2015, if current date is 23rd/July/2015

早早的又做了一个解决方案

  NSDate *date = ...;

  static NSTimeInterval k2WeeksInterval = 14 * 24 * 60 * 60.0;

  NSTimeInterval timeInterval = -date.timeIntervalSinceNow;

  double repeats = ceil(timeInterval / k2WeeksInterval);

  NSCalendar *calendar = [NSCalendar currentCalendar];

  NSDateComponents *components = [NSDateComponents new];
  components.weekOfYear = repeats * 2;

  NSDate *nextDate = [calendar dateByAddingComponents:components
                                               toDate:date
                                              options:NSCalendarMatchNextTime];