Jak ustalić, czy NSDate jest dzisiaj?

156

Jak sprawdzić, czy dana osoba NSDatenależy do dzisiaj?

Kiedyś sprawdzałem to używając pierwszych 10 znaków z [aDate description]. [[aDate description] substringToIndex:10]zwraca ciąg w taki "YYYY-MM-DD"sposób, że porównałem ciąg z ciągiem zwróconym przez [[[NSDate date] description] substringToIndex:10].

Czy istnieje szybszy i / lub schludniejszy sposób sprawdzenia?

Dzięki.

Seunghoon
źródło
Uważam, że moja metoda jest jedyną (jak to piszę), która faktycznie odwołuje się do dokumentacji Apple i robi to, co sugerują, aby określić, czy dwa dni są takie same. Chociaż inne metody mogą działać, uważam, że moja metoda jest najbardziej odporna na przyszłość. stackoverflow.com/a/17641925/901641
ArtOfWarfare
@ArtOfWarfare: Nie zgadzam się. Twój kod nadal musi radzić sobie z interwałami czasowymi, ale firma Apple zapewnia metody, które już to uwzględniają - patrz: stackoverflow.com/a/17642033/106435
vikingosegundo
1
Metoda „opisu” NSDate wyświetla datę w formacie UTC, tj. Greenwich Mean Time, bez korekty czasu letniego. Jeśli jesteś późnym wieczorem w USA lub wcześnie rano w Indiach, kalendarz nie wyświetli tego, co jest „dzisiaj”. Potrzebujesz obiektu NSCalendar, aby przetłumaczyć NSDate na dzień i przekonwertować bieżącą datę na dzień, a następnie porównać je.
gnasher729

Odpowiedzi:

308

W macOS 10.9+ i iOS 8+ w NSCalendar / Calendar jest metoda, która robi dokładnie to!

- (BOOL)isDateInToday:(NSDate *)date 

Więc po prostu to zrobisz

Cel C:

BOOL today = [[NSCalendar currentCalendar] isDateInToday:date];

Swift 3:

let today = Calendar.current.isDateInToday(date)
Catfish_Man
źródło
1
Sprawdź, używając SDK iOS 8
Catfish_Man,
1
Jestem zdezorientowany powyższym komentarzem, to zdecydowanie iOS 8: - (BOOL) isDateInToday: (NSDate *) data NS_AVAILABLE (10_9, 8_0);
powerj1984
Tak, to właśnie powiedziałem. Poprzedni komentator był zdezorientowany, patrząc prawdopodobnie na pakiet SDK iOS 7. Miał inną deklarację.
Catfish_Man
na „dziś” to jest idealna odpowiedź :)
Daij-Djan
Doskonała odpowiedź!
Supertecnoboff
192

Możesz porównać składniki daty:

NSDateComponents *otherDay = [[NSCalendar currentCalendar] components:NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:aDate];
NSDateComponents *today = [[NSCalendar currentCalendar] components:NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay fromDate:[NSDate date]];
if([today day] == [otherDay day] &&
   [today month] == [otherDay month] &&
   [today year] == [otherDay year] &&
   [today era] == [otherDay era]) {
    //do stuff
}

Edytować:

Bardziej podoba mi się metoda Stefana, myślę, że jest ona czystsza i bardziej zrozumiała, jeśli stwierdzenie:

NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *components = [cal components:(NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:[NSDate date]];
NSDate *today = [cal dateFromComponents:components];
components = [cal components:(NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:aDate];
NSDate *otherDate = [cal dateFromComponents:components];

if([today isEqualToDate:otherDate]) {
    //do stuff
}

Chris, uwzględniłem twoją sugestię. Musiałem sprawdzić, jaka była era, więc dla każdego, kto nie wie, rozróżnia ona BC i AD. Jest to prawdopodobnie niepotrzebne dla większości ludzi, ale łatwo to sprawdzić i dodaje pewności, więc dołączyłem to. Jeśli zależy ci na szybkości, prawdopodobnie i tak nie jest to dobra metoda.


UWAGA podobnie jak w przypadku wielu odpowiedzi na SO, po 7 latach jest to całkowicie nieaktualne. W Swift teraz po prostu użyj.isDateInToday

David Kanarek
źródło
1
Dzięki. Myślę, że jest to bardziej zgrabne i przyszłościowe rozwiązanie mojego pytania.
Seunghoon,
1
Myślę, że musisz również uwzględnić komponent ery. Myślę, że między tymi dwoma podejściami jest rzut monetą; isEqualToDate wydaje się ładniejszy, ale wydaje się trochę marnotrawny i zajmuje mniej więcej taką samą liczbę wierszy, aby odtworzyć datę z komponentów.
Chris Page
1
Ponieważ zarówno „otherDay”, jak i „Today” i tak mają wypełnione tylko te cztery pola (to wszystko, o co prosiłeś), możesz po prostu użyć [Today equalTo: otherDay], bez przeskakiwania kółek w drugim.
Glenn Maynard,
@Glenn Czy sugerujesz porównanie NSDateComponents w pierwszym? Tak naprawdę nie myślałem o tym, ale wydaje mi się, że to dobry pomysł.
David Kanarek,
1
@DavidKanarek Odkopywanie starych postów ... Nie polecałbym bezpośredniego porównywania składowych dat, jeśli wpadniesz w sytuację, w której musisz przesunąć jeden ze składników, możesz otrzymać coś w stylu miesiąc == 3, dzień == 34. Konwersja na daty poprawnie zinterpretuje to jako 3 kwietnia, porównanie składników daty nie będzie tego samego, co miesiąc == 4, dzień == 3.
Sean Kladek
27

To jest pochodna twojego pytania, ale jeśli chcesz wydrukować NSDate z „Dzisiaj” lub „Wczoraj”, użyj funkcji

- (void)setDoesRelativeDateFormatting:(BOOL)b

dla NSDateFormatter

Jon
źródło
3
Sprytny pomysł. Zakładam jednak, że wyszukiwanie ciągu „Dzisiaj” nie działa, jeśli zlokalizujesz.
Basil Bourque
16

Spróbowałbym znormalizować dzisiejszą datę do północy i drugą datę, znormalizować do północy, a następnie porównać, czy jest to ta sama NSDate.

Z przykładu Apple, oto jak normalizujesz do dzisiejszej północy, zrób to samo dla drugiej daty i porównaj:

NSCalendar * gregorian = [[NSCalendar alloc]
                               initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents * components =
    [gregorian components:
                 (NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit)
                 fromDate:[NSDate date]];
NSDate * today = [gregorian dateFromComponents:components];
stefanB
źródło
Myślę, że trzeba też uwzględnić tę erę.
Chris Page
Czy nie należy znormalizować daty, ustawiając godzinę na 12, tj. Południe, a nie północ? Ponieważ data jest w czasie GMT, ustawienie jej na południe zapewnia, że ​​zmiany strefy czasowej w obie strony (i trwają do 12 godzin tylko w obie strony) nie „przeskakują” na dzień przed lub po.
artooras
13

Working Swift 3 i 4 rozszerzenie sugestii Catfish_Man:

extension Date {

    func isToday() -> Bool {
        return Calendar.current.isDateInToday(self)
    }

}
Benno Kress
źródło
12

Nie ma potrzeby żonglowania komponentami, epokami i innymi rzeczami.

NSCalendar zapewnia metodę pobierania początku określonej jednostki czasu dla istniejącej daty.

Ten kod rozpocznie dzisiejszy dzień i inną datę i porówna to. Jeśli oszacuje NSOrderedSame, obie daty przypadają w tym samym dniu - czyli dzisiaj.

NSDate *today = nil;
NSDate *beginningOfOtherDate = nil;

NSDate *now = [NSDate date];
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&today interval:NULL forDate:now];
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&beginningOfOtherDate interval:NULL forDate:beginningOfOtherDate];

if([today compare:beginningOfOtherDate] == NSOrderedSame) {
    //otherDate is a date in the current day
}
vikingosegundo
źródło
9
extension NSDate {
  func isToday() -> Bool {
    let cal = NSCalendar.currentCalendar()
    var components = cal.components([.Era, .Year, .Month, .Day], fromDate:NSDate())
    let today = cal.dateFromComponents(components)!

    components = cal.components([.Era, .Year, .Month, .Day], fromDate:self)
    let otherDate = cal.dateFromComponents(components)!

    return today.isEqualToDate(otherDate)
}

Pracował dla mnie w Swift 2.0

Omar Albeik
źródło
6

Szybka wersja najlepszej odpowiedzi:

let cal = NSCalendar.currentCalendar()
var components = cal.components([.Era, .Year, .Month, .Day], fromDate:NSDate())
let today = cal.dateFromComponents(components)!

components = cal.components([.Era, .Year, .Month, .Day], fromDate:aDate);
let otherDate = cal.dateFromComponents(components)!

if(today.isEqualToDate(otherDate)) {
    //do stuff
}
Vojtech Vrbka
źródło
5

Zapoznaj się z wpisem dokumentacji Apple zatytułowanym „Wykonywanie obliczeń kalendarza” [łącze] .

Listing 13 na tej stronie sugeruje, że aby określić liczbę nocy między dniami, użyj:

- (NSInteger)midnightsFromDate:(NSDate *)startDate toDate:(NSDate *)endDate
{
    NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];
    NSInteger startDay = [calendar ordinalityOfUnit:NSDayCalendarUnit
                                             inUnit:NSEraCalendarUnit
                                            forDate:startDate];
    NSInteger endDay = [calendar ordinalityOfUnit:NSDayCalendarUnit
                                           inUnit:NSEraCalendarUnit
                                          forDate:endDate];
    return endDay - startDay;
}

Następnie możesz określić, czy dwa dni są takie same, używając tej metody i sprawdzając, czy zwraca 0, czy nie.

ArtOfWarfare
źródło
2
Pamiętaj, że era zaczyna się o godzinie 00:00 GMT! Ta metoda może nie działać poprawnie, jeśli jedna z dat (lub obie) nie są w strefie czasowej GMT.
Andreas Ley
5

Możesz również sprawdzić odstęp czasu między datą, którą masz, a aktualną datą:

[myDate timeIntervalSinceNow]

W ten sposób uzyskasz odstęp czasu w sekundach między myDate a bieżącą datą / godziną.

Link .

Edycja: Uwaga dla wszystkich: doskonale zdaję sobie sprawę, że [myDate timeIntervalSinceNow] nie określa jednoznacznie, czy myDate jest dzisiaj.

Zostawiam tę odpowiedź tak, jak jest, więc jeśli ktoś szuka czegoś podobnego i [myDate timeIntervalSinceNow] jest przydatne, może to znaleźć tutaj.

alesplin
źródło
1
To, co mówisz, jest poprawne, ale interwał tak naprawdę nie pomaga w sprawdzeniu, czy data jest dzisiaj.
Jaanus
3
Pamiętaj tylko, że to pokazuje, czy jesteś w ciągu 24 godzin od teraz, ale nie, jeśli daty są takie same. Różnica jednej sekundy może oznaczać różne daty, a różnica 86399 sekund może oznaczać tę samą datę.
David Kanarek,
@David: Prawda. Nie myślałem o tym, ponieważ odpowiadałem na 3 inne rzeczy, a twoja odpowiedź nie została opublikowana, kiedy ładowałem stronę.
alesplin
1
To wcale nie odpowiada na pytanie. Powinieneś go usunąć.
vikingosegundo
1
Ta odpowiedź to nic innego jak hałas. Nie jest próbą odpowiedzi na pytanie ani nie jest konieczne, aby się do timeIntervalSinceNowniego odwoływać, ponieważ jest omawiany w wielu innych postach. Również obsługa porównania dni ze sprawdzaniem sekund zachęca do dzielenia podatnego na błędy przez 86400.
vikingosegundo
4

Swift Extension oparte na najlepszych odpowiedziach:

extension NSDate {
    func isToday() -> Bool {
        let cal = NSCalendar.currentCalendar()
        if cal.respondsToSelector("isDateInToday:") {
            return cal.isDateInToday(self)
        }
        var components = cal.components((.CalendarUnitEra | .CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay), fromDate:NSDate())
        let today = cal.dateFromComponents(components)!

        components = cal.components((.CalendarUnitEra | .CalendarUnitYear | .CalendarUnitMonth | .CalendarUnitDay), fromDate:self);
        let otherDate = cal.dateFromComponents(components)!
        return today.isEqualToDate(otherDate)
    }
}
ZiggyST
źródło
2

Jeśli masz wiele takich porównań dat, wezwania do calendar:components:fromDaterozpoczęcia zajmowania dużo czasu. Według niektórych profilów, które zrobiłem, wydają się one dość drogie.

Powiedzmy, że próbujesz określić, które z jakiejś tablicy dat, powiedzmy NSArray *datesToCompare, są tego samego dnia co jakiś podany dzień, powiedzmy NSDate *baseDate, a następnie możesz użyć czegoś podobnego do następującego (częściowo zaadaptowanego z powyższej odpowiedzi):

NSDate *baseDate = [NSDate date];

NSArray *datesToCompare = [NSArray arrayWithObjects:[NSDate date], 
                           [NSDate dateWithTimeIntervalSinceNow:100],
                           [NSDate dateWithTimeIntervalSinceNow:1000],
                           [NSDate dateWithTimeIntervalSinceNow:-10000],
                           [NSDate dateWithTimeIntervalSinceNow:100000],
                           [NSDate dateWithTimeIntervalSinceNow:1000000],
                           [NSDate dateWithTimeIntervalSinceNow:50],
                           nil];

// determine the NSDate for midnight of the base date:
NSCalendar* calendar = [NSCalendar currentCalendar];
NSDateComponents* comps = [calendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit) 
                                       fromDate:baseDate];
