Szybkie dni między dwoma NSDates

112

Zastanawiam się, czy jest jakaś nowa i niesamowita możliwość uzyskania liczby dni między dwoma NSDates w Swift / „nowym” Cocoa?

Np. Jak w Rubim zrobiłbym:

(end_date - start_date).to_i
Linus
źródło
5
Myślę, że nadal musisz używać NSCalendar i NSDateComponents (na które muszą być setki odpowiedzi na SO). - Jeśli szukasz czegoś „nowego i niesamowitego”, warto byłoby pokazać swoje obecne rozwiązanie do porównania.
Martin R,
1
Jest to teraz bardzo łatwe i nie musisz niczego używać "NS". Wpisałem odpowiedź na 2017 rok, aby skopiować i wkleić.
Fattie

Odpowiedzi:

248

Musisz również wziąć pod uwagę różnicę czasu. Na przykład, jeśli porównasz daty 2015-01-01 10:00i 2015-01-02 09:00, dni między tymi datami zostaną zwrócone jako 0 (zero), ponieważ różnica między tymi datami jest mniejsza niż 24 godziny (to 23 godziny).

Jeśli Twoim celem jest uzyskanie dokładnej liczby dni między dwiema datami, możesz obejść ten problem w następujący sposób:

// Assuming that firstDate and secondDate are defined
// ...

let calendar = NSCalendar.currentCalendar()

// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDayForDate(firstDate)
let date2 = calendar.startOfDayForDate(secondDate)

let flags = NSCalendarUnit.Day
let components = calendar.components(flags, fromDate: date1, toDate: date2, options: [])

components.day  // This will return the number of day(s) between dates

Wersja Swift 3 i Swift 4

let calendar = Calendar.current

// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: firstDate)
let date2 = calendar.startOfDay(for: secondDate)

let components = calendar.dateComponents([.day], from: date1, to: date2)
Emin Bugra Saral
źródło
14
W rzeczywistości możesz chcieć sprawdzić godzinę 12:00 (południe) zamiast startOfDayForDate - powinno być mniej prawdopodobne, że zepsujesz się ze względu na dostosowanie stref czasowych i czasu letniego.
brandonscript
11
Daty na południe można ustawić w następujący sposób:calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: firstDate))
MonsieurDart
Krótsza wersja do ustawiania w południe ( startOfDay()wydaje się być niepotrzebne) calendar.date(bySettingHour: 12, minute: 0, second: 0, of: firstDate).
jamix
52

Oto moja odpowiedź na Swift 2:

func daysBetweenDates(startDate: NSDate, endDate: NSDate) -> Int
{
    let calendar = NSCalendar.currentCalendar()

    let components = calendar.components([.Day], fromDate: startDate, toDate: endDate, options: [])

    return components.day
}
iphaaw
źródło
Z powodzeniem użyłem tego z komponentami postu @vikingosegundo powyżej. Zwraca liczbę całkowitą reprezentującą prawidłową liczbę dni między dwiema datami. <kciuki w górę>
Usuń moje konto
Podoba mi się, ale nazwa funkcji powinna brzmieć „daysBetweenDates”
mbonness
4
Zwraca to 0, jeśli porównujemy todayitomorrow
tawheed
39

Widzę kilka odpowiedzi Swift3, więc dodam własne:

public static func daysBetween(start: Date, end: Date) -> Int {
   Calendar.current.dateComponents([.day], from: start, to: end).day!
}

Nazewnictwo wydaje się bardziej szybkie, to jedna linia i przy użyciu najnowszej dateComponents()metody.

trevor-e
źródło
28

Przetłumaczyłem moją odpowiedź Objective-C

let start = "2010-09-01"
let end = "2010-09-05"

let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"

let startDate:NSDate = dateFormatter.dateFromString(start)
let endDate:NSDate = dateFormatter.dateFromString(end)

let cal = NSCalendar.currentCalendar()


let unit:NSCalendarUnit = .Day

let components = cal.components(unit, fromDate: startDate, toDate: endDate, options: nil)


println(components)

wynik

<NSDateComponents: 0x10280a8a0>
     Day: 4

Najtrudniejsze było to, że autouzupełnianie nalega, aby fromDate i toDate były NSDate?, ale w rzeczywistości muszą być takie, NSDate!jak pokazano w pliku referencyjnym.

