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:
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)?
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)
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);
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;
;-)
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!
Juste au cas où quelqu'un chercherait ceci:
En utilisant SwiftDate vous pouvez simplement faire ceci:
Date().atTime(hour: 0, minute: 0, second: 0)
À 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)
}
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!
}