Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Round NSDate to the nearest 5 minutes

For example I have

NSDate *curDate = [NSDate date];

and its value is 9:13 am. I am not using year, month and day parts of curDate.

What I want to get is date with 9:15 time value; If I have time value 9:16 I want to advance it to 9:20 and so on.

How can I do that with NSDate?

like image 899
Aler Avatar asked Jul 19 '09 04:07

Aler


4 Answers

Here's my solution:

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

I did some testing and it is about ten times as fast as Voss's solution. With 1M iterations it took about 3.39 seconds. This one performed in 0.38 seconds. J3RM's solution took 0.50 seconds. Memory usage should be the lowest also.

Not that the performance is everything but it's a one-liner. Also you can easily control the rounding with division and multiplication.

EDIT: To answer the question, you can use ceil to round up properly:

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

EDIT: An extension in 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)
    }
}
like image 193
mkko Avatar answered Oct 15 '22 03:10

mkko


Take the minute value, divide by 5 rounding up to get the next highest 5 minute unit, multiply to 5 to get that back into in minutes, and construct a new 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];
like image 37
Donovan Voss Avatar answered Oct 15 '22 03:10

Donovan Voss


How about this based on Chris' and 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)
like image 40
GregP Avatar answered Oct 15 '22 02:10

GregP


Wowsers, I see a lot of answers here, but many are long or difficult to understand, so I'll try to throw in my 2 cents in case it helps. The NSCalendar class provides the functionality needed, in a safe and concise manner. Here is a solution that works for me, without multiplying time interval seconds, rounding, or anything. NSCalendar takes into account leap days/years, and other time and date oddities. (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()

It can be added to an extension on NSDate if needed, or as a free-form function returning a new NSDate instance, whatever you need. Hope this helps anyone who needs it.

Swift 3 Update

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()
like image 33
BJ Miller Avatar answered Oct 15 '22 01:10

BJ Miller