Nie widzę, jak wyglądałoby dobre rozwiązanie z operatorem, ponieważ w każdym przypadku chcesz inaczej określić jednostkę. Możesz zwrócić przedział czasowy, ale niewiele zyskasz.

vikingosegundo
źródło
Wygląda na .DayCalendarUnitto, że jest przestarzały. Uważam, że teraz powinieneś użyć .CalendarUnitDayzamiast tego.
TaylorAllred
2
options jest teraz oczekiwanym parametrem
Departamento B
2
Uruchamianie Swift 2 to działa dla mnie:let components = cal.components(.Day, fromDate: startDate, toDate: endDate, options: [])
Andrej
@TaylorAllred właśnie .Dayteraz
William GP
28

Oto bardzo ładne Daterozszerzenie, aby uzyskać różnicę między datami w latach, miesiącach, dniach, godzinach, minutach, sekundach

extension Date {

    func years(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.year], from: sinceDate, to: self).year
    }

    func months(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.month], from: sinceDate, to: self).month
    }

    func days(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.day], from: sinceDate, to: self).day
    }

    func hours(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.hour], from: sinceDate, to: self).hour
    }

    func minutes(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.minute], from: sinceDate, to: self).minute
    }

    func seconds(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.second], from: sinceDate, to: self).second
    }

}
Krunal
źródło
1
datepowinny znajdować się sinceDatew parametrach funkcji.
TheTiger
@TheTiger - Bardzo dziękuję za podkreślenie największego błędu w tej odpowiedzi. Wkrótce praktycznie przetestuję i zaktualizuję odpowiedź.
Krunal
1
Cała przyjemność po mojej stronie! Przetestowałem to daysi działa dobrze.
TheTiger
1
Dobra odpowiedź. Sugerowałbym tylko func years(since date: Date) -> Int? { return Calendar.current.dateComponents[.year], from: date, to: self).years }i możesz to nazwać jako let y = date1.years(since: date2). To może być bardziej spójne z nowoczesnymi konwencjami nazewnictwa.
Rob
18

Aktualizacja dla Swift 3 iOS 10 Beta 4

func daysBetweenDates(startDate: Date, endDate: Date) -> Int {
    let calendar = Calendar.current
    let components = calendar.dateComponents([Calendar.Component.day], from: startDate, to: endDate)
    return components.day!
}
ChaosSpeeder
źródło
10

Oto odpowiedź na Swift 3 (testowany pod kątem IOS 10 Beta)

func daysBetweenDates(startDate: Date, endDate: Date) -> Int
{
    let calendar = Calendar.current
    let components = calendar.components([.day], from: startDate, to: endDate, options: [])
    return components.day!
}

Wtedy możesz to tak nazwać

let pickedDate: Date = sender.date
let NumOfDays: Int = daysBetweenDates(startDate: pickedDate, endDate: Date())
    print("Num of Days: \(NumOfDays)")
kazantatar
źródło
7

Swift 3. Podziękowania dla Emin Buğra Saral powyżej za startOfDaysugestię.

extension Date {

    func daysBetween(date: Date) -> Int {
        return Date.daysBetween(start: self, end: date)
    }

    static func daysBetween(start: Date, end: Date) -> Int {
        let calendar = Calendar.current

        // Replace the hour (time) of both dates with 00:00
        let date1 = calendar.startOfDay(for: start)
        let date2 = calendar.startOfDay(for: end)

        let a = calendar.dateComponents([.day], from: date1, to: date2)
        return a.value(for: .day)!
    }
}

Stosowanie:

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let start = dateFormatter.date(from: "2017-01-01")!
let end = dateFormatter.date(from: "2018-01-01")!

let diff = Date.daysBetween(start: start, end: end) // 365
Norman
źródło
1
zdecydowanie lepiej byłoby przenieść ich oboje na południe niż o 00:00, aby uniknąć wielu problemów.
Fattie
3

Elementy wbudowane w szybkość są nadal bardzo podstawowe. Tak jak powinni być na tym wczesnym etapie. Ale możesz dodawać własne rzeczy z ryzykiem, które wiąże się z przeciążeniem operatorów i funkcji domeny globalnej. Będą jednak lokalne dla twojego modułu.

let now = NSDate()
let seventies = NSDate(timeIntervalSince1970: 0)

