Jours rapides entre deux NSDates

111

Je me demande s'il existe une possibilité nouvelle et impressionnante d'obtenir le nombre de jours entre deux NSDates dans Swift / le "nouveau" Cocoa?

Par exemple, comme dans Ruby, je ferais:

(end_date - start_date).to_i
Linus
la source
5
Je pense que vous devez toujours utiliser NSCalendar et NSDateComponents (pour lesquels il doit y avoir des centaines de réponses sur SO). - Si vous cherchez quelque chose de "possibilité nouvelle et géniale", il serait utile de montrer votre solution actuelle à des fins de comparaison.
Martin R
1
C'est maintenant très simple et vous n'avez plus besoin d'utiliser quoi que ce soit "NS". J'ai tapé une réponse pour 2017, à copier et coller.
Fattie

Réponses:

247

Vous devez également tenir compte du décalage horaire. Par exemple, si vous comparez les dates 2015-01-01 10:00et 2015-01-02 09:00, les jours entre ces dates reviendront à 0 (zéro) car la différence entre ces dates est inférieure à 24 heures (23 heures).

Si votre objectif est d'obtenir le nombre exact de jours entre deux dates, vous pouvez contourner ce problème comme suit:

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

Version Swift 3 et 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
la source
14
Vous voudrez peut-être vérifier 12 heures (midi) au lieu de startOfDayForDate - devrait être moins susceptible de bork en raison du réglage des fuseaux horaires et de l'heure d'été.
brandonscript
11
La fixation des dates à midi peut se faire comme ceci:calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: firstDate))
MonsieurDart
Version plus courte pour la mise à midi ( startOfDay()semble être inutile): calendar.date(bySettingHour: 12, minute: 0, second: 0, of: firstDate).
jamix
52

Voici ma réponse pour 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
la source
J'ai utilisé avec succès cela avec les composants du message @vikingosegundo ci-dessus. Il renvoie un entier représentant le nombre correct de jours entre deux dates. <thumbs up>
Supprimer mon compte
Je l'aime mais le nom de la fonction devrait être "daysBetweenDates"
mbonness
4
Cela renvoie 0 si nous comparons todayettomorrow
tawheed
39

Je vois quelques réponses Swift3, je vais donc ajouter les miennes:

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

La dénomination semble plus Swifty, c'est une ligne et utilise la dernière dateComponents()méthode.

trevor-e
la source
28

J'ai traduit ma réponse 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)

résultat

<NSDateComponents: 0x10280a8a0>
     Day: 4

Le plus difficile était que l'auto-complétion insiste sur le fait que fromDate et toDate seraient NSDate?, mais en fait, ils doivent être NSDate!comme indiqué dans la référence.

Je ne vois pas à quoi ressemblerait une bonne solution avec un opérateur, car vous souhaitez spécifier l'unité différemment dans chaque cas. Vous pouvez renvoyer l'intervalle de temps, mais vous ne gagnerez pas beaucoup.

vikingosegundo
la source
Il semble que ce .DayCalendarUnitsoit obsolète. Je crois maintenant que vous devriez utiliser à la .CalendarUnitDayplace.
TaylorAllred
2
options est maintenant un paramètre attendu
Departamento B
2
Lancer Swift 2 cela fonctionne pour moi:let components = cal.components(.Day, fromDate: startDate, toDate: endDate, options: [])
Andrej
@TaylorAllred juste .Daymaintenant
William GP
28

Voici très belle Dateextension pour obtenir la différence entre les dates en années, mois, jours, heures, minutes, secondes

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
la source
1
datedevrait être sinceDatedans les paramètres de fonction.
TheTiger
@TheTiger - Merci beaucoup d'avoir mis en évidence la plus grosse erreur de cette réponse .. Je vais pratiquement tester et mettre à jour la réponse bientôt.
Krunal
1
Mon plaisir! Je l'ai testé dayset cela fonctionne très bien.
TheTiger
1
Bonne réponse. Je suggérerais seulement func years(since date: Date) -> Int? { return Calendar.current.dateComponents[.year], from: date, to: self).years }, et vous pourriez l'appeler comme let y = date1.years(since: date2). Cela pourrait être plus cohérent avec les conventions de dénomination modernes.
Rob
18

Mise à jour pour 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
la source
10

Voici la réponse pour Swift 3 (testé pour 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!
}

Alors tu peux l'appeler comme ça

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