NSDate* theMidnightHour = [calendar dateFromComponents:comps];

// set up a localized date formatter so we can see the answers are right!
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];

// determine which dates in an array are on the same day as the base date:
for (NSDate *date in datesToCompare) {
    NSTimeInterval interval = [date timeIntervalSinceDate:theMidnightHour];
    if (interval >= 0 && interval < 60*60*24) {
        NSLog(@"%@ is on the same day as %@", [dateFormatter stringFromDate:date], [dateFormatter stringFromDate:baseDate]);
    }
    else {
        NSLog(@"%@ is NOT on the same day as %@", [dateFormatter stringFromDate:date], [dateFormatter stringFromDate:baseDate]);
    }
}

Wynik:

Nov 23, 2011 1:32:00 PM is on the same day as Nov 23, 2011 1:32:00 PM
Nov 23, 2011 1:33:40 PM is on the same day as Nov 23, 2011 1:32:00 PM
Nov 23, 2011 1:48:40 PM is on the same day as Nov 23, 2011 1:32:00 PM
Nov 23, 2011 10:45:20 AM is on the same day as Nov 23, 2011 1:32:00 PM
Nov 24, 2011 5:18:40 PM is NOT on the same day as Nov 23, 2011 1:32:00 PM
Dec 5, 2011 3:18:40 AM is NOT on the same day as Nov 23, 2011 1:32:00 PM
Nov 23, 2011 1:32:50 PM is on the same day as Nov 23, 2011 1:32:00 PM
Julian Richardson
źródło
Dzięki za to. Przyjęty sposób był zbyt intensywny dla naszej aplikacji, więc musieliśmy to zoptymalizować.
Accatyyc,
1
Ten kod nie powiedzie się na czas letni. Każdy, kto używa kodu o niesławnym numerze 86 400, zasługuje na ból głowy, jaki może powodować.
vikingosegundo
2

