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

发布于 2024-07-27 14:04:21 字数 216 浏览 9 评论 0原文

例如我有

NSDate *curDate = [NSDate date];

,它的值是上午 9:13。 我没有使用 curDate 的年、月和日部分。

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(19

[浮城] 2024-08-03 14:04:22

另一种 Swift 通用解决方案,使用 NSCalendar 最多可进行 30 分钟的舍入

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

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!;
    }
}
舞袖。长 2024-08-03 14:04:22

我自己一直在寻找这个,但是使用上面的例子给了我从 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)];

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)];
凉薄对峙 2024-08-03 14:04:22

我将 @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
    }
}

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
    }
}
作妖 2024-08-03 14:04:22

我不确定 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;
}

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;
}
月棠 2024-08-03 14:04:22

这是一个通用的解决方案,它四舍五入到最接近的输入“分钟”:

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

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];
}
魂牵梦绕锁你心扉 2024-08-03 14:04:22
- (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];
}
- (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];
}
黎夕旧梦 2024-08-03 14:04:22

甚至更短......限制为秒:

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

Even shorter... limit to seconds:

let seconds = ceil(Date().timeIntervalSinceReferenceDate/300.0)*300.0
let roundedDate = Date(timeIntervalSinceReferenceDate: seconds)
挽梦忆笙歌 2024-08-03 14:04:21

这是我的解决方案:

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

我做了一些测试,它的速度大约是沃斯解决方案的十倍。 1M 次迭代大约需要 3.39 秒。 这一次的表演时间为 0.38 秒。 J3RM的解决方案花费了0.50秒。 内存使用率也应该是最低的。

并不是说性能就是一切,但它只是一句台词。 您还可以轻松控制除法和乘法的舍入。

编辑:要回答这个问题,您可以使用 ceil 来正确舍入:

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

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

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)
    }
}
囚我心虐我身 2024-08-03 14:04:21

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

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];
友欢 2024-08-03 14:04:21

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

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)
梦过后 2024-08-03 14:04:21

Wowsers,我在这里看到了很多答案,但很多都很长或难以理解,所以我会尽力投入我的 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()

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()
邮友 2024-08-03 14:04:21

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

查看完整链接以及苹果工作人员的详细解答。 为了节省您的点击次数,解决方案:

let original = Date()

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

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)
谷夏 2024-08-03 14:04:21

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

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;
}
分開簡單 2024-08-03 14:04:21

感谢您提供样品。
下面我在最接近的 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;
}

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;
}
美羊羊 2024-08-03 14:04:21

不幸的是,这里的大多数回复并不完全正确(即使它们对于大多数用户来说似乎工作得很好),因为它们要么依赖于当前活动的系统日历是公历(情况可能并非如此),要么依赖于以下事实:闰秒不存在和/或总是被 OS X 和 iOS 忽略。 下面的代码可以复制和粘贴,保证是正确的,并且它没有做出这样的假设(因此,如果苹果改变闰秒支持,将来也不会中断,因为在这种情况下,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 (mines % 5) {,它就会始终向上舍入。

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.

漫漫岁月 2024-08-03 14:04:21

@ipje 的答案在接下来的 5 分钟内解决了问题,但我需要更灵活的东西,我想摆脱所有神奇的数字。
感谢对类似问题的回答,我找到了解决方案
我的解决方案使用 Swift 5.2 和 Measurement 来避免使用幻数:

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

在我的游乐场中:

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 


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 


时间你老了 2024-08-03 14:04:21

我刚刚开始在我的一个应用程序中尝试这一点,并提出了以下建议。 它是用 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)!
}

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)!
}
何时共饮酒 2024-08-03 14:04:21

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

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;
}
南渊 2024-08-03 14:04:21

我知道这是一个较旧的线程,但由于有更新的答案,我将分享我用来将 NSDate 舍入到最近的 5 分钟间隔的实用方法。

当 UITextField 成为 FirstResponder 时,我使用它来填充当前 UIDatePicker 日期。 当 UIDatePicker 配置为 1 分钟间隔以外的其他值时,您不能只使用 [NSDate date]。 我的配置为 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];

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];
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文