// Standard solution still works
let days = NSCalendar.currentCalendar().components(.CalendarUnitDay, 
           fromDate: seventies, toDate: now, options: nil).day

// Flashy swift... maybe...
func -(lhs:NSDate, rhs:NSDate) -> DateRange {
    return DateRange(startDate: rhs, endDate: lhs)
}

class DateRange {
    let startDate:NSDate
    let endDate:NSDate
    var calendar = NSCalendar.currentCalendar()
    var days: Int {
        return calendar.components(.CalendarUnitDay, 
               fromDate: startDate, toDate: endDate, options: nil).day
    }
    var months: Int {
        return calendar.components(.CalendarUnitMonth, 
               fromDate: startDate, toDate: endDate, options: nil).month
    }
    init(startDate:NSDate, endDate:NSDate) {
        self.startDate = startDate
        self.endDate = endDate
    }
}

// Now you can do this...
(now - seventies).months
(now - seventies).days
Daniel Schlaug
źródło
19
Nie używaj (24 * 60 * 60) na długość dnia. Nie uwzględnia to zmian czasu letniego.
Martin R,
Myślę, że NSDate dostosowałoby się do tego, ponieważ zawsze używa GMT, a czas letni to tylko formatowanie lub lokalizacja. Z pewnością staje się to trudniejsze przez miesiące, lata lub cokolwiek o naprawdę zmiennej długości.
Daniel Schlaug,
1
@MartinR Musiałem spróbować, aby w to uwierzyć, ale rzeczywiście, teraz, gdy to zrobiłem, zobaczyłem również, że wikipedia o tym wspomina. Masz rację. Dzięki, że byłeś dla mnie uparty.
Daniel Schlaug,
1
Tam, poprawione. Ale błyskotliwość odeszła.
Daniel Schlaug,
1
jest określany przez lokalizację, czas i system kalendarza. kalendarz hebrajski ma miesiąc przestępny. istnieje świetne wideo wwdc: wykonywanie obliczeń kalendarza - pozycja obowiązkowa dla każdego programisty kakao.
vikingosegundo
3

Oto moja odpowiedź na Swift 3:

func daysBetweenDates(startDate: NSDate, endDate: NSDate, inTimeZone timeZone: TimeZone? = nil) -> Int {
    var calendar = Calendar.current
    if let timeZone = timeZone {
        calendar.timeZone = timeZone
    }
    let dateComponents = calendar.dateComponents([.day], from: startDate.startOfDay, to: endDate.startOfDay)
    return dateComponents.day!
}
Alen Liang
źródło
2

Nie ma jeszcze żadnej standardowej biblioteki specyficznej dla języka Swift; tylko podstawowe typy liczbowe, łańcuchowe i kolekcjonerskie.

Jest całkowicie możliwe zdefiniowanie takich skrótów za pomocą rozszerzeń, ale jeśli chodzi o rzeczywiste gotowe interfejsy API, nie ma „nowego” Cocoa; Swift po prostu mapuje bezpośrednio do tych samych starych, pełnych funkcji API Cocoa, które już istnieją.

Wes Campaigne
źródło
2

Dodam swoją wersję, mimo że ten wątek ma już rok. Mój kod wygląda tak:

    var name = txtName.stringValue // Get the users name

    // Get the date components from the window controls
    var dateComponents = NSDateComponents()
    dateComponents.day = txtDOBDay.integerValue
    dateComponents.month = txtDOBMonth.integerValue
    dateComponents.year = txtDOBYear.integerValue

    // Make a Gregorian calendar
    let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)

    // Get the two dates we need
    var birthdate = calendar?.dateFromComponents(dateComponents)
    let currentDate = NSDate()

    var durationDateComponents = calendar?.components(NSCalendarUnit.CalendarUnitDay, fromDate: birthdate!, toDate: currentDate, options: nil)

    let numberOfDaysAlive = durationDateComponents?.day

    println("\(numberOfDaysAlive!)")

    txtGreeting.stringValue = "Hello \(name), You have been alive for \(numberOfDaysAlive!) days."

Mam nadzieję, że to komuś pomoże.

Twoje zdrowie,

Andrew H.
źródło
2

Metoda Erin została zaktualizowana do Swift 3, pokazuje dni od dzisiaj (bez względu na porę dnia)

