Días rápidos entre dos NSDates

110

Me pregunto si hay alguna posibilidad nueva e increíble de obtener la cantidad de días entre dos NSDates en Swift / el "nuevo" Cocoa.

Por ejemplo, como en Ruby, haría:

(end_date - start_date).to_i
Linus
fuente
5
Creo que todavía tiene que usar NSCalendar y NSDateComponents (para lo cual debe haber cientos de respuestas en SO). - Si está buscando algo con "posibilidades nuevas e increíbles" , sería útil mostrar su solución actual para comparar.
Martin R
1
Ahora es muy fácil y no es necesario utilizar "NS". Escribí una respuesta para 2017, para copiar y pegar.
Fattie

Respuestas:

246

También debes considerar la diferencia horaria. Por ejemplo, si compara las fechas 2015-01-01 10:00y 2015-01-02 09:00, los días entre esas fechas volverán como 0 (cero) ya que la diferencia entre esas fechas es menos de 24 horas (son 23 horas).

Si su propósito es obtener el número de día exacto entre dos fechas, puede solucionar este problema de la siguiente manera:

// 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

Versión Swift 3 y 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
fuente
14
En realidad, es posible que desee verificar las 12 p.m. (mediodía) en lugar de startOfDayForDate; debería ser menos probable que funcione debido al ajuste de las zonas horarias y el horario de verano.
brandonscript
11
Establecer las fechas al mediodía se puede hacer así:calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: firstDate))
MonsieurDart
Versión más corta para el ajuste del mediodía ( startOfDay()parece ser innecesario): calendar.date(bySettingHour: 12, minute: 0, second: 0, of: firstDate).
Jamix
52

Aquí está mi respuesta para 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
}
Ifaaw
fuente
Utilicé esto con éxito con los componentes de la publicación de @vikingosegundo anterior. Devuelve un número entero que representa el número correcto de días entre dos fechas. <thumbs up>
Eliminar mi cuenta
Me gusta, pero el nombre de la función debería ser "daysBetweenDates"
mbonness
4
Esto devuelve 0 si estamos comparando todayytomorrow
tawheed
39

Veo un par de respuestas de Swift3, así que agregaré las mías:

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

El nombre se siente más rápido, es una línea y usa el dateComponents()método más reciente .

trevor-e
fuente
28

Traduje mi respuesta de 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)

resultado

<NSDateComponents: 0x10280a8a0>
     Day: 4

La parte más difícil fue que el autocompletado insiste en que fromDate y toDate serían NSDate?, pero de hecho deben ser NSDate!como se muestra en la referencia.

No veo cómo se vería una buena solución con un operador, ya que desea especificar la unidad de manera diferente en cada caso. Puede devolver el intervalo de tiempo, pero no ganará mucho.

vikingosegundo
fuente
Parece que .DayCalendarUnitestá en desuso. Creo que ahora deberías usar .CalendarUnitDayen su lugar.
TaylorAllred
2
opciones ahora es un parámetro esperado
Departamento B
2
Al ejecutar Swift 2, esto me funciona:let components = cal.components(.Day, fromDate: startDate, toDate: endDate, options: [])
Andrej
@TaylorAllred hace un .Daymomento
William GP
28

Aquí es muy agradable, Dateextensión para obtener la diferencia entre fechas en años, meses, días, horas, minutos, segundos.

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
fuente
1
datedebe estar sinceDateen los parámetros de función.
TheTiger
@TheTiger - Muchas gracias por resaltar el mayor error de esta respuesta. Prácticamente probaré y actualizaré la respuesta pronto.
Krunal
1
¡El gusto es mio! Lo he probado daysy funciona bien.
TheTiger
1
Buena respuesta. Solo sugeriría func years(since date: Date) -> Int? { return Calendar.current.dateComponents[.year], from: date, to: self).years }, y podrías llamarlo como let y = date1.years(since: date2). Eso podría ser más consistente con las convenciones de nomenclatura modernas.
Rob
18

Actualización para 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
fuente
10

Aquí está la respuesta para Swift 3 (probado para 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!
}

Entonces puedes llamarlo así

let pickedDate: Date = sender.date
let NumOfDays: Int = daysBetweenDates(startDate: pickedDate, endDate: Date())
    print("Num of Days: \(NumOfDays)")
kazantatar
fuente
7

Swift 3. Gracias a Emin Buğra Saral arriba por la startOfDaysugerencia.

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

Uso:

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
normando
fuente
1
Definitivamente sería mejor moverlos a mediodía, en lugar de las 00:00 para evitar muchos problemas.
Fattie
3

