2009-07-19 19 views
42

Na przykład mamOkrągły NSDate do najbliższych 5 minut

NSDate *curDate = [NSDate date]; 

i jego wartość jest 09:13. Nie używam części rok, miesiąc i dzień curDate.

Co chcę dostać to data z wartości czasu 9:15; Jeśli mam wartość czasu 9:16, chcę awansować do 9:20 i tak dalej.

Jak mogę to zrobić z NSDate?

+0

można oszukać i uzyskać 'timeIntervalSince ...”, a wokół niego Ponieważ wszystkie (racjonalny) stref czasowych przynajmniej wyrównać na granicach 5-minutowych to będzie działać, nawet. jeśli da to purystom pasuje. (* Zwłaszcza * jeśli pasuje do purystów;) –

+0

@HotLicks Wątpię, że to zadziała, jeśli weźmiesz pod uwagę rzeczy takie jak sekundy przestępne. Czas na Wallclock nie zmienia się tak liniowo i przewidywalnie. – Mecki

+0

@Mecki - NSDate nie bierze pod uwagę sekund przestępnych –

Odpowiedz

46

Take wartość minut, podzielić przez 5 zaokrąglając w górę, aby dostać kolejną jednostkę najwyższa 5 minut, należy pomnożyć do 5, aby ta z powrotem w ciągu kilku minut, a wybudowanie nowego NSDate.

+4

Inną opcją zaokrąglania jest: reszta = minuty% 5; jeśli (pozostałe) minuty + = 5 - pozostała; – smorgan

+2

Może to być iOS 5.x, ale to absolutnie nie działa poprawnie. "curDate" podaje datę "0001-01-01" plus czas. – mpemburn

+1

@mpemburn dodaj odpowiednie jednostki NSCalendarUnits, aby wypełnić wszystkie pola w sekcji "komponenty". – diatrevolo

1

Szukałem tego sam, ale użycie powyższego przykładu dało mi rok 0001.

Oto moja alternatywa, sprzężone z smorgan jest bardziej elegancki mod sugestię choć uwaga nie mam przeciek przetestowane jeszcze:

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

Powodem, dla którego masz daty 0001 roku, jest to, że NSCalendar robił nową datę z NSDateComponents, która nie zawierała lat. Rozwiązaniem byłoby dodanie większej liczby NSCalendarUnits podczas tworzenia zmiennej czasowej. –

+0

Dzięki Dustin .. Gdybym później dowiedział się więcej o użytkowaniu NSCalendar z innymi komponentami –

6

Dzięki za próby. Poniżej dodałem trochę kodu do najbliższej rundy 5 minut

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

Nie trzeba przeskakiwać przez wiele kółek dookoła. Po prostu dodaj 4:59 i skróć. –

2

Oto moje rozwiązanie do oryginalnego problemu (zaokrąglając w górę) przy użyciu ayianni za otoki pomysł.

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

Nie jestem pewien, jak wydajny NSDateComponents są, ale jeśli chcesz po prostu do czynienia z samą NSDate to może dać wartości na podstawie sekund, które mogą być następnie manipulować.

Na przykład ta metoda zaokrągla w dół do najbliższej minuty. Zmień 60 na 300 i zaokrągli w dół do najbliższych 5 minut.

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

Myślę, że to najlepsze rozwiązanie, ale tylko moja opinia, oparta na poprzednim kodzie plakatu. zaokrągla do najbliższego znaku 5 min. Ten kod powinien zużywać o wiele mniej pamięci niż rozwiązania oparte na komponentach daty. Świetnie, dzięki za wskazówki.

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

+1 - Interesujące byłoby porównanie wyników, ale jest to zdecydowanie najbardziej efektywne. Inne rozwiązania zależą od co najmniej jednego nowego obiektu, który moim zdaniem jest zawsze zły w tego rodzaju narzędziach niższego poziomu. – mkko

+0

Z wyjątkiem tego, że chciał zrobić tylko łapankę, tak jak ją czytałem. Po prostu dodaj 299 sekund i skróć. –

+0

dobra odpowiedź .. działa dla mnie –

37

Oto moje rozwiązanie:

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

Zrobiłem kilka testów i to jest około dziesięciu razy szybciej niż rozwiązania Voss. W przypadku iteracji 1M zajęło to około 3,39 sekundy. Ten wykonano w 0.38 sekundy. Rozwiązanie J3RM zajęło 0,50 sekundy. Wykorzystanie pamięci również powinno być najniższe.

że wydajność nie jest wszystkim, ale jest to jedno-liner. Możesz także łatwo kontrolować zaokrąglanie z dzieleniem i mnożeniem.

EDIT: Aby odpowiedzieć na pytanie, można użyć ceil zaokrąglić w górę odpowiednio:

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

EDIT: Rozszerzenie w 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) 
    } 
} 
+0

ale dla 9:16 to powróci 9:15, a nie żądany 9:20 – vikingosegundo

+0

Dodaj 299 sekund jako pierwszy. –

+2

