Раунд NSDate до ближайших 5 минут - PullRequest
53 голосов
/ 19 июля 2009

Например, у меня есть

NSDate *curDate = [NSDate date];

и его значение 9:13 утра. Я не использую год, месяц и день в curDate.

То, что я хочу получить, это дата со значением времени 9:15; Если у меня есть значение времени 9:16, я хочу увеличить его до 9:20 и т. Д.

Как я могу сделать это с NSDate?

Ответы [ 18 ]

55 голосов
/ 01 октября 2013

Вот мое решение:

NSTimeInterval seconds = round([date timeIntervalSinceReferenceDate]/300.0)*300.0;
NSDate *rounded = [NSDate dateWithTimeIntervalSinceReferenceDate:seconds];

Я провел некоторое тестирование, и оно примерно в десять раз быстрее решения Восса. С 1М итерациями это заняло около 3,39 секунды. Этот был выполнен за 0,38 секунды. Решение J3RM заняло 0,50 секунды. Использование памяти также должно быть наименьшим.

Не то, чтобы производительность - все, но это - одна строка. Также вы можете легко контролировать округление с делением и умножением.

РЕДАКТИРОВАТЬ: Чтобы ответить на вопрос, вы можете использовать ceil для правильного округления:

NSTimeInterval seconds = ceil([date timeIntervalSinceReferenceDate]/300.0)*300.0;
NSDate *rounded = [NSDate dateWithTimeIntervalSinceReferenceDate:seconds];

РЕДАКТИРОВАТЬ: расширение в Swift:

public extension Date {

    public func round(precision: TimeInterval) -> Date {
        return round(precision: precision, rule: .toNearestOrAwayFromZero)
    }

    public func ceil(precision: TimeInterval) -> Date {
        return round(precision: precision, rule: .up)
    }

    public func floor(precision: TimeInterval) -> Date {
        return round(precision: precision, rule: .down)
    }

    private func round(precision: TimeInterval, rule: FloatingPointRoundingRule) -> Date {
        let seconds = (self.timeIntervalSinceReferenceDate / precision).rounded(rule) *  precision;
        return Date(timeIntervalSinceReferenceDate: seconds)
    }
}
54 голосов
/ 19 июля 2009

Возьмите минутное значение, разделите на 5, округляя вверх, чтобы получить следующую наибольшую 5-минутную единицу, умножьте на 5, чтобы получить это обратно в минутах, и создайте новую NSDate.

NSDateComponents *time = [[NSCalendar currentCalendar]
                          components:NSHourCalendarUnit | NSMinuteCalendarUnit
                            fromDate:curDate];
NSInteger minutes = [time minute];
float minuteUnit = ceil((float) minutes / 5.0);
minutes = minuteUnit * 5.0;
[time setMinute: minutes];
curDate = [[NSCalendar currentCalendar] dateFromComponents:time];
27 голосов
/ 30 июня 2016

Wowsers, я вижу здесь много ответов, но многие из них длинные или трудные для понимания, поэтому я постараюсь добавить 2 цента на случай, если это поможет. Класс NSCalendar обеспечивает необходимую функциональность безопасным и лаконичным способом. Вот решение, которое работает для меня, без умножения временных интервалов секунд, округления или чего-либо еще. NSCalendar учитывает високосные дни / годы и другие странности времени и даты. (Swift 2.2)

let calendar = NSCalendar.currentCalendar()
let rightNow = NSDate()
let interval = 15
let nextDiff = interval - calendar.component(.Minute, fromDate: rightNow) % interval
let nextDate = calendar.dateByAddingUnit(.Minute, value: nextDiff, toDate: rightNow, options: []) ?? NSDate()

Он может быть добавлен к расширению на NSDate, если необходимо, или как функция произвольной формы, возвращающая новый экземпляр NSDate, что вам нужно. Надеюсь, это поможет всем, кто в этом нуждается.

