web-dev-qa-db-fra.com

Comment puis-je définir un objet NSDate à minuit?

J'ai un objet NSDate et je veux le régler sur une heure arbitraire (disons, minuit) pour pouvoir utiliser le timeIntervalSince1970 fonction pour récupérer les données de manière cohérente sans se soucier de l'heure quand l'objet est créé.

J'ai essayé d'utiliser un NSCalendar et de modifier ses composants en utilisant certaines méthodes Objective-C, comme ceci:

let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!

let components: NSDateComponents = cal.components(NSCalendarUnit./* a unit of time */CalendarUnit, fromDate: date)
let newDate: NSDate = cal.dateFromComponents(components)

Le problème avec la méthode ci-dessus est que vous ne pouvez définir qu'une seule unité de temps (/* a unit of time */), vous ne pouvez donc avoir qu'une seule précision:

  1. Journée
  2. Mois
  3. An
  4. Heures
  5. Minutes
  6. Secondes

Existe-t-il un moyen de régler les heures, les minutes et les secondes en même temps et de conserver la date (jour/mois/année)?

50
AstroCB

Votre déclaration

Le problème avec la méthode ci-dessus est que vous ne pouvez définir qu'une seule unité de temps ...

n'est pas correcte. NSCalendarUnit est conforme au protocole RawOptionSetType qui hérite de BitwiseOperationsType. Cela signifie que les options peuvent être combinées au niveau du bit avec & Et |.

Dans Swift 2 (Xcode 7) , cela a été à nouveau modifié pour être un OptionSetType qui offre une interface de type ensemble, voir par exemple - Erreur lors de la combinaison de NSCalendarUnit avec OR (pipe) dans Swift 2. .

Par conséquent, ce qui suit se compile et fonctionne dans iOS 7 et iOS 8:

let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!

// Swift 1.2:
let components = cal.components(.CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear, fromDate: date)
// Swift 2:
let components = cal.components([.Day , .Month, .Year ], fromDate: date)

let newDate = cal.dateFromComponents(components)

(Notez que j'ai omis les annotations de type pour les variables, le compilateur Swift infère automatiquement le type à partir de l'expression sur le côté droit des affectations.)

La détermination du début d'un jour donné (minuit) peut également se faire avec la méthode rangeOfUnit() (iOS 7 et iOS 8):

let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var newDate : NSDate?

// Swift 1.2:
cal.rangeOfUnit(.CalendarUnitDay, startDate: &newDate, interval: nil, forDate: date)
// Swift 2:
cal.rangeOfUnit(.Day, startDate: &newDate, interval: nil, forDate: date)

Si votre cible de déploiement est iOS 8 alors c'est encore plus simple:

let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)

Mise à jour pour Swift 3 (Xcode 8):

let date = Date()
let cal = Calendar(identifier: .gregorian)
let newDate = cal.startOfDay(for: date)
84
Martin R

Oui.

Vous n'avez pas du tout besoin de jouer avec les composants de NSCalendar; vous pouvez simplement appeler la méthode dateBySettingHour et utiliser le paramètre ofDate avec votre date existante.

let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!

let newDate: NSDate = cal.dateBySettingHour(0, minute: 0, second: 0, ofDate: date, options: NSCalendarOptions())!

Pour Swift 3:

let date: Date = Date()
let cal: Calendar = Calendar(identifier: .gregorian)

let newDate: Date = cal.date(bySettingHour: 0, minute: 0, second: 0, of: date)!

Ensuite, pour obtenir votre temps depuis 1970, vous pouvez simplement faire

let time: NSTimeInterval = newDate.timeIntervalSince1970

dateBySettingHour a été introduit dans OS X Mavericks (10.9) et a pris en charge iOS avec iOS 8.

Déclaration en NSCalendar.h:

/*
    This API returns a new NSDate object representing the date calculated by setting hour, minute, and second to a given time.
    If no such time exists, the next available time is returned (which could, for example, be in a different day than the nominal target date).
    The intent is to return a date on the same day as the original date argument.  This may result in a date which is earlier than the given date, of course.
 */
