Как установить объект NSDate до полуночи?
У меня есть объект NSDate
, и я хочу установить его в произвольное время (например, полночь), чтобы я мог использовать функцию timeIntervalSince1970
для последовательного извлечения данных, не беспокоясь о времени создания объекта.
Я попытался использовать NSCalendar
и изменил его компоненты, используя некоторые методы Objective-C, например:
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)
Проблема с указанным выше методом заключается в том, что вы можете установить только одну единицу времени (/* a unit of time */
), чтобы можно было только одно из следующего:
- день
- Месяц
- Год
- Часы
- Минуты
- Секунды
Есть ли способ установить часы, минуты и секунды одновременно и сохранить дату (день/месяц/год)?
Ответы
Ответ 1
Ваше выражение
Проблема с вышеуказанным методом заключается в том, что вы можете установить только одну единицу время...
неверно. NSCalendarUnit
соответствует протоколу RawOptionSetType
, который
наследует от BitwiseOperationsType
. Это означает, что параметры могут быть побитовыми
в сочетании с &
и |
.
В Swift 2 (Xcode 7) это снова было изменено, чтобы быть
a OptionSetType
, который предлагает наборный интерфейс, см.
например Ошибка объединения NSCalendarUnit с OR (трубой) в Swift 2.0.
Поэтому следующие компиляции и работы в iOS 7 и 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)
(Обратите внимание, что я пропустил аннотации типов для переменных, компилятор Swift
автоматически выводит тип из выражения в правой части
присвоения.)
Определение начала данного дня (полночь) также может быть выполнено
с помощью метода rangeOfUnit()
(iOS 7 и 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)
Если целью развертывания является iOS 8, то это еще проще:
let date = NSDate()
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let newDate = cal.startOfDayForDate(date)
Обновление для Swift 3 (Xcode 8):
let date = Date()
let cal = Calendar(identifier: .gregorian)
let newDate = cal.startOfDay(for: date)
Ответ 2
Да.
Вам вообще не нужно возиться с компонентами NSCalendar
; вы можете просто вызвать метод dateBySettingHour
и использовать параметр ofDate
с вашей существующей датой.
let date: NSDate = NSDate()
let cal: NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
let newDate: NSDate = cal.dateBySettingHour(0, minute: 0, second: 0, ofDate: date, options: NSCalendarOptions())!
Для 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)!
Затем, чтобы получить свое время с 1970 года, вы можете просто сделать
let time: NSTimeInterval = newDate.timeIntervalSince1970
dateBySettingHour
был введен в OS X Mavericks (10.9) и получил поддержку iOS с iOS 8.
Объявление 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);
Ответ 3
Вот пример того, как вы это сделаете, без использования функции dateBySettingHour
(чтобы убедиться, что ваш код по-прежнему совместим с устройствами 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];
Тьфу.
Есть причина, по которой я предпочитаю кодирование в С#...
Кому-нибудь нравится какой-нибудь читаемый код..?
DateTime midnightLastNight = DateTime.Today;
; -)
Ответ 4
На мой взгляд, решение, которое проще всего проверять, но, возможно, не самое быстрое, - это использовать строки.
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)
}
Ответ 5
Swift iOS 8 и. Люди склонны забывать, что объекты Calendar и DateFormatter имеют TimeZone. Если вы не установили желаемый тембр, а значение часового пояса по умолчанию для вас не подходит, то полученные часы и минуты могут быть отключены.
Примечание: В реальном приложении вы можете еще немного оптимизировать этот код.
Примечание.. Вы даже можете потратить время на поиск ошибок, если на одном устройстве все в порядке, а на другом - тот же код не работает только из-за разных настроек часового пояса.
Примечание. Обязательно добавьте существующий идентификатор часового пояса! Этот код не обрабатывает ошибку.
func dateTodayZeroHour() -> Date {
var cal = Calendar.current
cal.timeZone = TimeZone(identifier: "Europe/Paris")!
return cal.startOfDay(for: Date())
}
Вы даже можете расширить язык. Если часовой пояс по умолчанию подходит для вас, не устанавливайте его.
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)!
}
}
И используйте его следующим образом:
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)")
Преобразования строк и DateFormatter необходимы в нашем случае для некоторого форматирования и для перемещения часового пояса, поскольку сам объект даты не сохраняет значение часового пояса.
Примечание. Но значение может отличаться из-за смещения часового пояса где-то в вашей вычислительной цепочке!
Ответ 6
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!
}