65

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?

AnderCover
  • 2,488
  • 3
  • 23
  • 42
Aler
  • 15,097
  • 6
  • 23
  • 26
  • You can cheat and get the `timeIntervalSince..." and round it. Since all (rational) timezones at least align on 5-minute boundaries this will work, even if it gives the purists fits. (*Especially* if it gives the purists fits. ;) – Hot Licks Oct 01 '13 at 20:05
  • @HotLicks I doubt that will work if you take things like leap seconds into account. Wallclock time does not change as linear and predictable as that. – Mecki May 15 '14 at 12:14
  • @Mecki - NSDate doesn't consider leap seconds. – Hot Licks May 15 '14 at 12:15
  • @HotLicks NSDate not, but the real world does. And when you convert a NSDate to a printable date string using a NSCalendar, your "rounded" five minute time may not be exactly a multiple of 5 minutes but off by a couple of seconds. NSDate is a just a wrapper around an integer of seconds since a reference date, but that is not the same a real date/time on a calendar and a wallclock. Simple rounding will always be accurate to the minute, but I'm not convinced it will always be accurate to the second. – Mecki May 15 '14 at 12:52
  • @HotLicks But in this case you may be right: As Cocoa handles leap seconds according to NTP standard and not UTC standard (Apple's docs say that explicitly!), leap seconds only play a role when calculating "past dates". As here only future dates are relevant (round up), leap seconds may currently not affect the rounding upwards. See http://www.eecis.udel.edu/~mills/leap.html - paragraph three of "3. How NTP and POSIX Reckon with Leap Seconds". Note that Linux does have real leap second support if enabled when GlibC was built. – Mecki May 15 '14 at 13:14
  • I was looking for something similar, but for 15 minutes. I went with this guy: http://bdunagan.com/2010/09/24/cocoa-tip-nsdate-to-the-nearest-15-minutes/ – NYC Tech Engineer Dec 04 '15 at 21:41

19 Answers19

63

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)
    }
}
mkko
  • 4,262
  • 3
  • 25
  • 29
55

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];
Donovan Voss
  • 1,450
  • 13
  • 12
  • 4
    Another option for the rounding itself is: remainder = minutes % 5; if (remainder) minutes += 5 - remainder; – smorgan Jul 19 '09 at 13:24
  • 4
    It may be an iOS 5.x thing but this absolutely does not work correctly. 'curDate' gives a date of '0001-01-01' plus the time. – mpemburn Aug 15 '12 at 14:14
  • 1
    @mpemburn add the appropriate NSCalendarUnits as necessary to populate each field in the 'components' section. – diatrevolo Jan 09 '13 at 20:11
  • 1
    Why is this answer accepted? It is just wrong. Copy/pasting this code does not lead to the requested result. – Mecki May 15 '14 at 12:05
  • 2
    @Sulthan It relies upon `currentCalendar` to be a Gregorian calendar, which may not be the case (user can configure that in system prefs) and then component splitting may not work as expected. Also it does not fetch year, month and day to the components and thus the created date is not a date rounded to the nearest multiple of 5 minutes in the future, but it is more than a decade in the past (NSDate is not a time, it is a point of time, you cannot use NSDate to express a time of day ignoring the current date). Is cannot use the result to schedule a timer to run on the next multiple of 5 min – Mecki May 15 '14 at 13:41
  • @Mecki It works if what you want is to get a time string from a `NSDateFormatter` (and if the day doesn't matter). It's absolutely possible to use `NSDate` to express a time, ignoring the day. – Sulthan May 15 '14 at 13:59
  • 1
    @Sulthan That's just because you told the formatter to ignore the day part. But where did the original poster state that he wants to print the time in UI? He said he wants to "round NSDate to the nearest 5 minutes", maybe because he wants to schedule an event on that date (e.g. update UI, pull data from a server, throw an alarm event). In the code above he'd schedule it over 10 years in the past. When I say round the current date (`2014-05-15 16:56`) to the nearest 5 minutes, I don't expect you to round it to `0001-01-01 17:00`, probably nobody would expect that! Unexpected behavior is wrong. – Mecki May 15 '14 at 14:58
  • @Mecki The OP specifically said that "I am not using year, month and day parts of curDate". This answer works. There are some cases when it won't work but that's true for most answers on SO. It worked for the OP and that's what's important. – Sulthan May 15 '14 at 15:02
  • 1
    @Sulthan All that was necessary is adding 3 constants to the second line and you'd have a universal solution, that works for everyone in all cases and the way everyone would expect it (and also for the OP!). See comment of mpemburn, he ran exactly into the issue I described before because this solution is behaving highly unexpected and that for which benefit? Saving you to type 40 characters? Seriously??? Aside from the fact that the solution still depends on a system calendar being Gregorian or compatible with its time representation. – Mecki May 15 '14 at 16:04
  • Additionally, it doesn't look like it handles even the case where the minutes value is between 56 and 59 inclusive, since these minutes values should round to 0 and advance the hour, not return '60.' – Alekxos Jan 01 '17 at 21:24
43

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)
Jaydeep Vora
  • 6,085
  • 1
  • 22
  • 40
GregP
  • 1,584
  • 17
  • 16
34

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()
BJ Miller
  • 1,536
  • 12
  • 16
  • 4
    Yep, I just went through all of them - this is the simplest, nicest code, and works. :) – legel Aug 11 '16 at 17:38
  • 1
    Top marks! Did the trick very efficiently! Thank you for posting! Swift 3 snippet worked like a charm! – H2wk Oct 20 '16 at 11:34
  • 1
    Great solution, the only drawback is that it doesn't handle the instance where you give it a time that's inline with the interval. If you have a 15 minute interval and pass it 10:30AM, it will return 10:45AM when ideally it should return 10:30AM because it's already rounded. – William T. Apr 02 '17 at 22:44
  • 1
    @WilliamT. Yes, you are currect. It should be: if nextDiff != interval { let nextDate = calendar.date(byAdding: .minute, value: nextDiff, to: rightNow) ?? Date() } – Mehul Sojitra Aug 03 '18 at 05:20
14

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)
slfan
  • 8,950
  • 115
  • 65
  • 78
ipje
  • 161
  • 1
  • 7
13

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;
}
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
J3RM
  • 1,702
  • 12
  • 17
  • +1 – It'd be interesting to see the performance comparison but this is definitely the most efficient one. Other solutions depend on at least one new object which I think is always bad with this kind of lower level utility. – mkko Oct 01 '13 at 18:04
  • Except he wanted round-up only, as I read it. Just add 299 seconds and truncate. – Hot Licks Oct 01 '13 at 20:08
6

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;
}
bleeksie
  • 61
  • 1
  • 1
6

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.

Mecki
  • 125,244
  • 33
  • 244
  • 253
5

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 


AnderCover
  • 2,488
  • 3
  • 23
  • 42
3

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)!
}
M. Kremer
  • 679
  • 6
  • 24
Daniel Saidi
  • 6,079
  • 4
  • 27
  • 29
2

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;
}
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
treblig
  • 161
  • 1
  • 5
2

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];
jjmias
  • 196
  • 7
2

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!;
    }
}
HotJard
  • 4,598
  • 2
  • 36
  • 36
1

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)];
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • 2
    The reason you got year 0001 dates is because the NSCalendar was making the new date from an NSDateComponents that didn't include years. A solution would have been to add more NSCalendarUnits when creating the time variable. – Donovan Voss Jul 24 '09 at 02:11
  • Thanks Dustin.. Had learnt more about the NSCalendar usage myself later with the other components –  Jul 24 '09 at 11:53
1

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
    }
}
vikzilla
  • 3,998
  • 6
  • 36
  • 57
  • @FedeHenze J3R's solution is in objective-c. If you want it designed similarly to a Swift extension, you could add it within a Category of the NSDate class – vikzilla May 02 '18 at 21:55
0

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];
}
Andrew Bennett
  • 910
  • 11
  • 11
0
- (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];
}
poyo fever.
  • 742
  • 1
  • 5
  • 22
0

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;
}
Collin
  • 6,720
  • 4
  • 26
  • 29
-1

Even shorter... limit to seconds:

let seconds = ceil(Date().timeIntervalSinceReferenceDate/300.0)*300.0
let roundedDate = Date(timeIntervalSinceReferenceDate: seconds)
mm282
  • 453
  • 3
  • 7