Подтвердить что ты не робот

Как установить объект 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 */), чтобы можно было только одно из следующего:

  • день
  • Месяц
  • Год
  • Часы
  • Минуты
  • Секунды

Есть ли способ установить часы, минуты и секунды одновременно и сохранить дату (день/месяц/год)?

4b9b3361

Ответ 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!
}