func daysBetweenDates( endDate: Date) -> Int 
    let calendar: Calendar = Calendar.current 
    let date1 = calendar.startOfDay(for: Date()) 
    let date2 = calendar.startOfDay(for: secondDate) 
    return calendar.dateComponents([.day], from: date1, to: date2).day! 
}
Peter Johnson
źródło
2

Zwraca to bezwzględną różnicę w dniach między niektórymi Datea dzisiejszymi:

extension Date {
  func daysFromToday() -> Int {
    return abs(Calendar.current.dateComponents([.day], from: self, to: Date()).day!)
  }
}

a następnie użyj go:

if someDate.daysFromToday() >= 7 {
  // at least a week from today
}
budidino
źródło
2

Możesz użyć następującego rozszerzenia:

public extension Date {
    func daysTo(_ date: Date) -> Int? {
        let calendar = Calendar.current

        // Replace the hour (time) of both dates with 00:00
        let date1 = calendar.startOfDay(for: self)
        let date2 = calendar.startOfDay(for: date)

        let components = calendar.dateComponents([.day], from: date1, to: date2)
        return components.day  // This will return the number of day(s) between dates
    }
}

Następnie możesz to nazwać tak:

startDate.daysTo(endDate)
Mauro García
źródło
1

Swift 3.2

extension DateComponentsFormatter {
    func difference(from fromDate: Date, to toDate: Date) -> String? {
        self.allowedUnits = [.year,.month,.weekOfMonth,.day]
        self.maximumUnitCount = 1
        self.unitsStyle = .full
        return self.string(from: fromDate, to: toDate)
    }
}
Adam Smaka
źródło
1

Cała odpowiedź jest dobra. Ale w przypadku lokalizacji musimy obliczyć liczbę dni dziesiętnych między dwiema datami. dzięki czemu możemy zapewnić trwały format dziesiętny.

// This method returns the fractional number of days between to dates
func getFractionalDaysBetweenDates(date1: Date, date2: Date) -> Double {

    let components = Calendar.current.dateComponents([.day, .hour], from: date1, to: date2)

    var decimalDays = Double(components.day!)
    decimalDays += Double(components.hour!) / 24.0

    return decimalDays
}
Durul Dalkanat
źródło
1
extension Date {
    func daysFromToday() -> Int {
        return Calendar.current.dateComponents([.day], from: self, to: Date()).day!
    }
}

Następnie użyj tego jak

    func dayCount(dateString: String) -> String{
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "MMM dd,yyyy hh:mm a"
        let fetchedDate = dateFormatter.date(from: dateString)


        let day = fetchedDate?.daysFromToday()
        if day! > -1{
            return "\(day!) days passed."
        }else{
        return "\(day! * -1) days left."
        }
    }
Murad Al Wajed
źródło
1

To jest zaktualizowana wersja odpowiedzi Emin dla Swift 5, która zawiera sugestię, aby użyć południa zamiast północy jako ostatecznego czasu porównywania dni. Obsługuje również potencjalną awarię różnych funkcji daty, zwracając opcjonalny.

    ///
    /// This is an approximation; it does not account for time differences. It will set the time to 1200 (noon) and provide the absolute number
    /// of days between now and the given date. If the result is negative, it should be read as "days ago" instead of "days from today."
    /// Returns nil if something goes wrong initializing or adjusting dates.
    ///

    func daysFromToday() -> Int?
    {
        let calendar = NSCalendar.current

        // Replace the hour (time) of both dates with noon. (Noon is less likely to be affected by DST changes, timezones, etc. than midnight.)
        guard let date1 = calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: Date())),
              let date2 = calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: self)) else
        {
            return nil
        }

        return calendar.dateComponents([.day], from: date1, to: date2).day
    }
Bryan
źródło
Powinieneś użyć natywnego kalendarza Swift (upuść NS). Stosowanie osłony przy ustawianiu godziny na 12:00 jest bezcelowe. To nigdy nie zawiedzie.
Leo Dabus
wywołanie startOfDay przed ustawieniem godziny na południe też jest bezcelowe.
Leo Dabus
0

Swift 3 - dni od dziś do daty

func daysUntilDate(endDateComponents: DateComponents) -> Int
    {
        let cal = Calendar.current
        var components = cal.dateComponents([.era, .year, .month, .day], from: NSDate() as Date)
        let today = cal.date(from: components)
        let otherDate = cal.date(from: endDateComponents)

        components = cal.dateComponents([Calendar.Component.day], from: (today! as Date), to: otherDate!)
        return components.day!
    }