Обновление Swift 3

let calendar = Calendar.current  
let rightNow = Date()  
let interval = 15  
let nextDiff = interval - calendar.component(.minute, from: rightNow) % interval  
let nextDate = calendar.date(byAdding: .minute, value: nextDiff, to: rightNow) ?? Date()
26 голосов
/ 16 мая 2016

Как насчет этого на основе Криса и swift3

import UIKit

enum DateRoundingType {
    case round
    case ceil
    case floor
}

extension Date {
    func rounded(minutes: TimeInterval, rounding: DateRoundingType = .round) -> Date {
        return rounded(seconds: minutes * 60, rounding: rounding)
    }
    func rounded(seconds: TimeInterval, rounding: DateRoundingType = .round) -> Date {
        var roundedInterval: TimeInterval = 0
        switch rounding  {
        case .round:
            roundedInterval = (timeIntervalSinceReferenceDate / seconds).rounded() * seconds
        case .ceil:
            roundedInterval = ceil(timeIntervalSinceReferenceDate / seconds) * seconds
        case .floor:
            roundedInterval = floor(timeIntervalSinceReferenceDate / seconds) * seconds
        }
        return Date(timeIntervalSinceReferenceDate: roundedInterval)
    }
}

// Example

let nextFiveMinuteIntervalDate = Date().rounded(minutes: 5, rounding: .ceil)
print(nextFiveMinuteIntervalDate)
13 голосов
/ 29 ноября 2011

Я думаю, что это лучшее решение, но только мое мнение, основанное на предыдущем коде постера. раунды до ближайшей 5 мин. Этот код должен использовать намного меньше памяти, чем решения для компонентов даты. Блестящий, спасибо за руководство.

+(NSDate *) dateRoundedDownTo5Minutes:(NSDate *)dt{
    int referenceTimeInterval = (int)[dt timeIntervalSinceReferenceDate];
    int remainingSeconds = referenceTimeInterval % 300;
    int timeRoundedTo5Minutes = referenceTimeInterval - remainingSeconds; 
    if(remainingSeconds>150)
    {/// round up
         timeRoundedTo5Minutes = referenceTimeInterval +(300-remainingSeconds);            
    }
    NSDate *roundedDate = [NSDate dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)timeRoundedTo5Minutes];
    return roundedDate;
}
6 голосов
/ 22 ноября 2009

Спасибо за образец. Ниже я добавил код раунда с точностью до 5 минут

 -(NSDate *)roundDateTo5Minutes:(NSDate *)mydate{
    // Get the nearest 5 minute block
    NSDateComponents *time = [[NSCalendar currentCalendar]
                              components:NSHourCalendarUnit | NSMinuteCalendarUnit
                              fromDate:mydate];
    NSInteger minutes = [time minute];
    int remain = minutes % 5;
    // if less then 3 then round down
    if (remain<3){
        // Subtract the remainder of time to the date to round it down evenly
        mydate = [mydate addTimeInterval:-60*(remain)];
    }else{
        // Add the remainder of time to the date to round it up evenly
        mydate = [mydate addTimeInterval:60*(5-remain)];
    }
    return mydate;
}
6 голосов
/ 15 мая 2014

Большинство ответов здесь, к сожалению, не совсем корректны (даже если кажется, что они работают довольно хорошо для большинства пользователей), поскольку они либо полагаются на текущий активный системный календарь как григорианский календарь (что может быть не так), либо на тот факт, что високосные секунды не существуют и / или всегда будут игнорироваться OS X iOS. Следующий код работает в режиме «копировать и вставить», он гарантированно верен и не допускает таких допущений (и, следовательно, также не сломается в будущем, если Apple изменит поддержку високосных секунд, поскольку в этом случае NSCalendar также придется корректно их поддерживать):