Las cosas integradas en Swift siguen siendo muy básicas. Como deberían ser en esta etapa inicial. Pero puede agregar sus propias cosas con el riesgo de sobrecargar operadores y funciones de dominio global. Sin embargo, serán locales para su módulo.

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
fuente
19
No use (24 * 60 * 60) durante un día. Esto no tiene en cuenta las transiciones del horario de verano.
Martin R
Creo que NSDate se ajustaría a eso, ya que siempre usa GMT y el horario de verano es solo un formato o localización sobre eso. Sin embargo, seguro que se vuelve más complicado durante meses, años o cualquier cosa de duración realmente variable.
Daniel Schlaug
1
@MartinR Tuve que probarlo para creerlo, pero de hecho, ahora que lo hice, también vi que wikipedia menciona esto. Estás en lo correcto. Gracias por ser terco conmigo.
Daniel Schlaug
1
Allí, editado para ser correcto. Pero el brillo desapareció.
Daniel Schlaug
1
se define por ubicación, punto de tiempo y sistema de calendario. el calendario hebreo tiene un mes bisiesto. hay un gran video de wwdc: realizar el cálculo del calendario, una visita obligada para todo codificador de cacao.
vikingosegundo
3

Aquí está mi respuesta para 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
fuente
2

Apenas hay una biblioteca estándar específica de Swift todavía; sólo los tipos de colección, de cadena y de números básicos lean.

Es perfectamente posible definir tales abreviaturas usando extensiones, pero en lo que respecta a las API listas para usar, no hay Cocoa "nuevo"; Swift simplemente se asigna directamente a las mismas API detalladas de Cocoa que ya existen.

Wes Campaigne
fuente
2

Voy a agregar mi versión a pesar de que este hilo tiene un año. Mi código se ve así:

    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."

Espero que esto ayude a alguien.

Salud,

Andrew H
fuente
2

El método de Erin actualizado a Swift 3, muestra los días a partir de hoy (sin tener en cuenta la hora del día)

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
fuente
2

Esto devuelve una diferencia absoluta en días entre algunos Datey hoy:

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

y luego úsalo:

if someDate.daysFromToday() >= 7 {
  // at least a week from today
}
budidino
fuente
2

Puedes usar la siguiente extensión:

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

Entonces, puedes llamarlo así:

startDate.daysTo(endDate)
Mauro García
fuente
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
fuente
1

Toda respuesta es buena. Pero para las localizaciones necesitamos calcular un número de días decimales entre dos fechas. para que podamos proporcionar el formato decimal sostenible.

// 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
fuente
1
extension Date {
    func daysFromToday() -> Int {
        return Calendar.current.dateComponents([.day], from: self, to: Date()).day!
    }
}

Entonces úsalo como

    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
fuente
0

Swift 3: días desde hoy hasta la fecha

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

Llamar a una función como esta

// Days from today until date
   var examnDate = DateComponents()
   examnDate.year = 2016
   examnDate.month = 12
   examnDate.day = 15
   let daysCount = daysUntilDate(endDateComponents: examnDate)
dianakarenms
fuente
0

La opción más fácil sería crear una extensión en la fecha.

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




}

si necesita año, mes, días y horas como cadena, use esto

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

deje dc = tomorrow.completeOffset (desde: Fecha ())

Akash Shindhe
fuente
0

Un buen trazador de líneas práctico:

extension Date {
  var daysFromNow: Int {
    return Calendar.current.dateComponents([.day], from: Date(), to: self).day!
  }
}
ROM.
fuente
0

Rápido 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
fuente
0

Esta es una versión actualizada de la respuesta de Emin para Swift 5 que incorpora la sugerencia de usar el mediodía en lugar de la medianoche como la hora definitiva para comparar días. También maneja la falla potencial de varias funciones de fecha al devolver un opcional.

    ///
    /// 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
fuente
Debes usar el Calendario nativo de Swift (elimina el NS). El uso de guardia cuando se establece la hora a las 12 pm no tiene sentido. Nunca fallará.
Leo Dabus
llamar a startOfDay antes de establecer la hora al mediodía tampoco tiene sentido.
Leo Dabus
0

Solución rápida 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
embrollado
fuente
-1

Versión 2017, copiar y pegar

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
fuente
-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
fuente
1
que mide la diferencia entre la porción numérica de las fechas, es decir, del 21 de enero al 22 de febrero dará 1 día, no 32 días como debería ser
Peter Johnson