@vikingosegundo Możesz użyć 'ceil' zamiast' round'. – mkko

6

Najbardziej odpowiada tutaj niestety nie idealnie poprawne (nawet jeśli wydają się działać całkiem dobrze dla większości użytkowników), ponieważ albo opierają się na bieżącym, aktywnym kalendarzu systemu, by być kalendarzem gregoriańskim (co może nie być prawdą), albo na tym, że nie istnieją sekundy przestępne i/lub będzie zawsze ignorowany przez OS X i iOS. Poniższy kod działa w postaci kopii &, z pewnością jest poprawny i nie zawiera takich założeń (a zatem nie będzie również łamał się w przyszłości, jeśli Apple zmieni sekundy przestępne, tak jak w przypadku NSCalendar będzie również musiał poprawnie je wspierać) :

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

Jeśli aktualny czas jest już wielokrotnością 5 minut, kod go nie zmieni. Pierwotne pytanie nie określało wyraźnie tego przypadku. Jeśli kod zawsze zaokrągla się do następnej wielokrotności 5 minut, po prostu usuń test if (minutes % 5) { i zawsze zaokrągli się w górę.

3

Właśnie zacząłem eksperymentować z tym dla mojej aplikacji i wymyśliłem następujące. Jest w Swift, ale koncepcja powinna być wystarczająco zrozumiała, nawet jeśli nie znasz 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)! 
} 

Wynik wygląda poprawnie na moim placu zabaw, gdzie wstrzyknąć różne daty niestandardowych, blisko północy, w pobliżu nowego roku itd

Edit: Swift2 wsparcia:

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

Przepisałem rozwiązanie @ J3RM jako rozszerzenie w Swift na klasie NSDate. Tu jest do zaokrąglania datę z dokładnością do 15-minutowej przerwy:

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

Czy możesz umieścić kod w obj-c na? –

0

wiem, że to starszy wątek, ale ponieważ istnieją nowsze odpowiedzi będą dzielić metody narzędziowy, który używam zaokrąglić się NSDate do najbliższa 5-minutowa przerwa.

Używam tego do zapełnienia UITextField z bieżącą datą UIDatePicker, kiedy staje się FirstResponder. Nie można po prostu użyć [NSDate date], gdy UIDatePicker jest skonfigurowany z czymś innym niż 1 minuta. Moje są skonfigurowane z 5-minutowymi przerwami.

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

Ignorowanie tytułu pytania i czytanie tego, co naprawdę chce osiągnąć (zaokrąglanie w górę do najbliższych 5 minut).Wszystko co musisz zrobić to:

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

Jest to ogólny rozwiązanie, które zaokrągla się do najbliższej „min” Wejście:

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

Jeszcze Swift rozwiązanie rodzajowego, która działa do 30 minut zaokrąglenia przy użyciu 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!; 
    } 
} 
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]; 
} 
13

Jak to na podstawie Chris o i 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) 
+2

Oto mój głos na najlepsze i najbardziej aktualne rozwiązanie tego pytania. Czyste i ogólne! – timgcarlson

+0

Działa idealnie. – akr

+0

Superb :) Bardzo schludny i czysty –

22

Wowsers, widzę wiele odpowiedzi tutaj, ale wiele z nich jest długi lub trudne do zrozumienia, więc postaram się rzucić w moim 2 centów w przypadku to pomaga. Klasa NSCalendar zapewnia wymaganą funkcjonalność w bezpieczny i zwięzły sposób. Oto rozwiązanie, które działa dla mnie, bez pomnożenia interwałów sekundowych, zaokrągleń lub czegokolwiek. NSCalendar uwzględnia dni/lata przestępne oraz inne dziwności czasu i daty. (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() 

Może być dodany do przedłużenia na NSDate razie potrzeby lub w funkcji free-form powrocie nowy NSDate przykład, co trzeba. Mam nadzieję, że to pomoże każdemu, kto tego potrzebuje.

Swift 3 Aktualizacja

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

Tak, właśnie przeszedłem wszystkie - to najprostszy, najpiękniejszy kod i działa. :) – legel

+1

Najlepsze oceny! Czy sztuczka bardzo sprawnie! Dziękujemy za publikację! Swift 3 snippet działał jak urok! – H2wk

+0

Świetne rozwiązanie, jedyną wadą jest to, że nie radzi sobie z instancją, w której podajesz czas zgodny z interwałem. Jeśli masz 15-minutową przerwę i podajesz ją o 10:30, wróci ona o 10:45, kiedy najlepiej powróci o 10:30, ponieważ jest już zaokrąglona. –

0

... Nawet krótszy limit sekund:

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

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

patrz link do pełnej i szczegółowej odpowiedzi od pracownika Apple. Aby zaoszczędzić kliknięcie, rozwiązanie:

let original = Date() 

let rounded = Date(timeIntervalSinceReferenceDate: 
(original.timeIntervalSinceReferenceDate/300.0).rounded(.toNearestOrEven) * 300.0) 
Powiązane problemy