Wywołaj taką funkcję

// Days from today until date
   var examnDate = DateComponents()
   examnDate.year = 2016
   examnDate.month = 12
   examnDate.day = 15
   let daysCount = daysUntilDate(endDateComponents: examnDate)
dianakarenms
źródło
0

łatwiejszą opcją byłoby utworzenie rozszerzenia w dniu

public extension Date {

        public var currentCalendar: Calendar {
            return Calendar.autoupdatingCurrent
        }

        public func daysBetween(_ date: Date) -> Int {
            let components = currentCalendar.dateComponents([.day], from: self, to: date)
            return components.day!
        }
    }
Suhit Patil
źródło
0
  func completeOffset(from date:Date) -> String? {

    let formatter = DateComponentsFormatter()
    formatter.unitsStyle = .brief

    return  formatter.string(from: Calendar.current.dateComponents([.year,.month,.day,.hour,.minute,.second], from: date, to: self))




}

jeśli potrzebujesz rok, miesiąc, dni i godziny jako ciąg znaków, użyj tego

var tomorrow = Calendar.current.date (byAdding: .day, value: 1, to: Date ())!

let dc = tomorrow.completeOffset (from: Date ())

Akash Shindhe
źródło
0

Ładna, poręczna jedna wkładka:

extension Date {
  var daysFromNow: Int {
    return Calendar.current.dateComponents([.day], from: Date(), to: self).day!
  }
}
Rom.
źródło
0

Szybki 4

 func getDateHeader(indexPath: Int) -> String {
    let formatter2 = DateFormatter()
    formatter2.dateFormat = "MM-dd-yyyy"
    var dateDeadline : Date?

    dateDeadline = formatter2.date(from: arrCompletedDate[indexPath] as! String)

    let currentTime = dateDeadline?.unixTimestamp
    let calendar = NSCalendar.current

    let date = NSDate(timeIntervalSince1970: Double(currentTime!))
    if calendar.isDateInYesterday(date as Date) { return "Yesterday" }
    else if calendar.isDateInToday(date as Date) { return "Today" }
    else if calendar.isDateInTomorrow(date as Date) { return "Tomorrow" }
    else {
        let startOfNow = calendar.startOfDay(for: NSDate() as Date)
        let startOfTimeStamp = calendar.startOfDay(for: date as Date)
        let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp)
        let day = components.day!
        if day < 1 { return "\(abs(day)) days ago" }
        else { return "In \(day) days" }
    }
}
Niraj Paul
źródło
0

Rozwiązanie Swift 5.2.4:

import UIKit

let calendar = Calendar.current

let start = "2010-09-01"
let end = "2010-09-05"

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"

let firstDate = dateFormatter.date(from: start)!
let secondDate = dateFormatter.date(from: end)!

// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDay(for: firstDate)
let date2 = calendar.startOfDay(for: secondDate)

let components = calendar.dateComponents([Calendar.Component.day], from: date1, to: date2)

components.day  // This will return the number of day(s) between dates
mazy
źródło
-1

Wersja 2017, kopiuj i wklej

func simpleIndex(ofDate: Date) -> Int {
    
    // index here just means today 0, yesterday -1, tomorrow 1 etc.
    
    let c = Calendar.current
    let todayRightNow = Date()
    
    let d = c.date(bySetting: .hour, value: 13, of: ofDate)
    let t = c.date(bySetting: .hour, value: 13, of: todayRightNow)
    
    if d == nil || today == nil {
    
        print("weird problem simpleIndex#ofDate")
        return 0
    }
    
    let r = c.dateComponents([.day], from: today!, to: d!)
    // yesterday is negative one, tomorrow is one
    
    if let o = r.value(for: .day) {
        
        return o
    }
    else {
    
        print("another weird problem simpleIndex#ofDate")
        return 0
    }
}
Fattie
źródło
-2
let calendar = NSCalendar.currentCalendar();
let component1 = calendar.component(.Day, fromDate: fromDate)
let component2 = calendar.component(.Day, fromDate: toDate)
let difference  = component1 - component2
Raj Aggrawal
źródło
1
który mierzy różnicę między częściami liczbowymi dat - tj. 21 stycznia do 22 lutego da 1 dzień, a nie 32 dni, jak powinno
Peter Johnson