Круглый NSDate до ближайших 5 минут

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

NSDate *curDate = [NSDate date];

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

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

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

Ответ 1

Возьмите минутное значение, разделите его на 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];

Ответ 2

Здесь мое решение:

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

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

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

EDIT: Чтобы ответить на вопрос, вы можете использовать ceil для правильного округления:

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

EDIT: расширение в 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)
    }
}

Ответ 3

Уоуерс, я вижу здесь много ответов, но многие долго или трудно понять, поэтому я попытаюсь бросить свои 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()

Ответ 4

Я думаю, что это лучшее решение, но только мое мнение, основанное на предыдущем постельном коде. раундов до ближайшей отметки 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;
}

Ответ 5

Как насчет этого на основе Chris 'и 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)

Ответ 6

Спасибо за образец. Ниже я добавил код в раунд до ближайших 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;
}

Ответ 7

Большинство ответов здесь, к сожалению, не совсем корректны (хотя они, похоже, очень хорошо работают для большинства пользователей), поскольку они либо полагаются на текущий активный системный календарь, либо на григорианский календарь (что может быть не так), либо на тот факт, что секунды прыжка не существуют и/или будут всегда игнорироваться 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) { и он всегда будет округлен.

Ответ 8

Я только начал экспериментировать с этим для моего приложения и придумал следующее. Это в 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)!
}

Ответ 9

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

-(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;
}

Ответ 10

Еще одно общее решение Swift, которое работает до 30 минут округления с использованием NSCalendar

extension NSDate {
    func nearest(minutes: Int) -> NSDate {
        assert(minutes <= 30, "nearest(m) suppport rounding up to 30 minutes");
        let cal = NSCalendar.currentCalendar();
        var time = cal.components(.CalendarUnitMinute | .CalendarUnitSecond, fromDate: self);
        let rem = time.minute % minutes
        if rem > 0 {
            time.minute = minutes - rem;
        }
        time.second = -time.second;
        time.nanosecond = -time.nanosecond //updated 7.07.15
        let date = cal.dateByAddingComponents(time, toDate: self, options: NSCalendarOptions(0));
        return date!;
    }
}

Ответ 11

Я искал это сам, но, используя приведенный выше пример, дал мне даты года 0001.

Здесь моя альтернатива, включенная в smorgan более элегантное предложение, хотя будьте осторожны, я еще не протестировал это:

NSDate *myDate = [NSDate date];
// 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)];

Ответ 12

Я не уверен, насколько эффективны NSDateComponents, но если вы просто хотите иметь дело с самим NSDate, он может дать вам значения на основе секунд, которые затем можно манипулировать.

Например, этот метод округляется до ближайшей минуты. Измените 60 до 300 и округлите его до ближайших 5 минут.

+ (NSDate *)dateRoundedDownToMinutes:(NSDate *)date {
    // Strip miliseconds by converting to int
    int referenceTimeInterval = (int)[date timeIntervalSinceReferenceDate];

    int remainingSeconds = referenceTimeInterval % 60;
    int timeRoundedDownToMinutes = referenceTimeInterval - remainingSeconds;

    NSDate *roundedDownDate = [NSDate dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)timeRoundedDownToMinutes];

    return roundedDownDate;
}

Ответ 13

Я переписал решение @J3RM как расширение в Swift в классе NSDate. Здесь для округления даты до ближайшего 15-минутного интервала:

extension NSDate
{
    func nearestFifteenthMinute() -> NSDate!
    {
        let referenceTimeInterval = Int(self.timeIntervalSinceReferenceDate)
        let remainingSeconds = referenceTimeInterval % 900
        var timeRoundedTo5Minutes = referenceTimeInterval - remainingSeconds
        if remainingSeconds > 450
        {
            timeRoundedTo5Minutes = referenceTimeInterval + (900 - remainingSeconds)
        }
        let roundedDate = NSDate.dateWithTimeIntervalSinceReferenceDate(NSTimeInterval(timeRoundedTo5Minutes))
        return roundedDate
    }
}

Ответ 14

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

Я использую это для заполнения UITextField текущей датой UIDatePicker, когда он становится FirstResponder. Вы не можете просто использовать [NSDate date], когда UIDatePicker настроен с чем-то отличным от 1-минутного интервала. Шахта конфигурируется с интервалом в 5 минут.

+ (NSDate *)roundToNearest5MinuteInterval {

    NSDate *ceilingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ceil([[NSDate date] timeIntervalSinceReferenceDate]/300.0)*300.0];
    NSDate *floorDate = [NSDate dateWithTimeIntervalSinceReferenceDate:floor([[NSDate date] timeIntervalSinceReferenceDate]/300.0)*300.0];
    NSTimeInterval ceilingInterval = [ceilingDate timeIntervalSinceNow];
    NSTimeInterval floorInterval = [floorDate timeIntervalSinceNow];

    if (fabs(ceilingInterval) < fabs(floorInterval)) {
        return ceilingDate;
    } else {
        return floorDate;
    }
}

Игнорирование заголовка вопроса и чтение того, что действительно хочет сделать @aler (округление до ближайшей 5 минут). Все, что вам нужно сделать, это следующее:

NSDate *ceilingDate = [NSDate dateWithTimeIntervalSinceReferenceDate:ceil([[NSDate date] timeIntervalSinceReferenceDate]/300.0)*300.0];

Ответ 15

Это общее решение, которое округляется до ближайшего ввода "mins":

+(NSDate *)roundUpDate:(NSDate *)aDate toNearestMins:(NSInteger)mins
{
    NSDateComponents *components = [[NSCalendar currentCalendar] components:NSUIntegerMax fromDate:aDate];

    NSInteger dateMins = components.minute;
    dateMins = ((dateMins+mins)/mins)*mins;

    [components setMinute:dateMins];
    [components setSecond:0];
    return [[NSCalendar currentCalendar] dateFromComponents:components];
}

Ответ 16

- (NSDate *)roundDateToNearestFiveMinutes:(NSDate *)date
{
    NSDateComponents *time = [[NSCalendar currentCalendar]
                              components:NSHourCalendarUnit | NSMinuteCalendarUnit
                              fromDate:date];
    NSInteger minutes = [time minute];
    float minuteUnit = ceil((float) minutes / 5.0);
    minutes = minuteUnit * 5.0;
    [time setMinute: minutes];
    return [[NSCalendar currentCalendar] dateFromComponents:time];
}

Ответ 17

Еще короче... предел в секундах:

let seconds = ceil(Date().timeIntervalSinceReferenceDate/300.0)*300.0
let roundedDate = Date(timeIntervalSinceReferenceDate: seconds)

Ответ 18

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

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

let original = Date()

let rounded = Date(timeIntervalSinceReferenceDate: 
(original.timeIntervalSinceReferenceDate / 300.0).rounded(.toNearestOrEven) * 300.0)