objective-c 将 NSDate 舍入到最接近的 5 分钟

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1149256/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 21:56:21  来源:igfitidea点击:

Round NSDate to the nearest 5 minutes

iphoneobjective-ccocoa

提问by Aler

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.

其值为上午 9:13。我没有使用 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.

我想得到的是 9:15 时间值的日期;如果我有时间值 9:16,我想将它提前到 9:20,依此类推。

How can I do that with NSDate?

我怎样才能用 NSDate 做到这一点?

回答by mkko

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.

我做了一些测试,它的速度大约是 Voss 解决方案的十倍。100 万次迭代大约需要 3.39 秒。这一项在 0.38 秒内完成。J3RM 的解决方案耗时 0.50 秒。内存使用量也应该是最低的。

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 ceilto round up properly:

编辑:要回答这个问题,您可以使用ceil适当的四舍五入:

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

EDIT: An extension in Swift:

编辑: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)
    }
}

回答by Donovan Voss

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.

取分钟值,除以 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];

回答by GregP

How about this based on Chris' and swift3

这个基于 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)

回答by BJ Miller

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 NSCalendarclass 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. NSCalendartakes into account leap days/years, and other time and date oddities. (Swift 2.2)

哇塞们,我在这里看到了很多答案,但很多答案很长或难以理解,所以我会尝试投入 2 美分以防万一。本NSCalendar类提供所需的功能,在安全和简洁的方式。这是一个对我有用的解决方案,无需乘以时间间隔秒、四舍五入或任何东西。NSCalendar考虑闰日/年,以及其他时间和日期的奇数。(斯威夫特 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 NSDateif needed, or as a free-form function returning a new NSDateinstance, whatever you need. Hope this helps anyone who needs it.

NSDate如果需要,可以将它添加到扩展中,或者作为返回新NSDate实例的自由形式函数,无论您需要什么。希望这可以帮助任何需要它的人。

Swift 3 Update

斯威夫特 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()

回答by J3RM

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.

我认为这是最好的解决方案,但只是我的意见,基于以前的海报代码。舍入到最接近的 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;
}

回答by ipje

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

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:

有关 Apple 员工的完整详细答案,请参阅链接。为了节省您的点击,解决方案:

let original = Date()

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

回答by bleeksie

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

感谢您的样品。下面我添加了一些代码到最近的 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;
}

回答by Mecki

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

不幸的是,这里的大多数回复并不完全正确(尽管它们似乎对大多数用户来说效果很好),因为它们要么依赖当前活动的系统日历作为公历(可能不是这种情况),要么基于以下事实:闰秒不存在和/或将始终被 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];
}

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.

如果当前时间已经是 5 分钟的倍数,代码不会改变它。最初的问题没有明确说明这种情况。如果代码总是四舍五入到下一个 5 分钟的倍数,只需删除测试if (minutes % 5) {,它就会总是四舍五入。

回答by Daniel Saidi

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.

我刚刚开始为我的一个应用程序尝试这个,并想出了以下内容。它在 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)!
}

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:

编辑: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)!
}

回答by treblig

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

这是我使用 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;
}