Dias rápidos entre dois NSDates

111

Estou me perguntando se há alguma possibilidade nova e incrível de obter a quantidade de dias entre dois NSDates em Swift / o "novo" Cocoa?

Por exemplo, como em Ruby, eu faria:

(end_date - start_date).to_i
Linus
fonte
5
Acho que você ainda precisa usar NSCalendar e NSDateComponents (para os quais deve haver centenas de respostas no SO). - Se você está procurando por algo "nova e incrível possibilidade" , seria útil mostrar sua solução atual para comparação.
Martin R
1
Isso agora é muito fácil e você não precisa usar nada "NS". Digitei uma resposta para 2017, para copiar e colar.
Fattie de

Respostas:

247

Você também deve considerar a diferença de fuso horário. Por exemplo, se você comparar as datas 2015-01-01 10:00e 2015-01-02 09:00, os dias entre essas datas retornarão como 0 (zero), pois a diferença entre essas datas é menor que 24 horas (é 23 horas).

Se o seu objetivo é obter o número exato do dia entre duas datas, você pode contornar esse problema desta forma:

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

Versão Swift 3 e 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
fonte
14
Na verdade, você pode querer verificar 12h (meio-dia) em vez de startOfDayForDate - deve ser menos provável que bork devido ao ajuste de fusos horários e DST.
brandonscript de
11
Definir as datas para meio-dia pode ser feito assim:calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: firstDate))
MonsieurDart
Versão mais curta para a criação do meio-dia ( startOfDay()parece ser desnecessário): calendar.date(bySettingHour: 12, minute: 0, second: 0, of: firstDate).
Jamix
52

Aqui está minha resposta para o 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
fonte
Eu usei isso com sucesso com os componentes da postagem @vikingosegundo acima. Ele retorna um número inteiro que representa o número correto de dias entre duas datas. <polegares para cima>
Excluir minha conta
Eu gosto, mas o nome da função deve ser "daysBetweenDates"
mbonness
4
Retorna 0 se estivermos comparando todayetomorrow
tawheed de
39

Vejo algumas respostas do Swift3, então vou adicionar as minhas:

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

A nomenclatura parece mais rápida, é uma linha e usa o dateComponents()método mais recente .

trevor-e
fonte
28

Traduzi minha resposta 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

A parte mais difícil é que o autocompletar insiste em fromDate e toDate seria NSDate?, mas na verdade eles devem ser NSDate!conforme mostrado na referência.

Não vejo como seria uma boa solução com um operador, já que você deseja especificar a unidade de forma diferente em cada caso. Você poderia retornar o intervalo de tempo, mas não ganhará muito.

vikingosegundo
fonte
Parece que .DayCalendarUnitestá obsoleto. Eu acredito que agora você deve usar em seu .CalendarUnitDaylugar.
TaylorAllred
2
options agora é um parâmetro esperado
Departamento B
2
Executando o Swift 2 funciona para mim:let components = cal.components(.Day, fromDate: startDate, toDate: endDate, options: [])
Andrej
@TaylorAllred .Dayagora mesmo
William GP
28

Aqui é muito bom, Dateextensão para obter diferença entre datas em anos, meses, dias, 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
fonte
1
datedeve estar sinceDateem parâmetros de função.
TheTiger
@TheTiger - Muito obrigado por destacar o maior erro dessa resposta. Vou praticamente testar e atualizar a resposta em breve.
Krunal
1
O prazer é meu! Eu testei dayse funciona bem.
TheTiger
1
Boa resposta. Eu apenas sugeriria func years(since date: Date) -> Int? { return Calendar.current.dateComponents[.year], from: date, to: self).years }, e você poderia chamá-lo de let y = date1.years(since: date2). Isso pode ser mais consistente com as convenções de nomenclatura modernas.
Rob
18

Atualização 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
fonte
10

Aqui está a resposta para Swift 3 (testado 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!
}

Então você pode chamá-lo assim

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

Swift 3. Obrigado a Emin Buğra Saral acima pela startOfDaysugestão.

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
fonte
1
definitivamente seria melhor mover os dois para o meio-dia, em vez de 00:00 para evitar muitos problemas.
Fattie de
3

As coisas embutidas no Swift ainda são muito básicas. Como deveriam estar nesta fase inicial. Mas você pode adicionar suas próprias coisas com o risco de sobrecarregar operadores e funções de domínio global. Eles serão locais para o seu 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
fonte
19
Não use (24 * 60 * 60) para a duração de um dia. Isso não leva em consideração as transições do horário de verão.
Martin R
Acho que o NSDate se ajustaria a isso, pois sempre usa GMT e o horário de verão é apenas uma formatação ou localização sobre isso. Com certeza fica mais complicado por meses, anos ou qualquer coisa de comprimento realmente variável.
Daniel Schlaug
1
@MartinR Tive que tentar para acreditar, mas na verdade, agora que tentei, também vi que a wikipedia menciona isso. Você está certo. Obrigado por ser teimoso comigo.
Daniel Schlaug
1
Lá, editado para ficar correto. Mas o brilho meio que foi embora.
Daniel Schlaug
1
é definido por local, ponto do tempo e sistema de calendário. o calendário hebraico tem um mês bissexto. há um ótimo vídeo wwdc: realizando cálculos de calendário - algo imperdível para todos os codificadores de cacau.
vikingosegundo
3

Aqui está minha resposta para o 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
fonte
2

Quase não existe nenhuma biblioteca padrão específica do Swift ainda; apenas os tipos básicos de números, strings e coleções.

É perfeitamente possível definir essas abreviações usando extensões, mas no que diz respeito às APIs prontas para usar, não existe um "novo" Cocoa; O Swift apenas mapeia diretamente para as mesmas APIs Cocoa antigas e verbosas, conforme já existem.

Wes Campaigne
fonte
2

Vou adicionar minha versão, embora este tópico tenha um ano. Meu código é parecido com este:

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

Eu espero que isso ajude alguém.

Felicidades,

Andrew H
fonte
2

Método de Erin atualizado para Swift 3, mostra os dias a partir de hoje (desconsiderando a hora do dia)

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

Isso retorna uma diferença absoluta em dias entre alguns Datee hoje:

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

e então usá-lo:

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

Você pode usar a seguinte extensão:

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

Então, você pode chamá-lo assim:

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

Todas as respostas são boas. Mas, para localizações, precisamos calcular um número de dias decimais entre duas datas. para que possamos fornecer o formato decimal sustentável.

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

Então use 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
fonte
1

Esta é uma versão atualizada da resposta de Emin para o Swift 5 que incorpora a sugestão de usar meio-dia em vez de meia-noite como horário definitivo para comparar dias. Ele também lida com a falha potencial de várias funções de data retornando um 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
fonte
Você deve usar o calendário nativo Swift (largue o NS). O uso de guarda ao definir a hora para 12h é inútil. Nunca falhará.
Leo Dabus
chamar startOfDay antes de definir a hora como meio-dia também é inútil.
Leo Dabus
0

Swift 3 - dias de hoje até a data

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

Chamar função 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
fonte
0

opção mais fácil seria criar uma extensão em Date

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




}

se precisar de ano, mês, dias e horas como string, use este

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

deixe dc = tomorrow.completeOffset (de: Data ())

Akash Shindhe
fonte
0

Bom forro prático:

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

Swift 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
fonte
0

Solução 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
confuso
fonte
-1

Versão 2017, copie e cole

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
fonte
-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
fonte
1
que mede a diferença entre a parte do número das datas - Ie, 21 de janeiro a 22 de fevereiro dará 1 dia, não 32 dias como deveria
Peter Johnson