- (NSDate *)dateBySettingHour:(NSInteger)h minute:(NSInteger)m second:(NSInteger)s ofDate:(NSDate *)date options:(NSCalendarOptions)opts NS_AVAILABLE(10_9, 8_0);
33
AstroCB

Voici un exemple de la façon dont vous le feriez, sans utiliser la fonction dateBySettingHour (pour vous assurer que votre code est toujours compatible avec les appareils iOS 7):

NSDate* now = [NSDate date];
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComponents = [gregorian components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit) fromDate:now];
NSDate* midnightLastNight = [gregorian dateFromComponents:dateComponents];

Beurk.

Il y a une raison pour laquelle je préfère coder en C # ...

Quelqu'un a envie de code lisible ..?

DateTime midnightLastNight = DateTime.Today;

;-)

8
Mike Gledhill

SwiftiOS 8 et plus: Les gens ont tendance à oublier que les objets Calendar et DateFormatter ont un TimeZone. Si vous ne définissez pas le fuseau horaire souhaité et que la valeur de fuseau horaire par défaut ne vous convient pas, les heures et minutes résultantes peuvent être désactivées.

Note: Dans une vraie application, vous pouvez encore optimiser ce code.

Remarque: Lorsque vous ne vous souciez pas des fuseaux horaires, les résultats peuvent être OK sur un appareil et mauvais sur un autre appareil simplement en raison de différents paramètres de fuseau horaire.

Remarque: Assurez-vous d'ajouter un identifiant de fuseau horaire existant! Ce code ne gère pas un nom de fuseau horaire manquant ou mal orthographié.

func dateTodayZeroHour() -> Date {
    var cal = Calendar.current
    cal.timeZone = TimeZone(identifier: "Europe/Paris")!
    return cal.startOfDay(for: Date())   
}

Vous pouvez même étendre la langue. Si le fuseau horaire par défaut vous convient, ne le définissez pas.

extension Date {
    var midnight: Date {
        var cal = Calendar.current
        cal.timeZone = TimeZone(identifier: "Europe/Paris")!
        return cal.startOfDay(for: self)
    }
    var midday: Date {
        var cal = Calendar.current
        cal.timeZone = TimeZone(identifier: "Europe/Paris")!
        return cal.date(byAdding: .hour, value: 12, to: self.midnight)!
    }
}

Et utilisez-le comme ceci:

let formatter = DateFormatter()
formatter.timeZone = TimeZone(identifier: "Europe/Paris")
formatter.dateFormat = "yyyy/MM/dd HH:mm:ss"

let midnight = Date().midnight
let midnightString = formatter.string(from: midnight)
let midday = Date().midday
let middayString = formatter.string(from: midday)

let wheneverMidnight = formatter.date(from: "2018/12/05 08:08:08")!.midnight
let wheneverMidnightString = formatter.string(from: wheneverMidnight)

print("dates: \(midnightString) \(middayString) \(wheneverMidnightString)")

Les conversions de chaînes et le DateFormatter sont nécessaires dans notre cas pour un certain formatage et pour déplacer le fuseau horaire car l'objet date en lui-même ne conserve pas ou ne se soucie pas d'une valeur de fuseau horaire.

Attention! La valeur résultante peut différer en raison d'un décalage de fuseau horaire quelque part dans votre chaîne de calcul!

5
t1ser

Juste au cas où quelqu'un chercherait ceci:

En utilisant SwiftDate vous pouvez simplement faire ceci:

Date().atTime(hour: 0, minute: 0, second: 0)
2
Francois Nadeau

À mon avis, la solution, qui est la plus facile à vérifier, mais peut-être pas la plus rapide, consiste à utiliser des chaînes.

func set( hours: Int, minutes: Int, seconds: Int, ofDate date: Date ) -> Date {

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

    let newDateString = "\(dateFormatter.string(from: date)) \(hours):\(minutes):\(seconds)"

    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

    return dateFormatter.date(from: newDateString)
}
1
Jason
func resetHourMinuteSecond(date: NSDate, hour: Int, minute: Int, second: Int) -> NSDate{
    let nsdate = NSCalendar.currentCalendar().dateBySettingHour(hour, minute: minute, second: second, ofDate: date, options: NSCalendarOptions(rawValue: 0))
    return nsdate!
}
0
Frank Hou