Round NSDate to the nearest 5 minutes

IphoneObjective CCocoa

Iphone Problem Overview


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?

Iphone Solutions


Solution 1 - Iphone

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)
    }
}

Solution 2 - Iphone

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];

Solution 3 - Iphone

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)

Solution 4 - Iphone

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()

Solution 5 - Iphone

I think this is the best solution, but just my opinion, based on previous poster code. rounds to nearest 5 min mark. This code should use a lot less memory than the date components solutions. Brilliant, Thanks for the direction.

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

Solution 6 - Iphone

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

see link for full and detailed answer from an Apple staff member. To save you a click, the solution:

let original = Date()

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

Solution 7 - Iphone

Thanks for the sample. Below I have added some code the round to nearest 5 minutes

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

Solution 8 - Iphone

Most replies here are unfortunately not perfectly correct (even though they seem to work quite well for most users), as they either rely on the current active system calendar to be a Gregorian calendar (which may not be the case) or upon the fact that leap seconds don't exist and/or will always be ignored by OS X an iOS. The following code works copy&paste, is guaranteed to be correct and it makes no such assumptions (and thus will also not break in the future if Apple changes leap seconds support, as in that case NSCalendar will have to correctly support them as well):

{
	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];
}

If the current time is already a multiple of 5 minutes, the code will not change it. The original question did not specify this case explicitly. If the code shall always round up to the next multiple of 5 minutes, just remove the test if (minutes % 5) { and it will always round up.

Solution 9 - Iphone

The answer from @ipje did the trick for the next 5 minutes but I needed something more flexible and I wanted to get rid of all the magic numbers. I found a solution thanks to an answer to a similar question My solution uses the Swift 5.2 and Measurement to avoid using magic numbers:

extension UnitDuration {
    var upperUnit: Calendar.Component? {
        if self == .nanoseconds {
            return .second
        }
                
        if self == .seconds {
            return .minute
        }
        if self == .minutes {
            return .hour
        }
        if self == .hours {
            return .day
        }
        return nil
    }
}
extension Date {
    func roundDate(to value: Int, in unit: UnitDuration, using rule: FloatingPointRoundingRule, and calendar: Calendar = Calendar.current) -> Date? {
        guard unit != .picoseconds && unit != .nanoseconds,
            let upperUnit = unit.upperUnit else { return nil }
        let value = Double(value)
        let unitMeasurement = Measurement(value: value, unit: unit)
        let interval = unitMeasurement.converted(to: .seconds).value
        
        let startOfPeriod = calendar.dateInterval(of: upperUnit, for: self)!.start
        var seconds = self.timeIntervalSince(startOfPeriod)
        seconds = (seconds / interval).rounded(rule) * interval
        return startOfPeriod.addingTimeInterval(seconds)
    }
    
    func roundDate(toNearest value: Int, in unit: UnitDuration, using calendar: Calendar = Calendar.current) -> Date? {
        return roundDate(to: value, in: unit, using: .toNearestOrEven)
    }
    
    func roundDate(toNext value: Int, in unit: UnitDuration, using calendar: Calendar = Calendar.current) -> Date? {
        return roundDate(to: value, in: unit, using: .up)
    }
}

In my playground :

let calendar = Calendar.current
let date = Calendar.current.date(from: DateComponents(timeZone: TimeZone.current, year: 2020, month: 6, day: 12, hour: 00, minute: 24, second: 17, nanosecond: 577881))! // 12 Jun 2020 at 00:24

var roundedDate = date.roundDate(toNext: 5, in: .seconds)!
//"12 Jun 2020 at 00:24"
calendar.dateComponents([.nanosecond, .second, .minute, .hour, .day, .month], from: roundedDate) 
// month: 6 day: 12 hour: 0 minute: 24 second: 20 nanosecond: 0 isLeapMonth: false 

roundedDate = date.roundDate(toNearest: 5, in: .seconds)!
// "12 Jun 2020 at 00:24"
calendar.dateComponents([.nanosecond, .second, .minute, .hour, .day, .month], from: roundedDate)
// month: 6 day: 12 hour: 0 minute: 24 second: 15 nanosecond: 0 isLeapMonth: false 

roundedDate = date.roundDate(toNext: 5, in: .minutes)!
// "12 Jun 2020 at 00:25"
calendar.dateComponents([.nanosecond, .second, .minute, .hour, .day, .month], from: roundedDate)
// month: 6 day: 12 hour: 0 minute: 25 second: 0 nanosecond: 0 isLeapMonth: false 

roundedDate = date.roundDate(toNearest: 5, in: .minutes)!
// "12 Jun 2020 at 00:25"
calendar.dateComponents([.nanosecond, .second, .minute, .hour, .day, .month], from: roundedDate)
// month: 6 day: 12 hour: 0 minute: 25 second: 0 nanosecond: 0 isLeapMonth: false 

roundedDate = date.roundDate(toNext: 5, in: .hours)!
// "12 Jun 2020 at 05:00"
calendar.dateComponents([.nanosecond, .second, .minute, .hour, .day, .month], from: roundedDate)
// month: 6 day: 12 hour: 5 minute: 0 second: 0 nanosecond: 0 isLeapMonth: false 

roundedDate = date.roundDate(toNearest: 5, in: .hours)!
// "12 Jun 2020 at 00:00"
calendar.dateComponents([.nanosecond, .second, .minute, .hour, .day, .month], from: roundedDate)
// month: 6 day: 12 hour: 0 minute: 0 second: 0 nanosecond: 0 isLeapMonth: false 


Solution 10 - Iphone

I just started experimenting with this for an app of mine, and came up with the following. It is in Swift, but the concept should be understandable enough, even if you don't know 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)!
}