Jest łatwiejszy sposób niż wiele z powyższych odpowiedzi!

NSDate *date = ... // The date you wish to test
NSCalendar *calendar = [NSCalendar currentCalendar];

if([calendar isDateInToday:date]) {
    //do stuff
}
aryland
źródło
1
To samo, co odpowiedź Catfish_Man!
Thanh Pham
0

Prawdopodobnie można by to przerobić jako kategorię NSDate, ale użyłem:

// Seconds per day (24h * 60m * 60s)
#define kSecondsPerDay 86400.0f

+ (BOOL) dateIsToday:(NSDate*)dateToCheck
{
    // Split today into components
    NSCalendar* gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents* comps = [gregorian components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit) 
                                        fromDate:[NSDate date]];

    // Set to this morning 00:00:00
    [comps setHour:0];
    [comps setMinute:0];
    [comps setSecond:0];
    NSDate* theMidnightHour = [gregorian dateFromComponents:comps];
    [gregorian release];

    // Get time difference (in seconds) between date and then
    NSTimeInterval diff = [dateToCheck timeIntervalSinceDate:theMidnightHour];
    return ( diff>=0.0f && diff<kSecondsPerDay );
}

(Jednak porównanie dwóch ciągów daty, tak jak w pierwotnym pytaniu, wydaje się prawie „czystsze” ...)

LeoN
źródło
Po co uwzględniać godziny, minuty i sekundy podczas tworzenia, compsskoro od razu ustawiasz je na zero? Myślę też, że musisz uwzględnić tę erę.
Chris Page
Pamiętaj, że chociaż zadziała to prawie w każdym przypadku, istnieją takie rzeczy, jak sekundy przestępne. Nie wiem, czy i jak radzi sobie z nimi klasa NSDate firmy Apple, ale jest pewna szansa, że ​​to nie zadziała. Jest to bardziej „czy wiesz” niż krytyka twojej metody, ponieważ są one niezwykle rzadkie (24 w historii).
David Kanarek
co z datami, które nie mają 24 godzin, ale 23 lub 25 ze względu na czas letni?
vikingosegundo
0

dla iOS7 i wcześniejszych:

//this is now => need that for the current date
NSDate * now = [NSDate date];

NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
[calendar setTimeZone:[NSTimeZone systemTimeZone]];