{
    NSDate * date;
    NSUInteger units;
    NSCalendar * cal;
    NSInteger minutes;
    NSDateComponents * comp;

    // Get current date
    date = [NSDate date];

    // Don't rely that `currentCalendar` is a
    // Gregorian calendar that works the way we are used to.
    cal = [[NSCalendar alloc]
        initWithCalendarIdentifier:NSGregorianCalendar
    ];
    [cal autorelease]; // Delete that line if using ARC

    // Units for the day
    units = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit;
    // Units for the time (seconds are irrelevant)
    units |= NSHourCalendarUnit | NSMinuteCalendarUnit;

    // Split current date into components
    comp = [cal components:units fromDate:date];

    // Get the minutes,
    // will be a number between 0 and 59.
    minutes = [comp minute];
    // Unless it is a multiple of 5...
    if (minutes % 5) {
        // ... round up to the nearest multiple of 5.
        minutes = ((minutes / 5) + 1) * 5;
    }

    // Set minutes again.
    // Minutes may now be a value between 0 and 60,
    // but don't worry, NSCalendar knows how to treat overflows!
    [comp setMinute:minutes];

    // Convert back to date
    date = [cal dateFromComponents:comp];
}

Если текущее время уже кратно 5 минутам, код не изменит его. Исходный вопрос не уточнил этот случай в явном виде. Если код всегда округляется до следующего кратного 5 минутам, просто удалите тест if (minutes % 5) {, и он всегда будет округляться.

6 голосов
/ 20 декабря 2017

https://forums.developer.apple.com/thread/92399

см. Ссылку для полного и подробного ответа от сотрудника Apple. Чтобы сохранить вам клик, решение:

let original = Date()

let rounded = Date(timeIntervalSinceReferenceDate: 
(original.timeIntervalSinceReferenceDate / 300.0).rounded(.toNearestOrEven) * 300.0)
3 голосов
/ 03 сентября 2014

Я только начал экспериментировать с этим для моего приложения и придумал следующее. Это в Swift, но концепция должна быть достаточно понятной, даже если вы не знаете Swift.

func skipToNextEvenFiveMinutesFromDate(date: NSDate) -> NSDate {
   var componentMask : NSCalendarUnit = (NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitHour | NSCalendarUnit.CalendarUnitMinute)
   var components = NSCalendar.currentCalendar().components(componentMask, fromDate: date)

   components.minute += 5 - components.minute % 5
   components.second = 0
   if (components.minute == 0) {
      components.hour += 1
   }

   return NSCalendar.currentCalendar().dateFromComponents(components)!
}

Результат выглядит правильно на моей игровой площадке, где я вводю различные пользовательские даты, около полуночи, близко к новому году и т. Д.

Редактировать: Поддержка Swift2:

 func skipToNextEvenFiveMinutesFromDate(date: NSDate) -> NSDate {
    let componentMask : NSCalendarUnit = ([NSCalendarUnit.Year , NSCalendarUnit.Month , NSCalendarUnit.Day , NSCalendarUnit.Hour ,NSCalendarUnit.Minute])
    let components = NSCalendar.currentCalendar().components(componentMask, fromDate: date)

    components.minute += 5 - components.minute % 5
    components.second = 0
    if (components.minute == 0) {
        components.hour += 1
    }

    return NSCalendar.currentCalendar().dateFromComponents(components)!
}
2 голосов
/ 23 декабря 2009

Вот мое решение исходной задачи (округление) с использованием идеи обертки Айанни.

-(NSDate *)roundDateToCeiling5Minutes:(NSDate *)mydate{
    // Get the nearest 5 minute block
    NSDateComponents *time = [[NSCalendar currentCalendar]
                                           components:NSHourCalendarUnit | NSMinuteCalendarUnit
                                             fromDate:mydate];
    NSInteger minutes = [time minute];
    int remain = minutes % 5;
    // Add the remainder of time to the date to round it up evenly
    mydate = [mydate addTimeInterval:60*(5-remain)];
    return mydate;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...