The result looks correct in my playground, where I inject various custom dates, close to midnight, close to a new year etc.

Edit: Swift2 support:

 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)!
}

Solution 11 - Iphone

Here's my solution to the original problem (rounding up) using ayianni's wrapper idea.

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

Solution 12 - Iphone

I know this is an older thread, but since there are more recent answers I will share the utility method that I use to round an NSDate to the nearest 5 minute interval.

I use this to populate a UITextField with the current UIDatePicker date when it becomes FirstResponder. You can't just use [NSDate date] when the UIDatePicker is configured with something other than a 1 minute interval. Mine are configured with 5 minute intervals.

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

Ignoring the title of the question and reading what @aler really wants to accomplish (rounding UP to the nearest 5 minute). All you have to do is the following:

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

Solution 13 - Iphone

One more Swift generic solution, which works up to 30 minutes rounding using 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!;
    }
}

Solution 14 - Iphone

Had been looking for this myself, but using the example above gave me from year 0001 dates.

Here's my alternative, incorporated with smorgan's more elegant mod suggestion though beware I haven't leak tested this yet:

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)];

Solution 15 - Iphone

I rewrote @J3RM 's solution as an extension in Swift on the NSDate class. Here it is for rounding a date to the nearest 15th minute interval:

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

Solution 16 - Iphone

I'm not sure how efficient NSDateComponents are, but if you just want to deal with the NSDate itself it can give you values based on seconds which can then be manipulated.

For example, this method rounds down to the nearest minute. Change the 60 to 300 and it will round down to nearest 5 minutes.

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

Solution 17 - Iphone

This is a generic solution which rounds up to the nearest input '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];
}

Solution 18 - Iphone

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

Solution 19 - Iphone

Even shorter... limit to seconds:

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlerView Question on Stackoverflow
Solution 1 - IphonemkkoView Answer on Stackoverflow
Solution 2 - IphoneDonovan VossView Answer on Stackoverflow
Solution 3 - IphoneGregPView Answer on Stackoverflow
Solution 4 - IphoneBJ MillerView Answer on Stackoverflow
Solution 5 - IphoneJ3RMView Answer on Stackoverflow
Solution 6 - IphoneipjeView Answer on Stackoverflow
Solution 7 - IphonebleeksieView Answer on Stackoverflow
Solution 8 - IphoneMeckiView Answer on Stackoverflow
Solution 9 - IphoneAnderCoverView Answer on Stackoverflow
Solution 10 - IphoneDaniel SaidiView Answer on Stackoverflow
Solution 11 - IphonetrebligView Answer on Stackoverflow
Solution 12 - IphonejjmiasView Answer on Stackoverflow
Solution 13 - IphoneHotJardView Answer on Stackoverflow
Solution 14 - IphoneayianniView Answer on Stackoverflow
Solution 15 - IphonevikzillaView Answer on Stackoverflow
Solution 16 - IphoneCollinView Answer on Stackoverflow
Solution 17 - IphoneAndrew BennettView Answer on Stackoverflow
Solution 18 - Iphonepoyo fever.View Answer on Stackoverflow
Solution 19 - Iphonemm282View Answer on Stackoverflow