NSDateComponents * components = [calendar components:( NSYearCalendarUnit|    NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate: now];

[components setMinute:0];
[components setHour:0];
[components setSecond:0];

//this is Today's Midnight
NSDate *todaysMidnight = [calendar dateFromComponents: components];



//now timeIntervals since Midnight => in seconds
NSTimeInterval todayTimeInterval = [now timeIntervalSinceDate: todaysMidnight];

//now timeIntervals since OtherDate => in seconds
NSTimeInterval otherDateTimeInterval = [now timeIntervalSinceDate: otherDate];

if(otherDateTimeInterval > todayTimeInterval) //otherDate is not in today
{
    if((otherDateTimeInterval - todayTimeInterval) <= 86400) //86400 == a day total seconds
    {
        @"yesterday";
    }
    else
    {
        @"earlier";
    }
}
else
{
    @"today";
}


now = nil;
calendar = nil;
components = nil;
todaysMidnight = nil;

NSLog("Thank you :-)");
user3744424
źródło
0

Prawidłowe i bezpieczne rozwiązanie bez rozpakowywania na siłę, działające na Swift 2.2 i wcześniejszych iOS 8:

func isToday() -> Bool {
    let calendar = NSCalendar.currentCalendar()
    if #available(iOS 8.0, *) {
        return calendar.isDateInToday(self)
    }

    let todayComponents = calendar.components([.Era, .Year, .Month, .Day], fromDate:NSDate())
    let dayComponents = calendar.components([.Era, .Year, .Month, .Day], fromDate:self)

    guard let today = calendar.dateFromComponents(todayComponents),
        day = calendar.dateFromComponents(dayComponents) else {
        return false
    }

    return today.compare(day) == .OrderedSame
}
fpg1503
źródło
-1

Oto moja 2-centowa odpowiedź oparta na zaakceptowanej odpowiedzi, ale obsługująca również nowszy interfejs API. Uwaga: używam kalendarza gregoriańskiego, ponieważ większość znaczników czasu to GMT, ale zmieniaj je według własnego uznania

func isDateToday(date: NSDate) -> Bool {
    let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
    if calendar.respondsToSelector("isDateInToday:") {
        return calendar.isDateInToday(date)
    }
    let dateComponents = NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitDay
    let today = calendar.dateFromComponents(calendar.components(dateComponents, fromDate: NSDate()))!
    let dateToCompare = calendar.dateFromComponents(calendar.components(dateComponents, fromDate: date))!

    return dateToCompare == today
}
Daniel Galasko
źródło
-3

Moje rozwiązanie polega na obliczeniu liczby dni, które minęły od 1970 roku, przez podzielenie i porównanie części całkowitej

#define kOneDay (60*60*24)
- (BOOL)isToday {
  NSInteger offset = [[NSTimeZone defaultTimeZone] secondsFromGMT];

  NSInteger days =[self timeIntervalSince1970] + offset;
  NSInteger currentDays = [[NSDate date] timeIntervalSince1970] + offset;
  return (days / kOneDay == currentDays / kOneDay);
}
stcui
źródło
To właściwie nie jest złe rozwiązanie. Nie jestem pewien, dlaczego został tak odrzucony. Użycie dzielenia liczb całkowitych odetnie resztę i porówna tylko wartość „dni” z długiego czasu uniksa, po skorygowaniu o GMT. Myślę, że to najczystsze rozwiązanie ze wszystkich, jeśli mam być szczery.
devios1
1
@chaiguy: to złe rozwiązanie, ponieważ zakłada, że ​​każdy dzień trwa 24 godziny - a tak nie jest. Ze względu na czas letni, dni mogą trwać 23, 24 lub 25 godzin.
vikingosegundo
Hmm, to prawda. Jednak średnia roczna wynosi dokładnie 24 godziny, więc w najgorszym przypadku może się zdarzyć tylko o godzinę. Być może można to wyraźnie sprawdzić.
devios1
2
@chaiguy: no. po prostu nigdy nie używaj kodu zawierającego 60 * 60 * 24. lub żyć z bólem głowy.
vikingosegundo
W większości miejsc na świecie „secondsFromGMT” zmienia się gwałtownie dwa razy w roku. Dużo bezpieczniej jest używać metod NSCalendar.
gnasher729
-4
NSDate *dateOne = yourDate;
NSDate *dateTwo = [NSDate date];  

switch ([dateOne compare:dateTwo])
{  
    case NSOrderedAscending:  
        NSLog(@”NSOrderedAscending”);  
        break;  

    case NSOrderedSame: 
        NSLog(@”NSOrderedSame”);  
        break;

    case NSOrderedDescending:  
        NSLog(@”NSOrderedDescending”);  
        break;  
}  
rajesh
źródło
4
To nie mówi ci, czy obie NSDates są tej samej daty, tylko która jest wcześniejsza, nawet jeśli są tego samego dnia.
David Kanarek