Swift 3. Merci à Emin Buğra Saral ci-dessus pour la startOfDaysuggestion.

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

Usage:

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
normand
la source
1
il vaudrait certainement mieux les déplacer tous les deux à midi plutôt qu'à minuit pour éviter bien des problèmes.
Fattie
3

Les éléments intégrés à Swift sont toujours très basiques. Comme ils devraient l'être à ce stade précoce. Mais vous pouvez ajouter vos propres éléments avec le risque lié à la surcharge des opérateurs et des fonctions de domaine global. Ils seront cependant locaux à votre module.

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
la source
19
N'utilisez pas (24 * 60 * 60) pour la durée d'une journée. Cela ne prend pas en compte les transitions d'heure d'été.
Martin R
Je pense que NSDate s'ajusterait pour cela car il utilise toujours GMT et l'heure d'été n'est qu'un formatage ou une localisation à ce sujet. Bien sûr, cela devient plus compliqué pendant des mois, des années ou quoi que ce soit de durée vraiment variable.
Daniel Schlaug
1
@MartinR J'ai dû l'essayer pour le croire mais en effet, maintenant que je l'ai fait, j'ai aussi vu que Wikipédia le mentionne. Vous avez raison. Merci d'être têtu avec moi.
Daniel Schlaug
1
Là, édité pour être correct. Mais le côté éclatant a disparu.
Daniel Schlaug
1
il est défini par emplacement, point de temps et système de calendrier. le calendrier hébreu a un mois bissextile. il y a une superbe vidéo wwdc: effectuer un calcul de calendrier - un incontournable pour chaque codeur de cacao.
vikingosegundo
3

Voici ma réponse pour 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
la source
2

Il n'y a pas encore de bibliothèque standard spécifique à Swift; juste les types de base numérique, chaîne et collection.

Il est parfaitement possible de définir ces raccourcis en utilisant des extensions, mais en ce qui concerne les API prêtes à l'emploi, il n'y a pas de "nouveau" Cocoa; Swift correspond directement aux mêmes anciennes API Cocoa détaillées, telles qu'elles existent déjà.

Wes Campaigne
la source
2

Je vais ajouter ma version même si ce fil date d'un an. Mon code ressemble à ceci:

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

J'espère que cela aide quelqu'un.

À votre santé,

Andrew H
la source
2

Méthode d'Erin mise à jour vers Swift 3, cela montre les jours à partir d'aujourd'hui (sans tenir compte de l'heure de la journée)

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
la source
2

Cela renvoie une différence absolue en jours entre certains Dateet aujourd'hui:

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

puis utilisez-le:

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

Vous pouvez utiliser l'extension suivante:

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

Ensuite, vous pouvez l'appeler comme ceci:

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

Toute réponse est bonne. Mais pour les localisations, nous devons calculer un nombre de jours décimaux entre deux dates. afin que nous puissions fournir le format décimal durable.

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

Ensuite, utilisez-le comme

    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
la source
1

Il s'agit d'une version mise à jour de la réponse d'Emin pour Swift 5 qui incorpore la suggestion d'utiliser midi au lieu de minuit comme heure définitive pour comparer les jours. Il gère également l'échec potentiel de diverses fonctions de date en renvoyant une option.

    ///
    /// 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
la source
Vous devez utiliser le calendrier natif de Swift (supprimez le NS). L'utilisation d'un garde lors du réglage de l'heure à 12h est inutile. Cela n'échouera jamais.
Leo Dabus le
appeler startOfDay avant de régler l'heure à midi, il est également inutile.
Leo Dabus le
0

Swift 3 - Jours à partir d'aujourd'hui jusqu'à la date

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

Appelez une fonction comme celle-ci

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

une option plus simple serait de créer une extension sur 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
la source
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 vous avez besoin de l'année mois jours et des heures comme chaîne, utilisez ceci

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

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

Akash Shindhe
la source
0

Belle doublure pratique:

extension Date {
  var daysFromNow: Int {
    return Calendar.current.dateComponents([.day], from: Date(), to: self).day!
  }
}
ROM.
la source
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
la source
0

Solution 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
la source
-1

Version 2017, copier et coller

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
la source
-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
la source
1
qui mesure la différence entre la partie numérique des dates - Ie 21 janvier au 22 février donnera 1 jour, pas 32 jours comme il se doit
Peter Johnson