Работа с компонентами NSDate в Swift

у меня есть дата, которую мне нужно разделить на некоторые компоненты. например

let components = NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitHour
let date = calendar.components(components, fromDate: aDate, toDate: NSDate(), options: nil)
var dateToPrint = "(date.day) days  (date.hour) hours"

dateToPrint будет количество дней и часов от aDate до настоящего времени. Но если я хочу количество недель вместо дней

let components = NSCalendarUnit.CalendarUnitWeek | NSCalendarUnit.CalendarUnitHour
let date = calendar.components(components, fromDate: aDate, toDate: NSDate(), options: nil)
var dateToPrint = "(date.week) weeks (date.hour) hours"

дата.недели не существует. Так как я могу это решить?

4 ответов


Xcode 8.3.2 * Swift 3.1

extension Date {
    func xDays(_ x: Int) -> Date {
        return Calendar.current.date(byAdding: .day, value: x, to: self)!
    }
    func xWeeks(_ x: Int) -> Date {
        return Calendar.current.date(byAdding: .weekOfYear, value: x, to: self)!
    }
    var weeksHoursFromToday: DateComponents {
        return Calendar.current.dateComponents( [.weekOfYear, .hour], from: self, to: Date())
    }
    var relativeDateString: String {
        var result = ""
        if let weeks = weeksHoursFromToday.weekOfYear,
            let hours = weeksHoursFromToday.hour,
            weeks > 0 {
            result +=  "\(weeks) week"
            if weeks > 1 { result += "s" }
            if hours > 0 { result += " and " }
        }
        if let hours = weeksHoursFromToday.hour, hours > 0 {
            result +=  "\(hours) hour"
            if hours > 1 { result += "s" }
        }
        return result
    }
}

let today       = Date()                  // "May 1, 2017, 9:29 PM"
let yesterday   = Date().xDays(-1)        // "Apr 30, 2017, 9:29 PM"
let twoWeeksAgo = Date().xWeeks(-2)       // "Apr 17, 2017, 9:29 PM"
let anotherDate = DateComponents(calendar: .current, year: 2013, month: 12, day: 4).date!  // "Dec 4, 2013, 12:00 AM"
let anotherDate2 = DateComponents(calendar: .current, year: 2012, month: 12, day: 3).date!  // "Dec 3, 2012, 12:00 AM"


yesterday.relativeDateString          // "24 hours"
twoWeeksAgo.relativeDateString        // "2 weeks"
anotherDate.relativeDateString        // "177 weeks and 141 hours"
anotherDate2.relativeDateString       // "230 weeks and 21 hours"
yesterday.relativeDateString          // "24 hours"
twoWeeksAgo.relativeDateString        // "2 weeks"
anotherDate.relativeDateString              // "177 weeks and 141 hours"
anotherDate2.relativeDateString              // "230 weeks and 21 hours"


мне понравился API в решении Лео и использовал его. Однако я столкнулся с проблемой, когда я запустил его (Jun 13, 2015 9:33 AM PST). Симптомы были:

  1. когда дата находится в будущем, функция xFromToday возвращается результаты по запросу - (t-delta) (например, за 1 месяц в будущем Функции XFromToday будут возвращать (0, -4, -29, -719, -43199) для x=(месяц,неделя,дни, часы, минуты). Строка relativeDates вернется " через 1 неделю с сегодняшнего дня"

  2. когда дата находится в прошлом, результаты будут для -t, за исключением для относительной строки даты. Например, в течение одного месяца в прошлом я будет вам (1, 4, 31, 744, 44640). Относительная строка даты была: "4 недели и 744 часа"

Я не могу вставить тестовый вывод из-за конфиденциальности, но код с NSDate.тест() вставляется. У него также есть несколько других вещей, заимствованных из другого сообщения (цитируемого в коде), и некоторые материалы форматирования I писал.

import Foundation

// https://stackoverflow.com/questions/27339072/working-with-nsdate-components-in-swift

// **** Use with caution may not do what you expect. See the stackoverflow post above.  *******

public extension NSDate {


    func xDays(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitDay, value: x, toDate: self, options: nil)!
    }
    func xWeeks(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitWeekOfYear, value: x, toDate: self, options: nil)!
    }
    func xMonths(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitMonth, value: x, toDate: self, options: nil)!
    }
    func xMins(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitMinute, value: x, toDate: self, options: nil)!
    }
    func xHours(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitHour, value: x, toDate: self, options: nil)!
    }
    var hoursFromToday: Int{
        return NSCalendar.currentCalendar().components(.CalendarUnitHour, fromDate: self, toDate: NSDate(), options: nil).hour
    }
    var weeksFromToday: Int{
        return NSCalendar.currentCalendar().components(.CalendarUnitWeekOfYear, fromDate: self, toDate: NSDate(), options: nil).weekOfYear
    }
    var daysFromToday: Int{
        return NSCalendar.currentCalendar().components(.CalendarUnitDay, fromDate: self, toDate: NSDate(), options: nil).day
    }
    var monthsFromToday: Int{
        return NSCalendar.currentCalendar().components(.CalendarUnitMonth, fromDate: self, toDate: NSDate(), options: nil).month
    }
    var minsFromToday: Int{
        return NSCalendar.currentCalendar().components(.CalendarUnitMinute, fromDate: self, toDate: NSDate(), options: nil).minute
    }
    var relativeDateString: String {
        if weeksFromToday > 0 { return weeksFromToday > 1 ? "\(weeksFromToday) weeks and \(hoursFromToday) hours" : "\(weeksFromToday) week and \(hoursFromToday) hours"   }
        if hoursFromToday > 0 { return hoursFromToday > 1 ? "\(hoursFromToday) hours" : "\(hoursFromToday) hour"   }
        return ""
    }

    //Date Comparisions
    //https://stackoverflow.com/questions/26198526/nsdate-comparison-using-swift  


    func isGreaterThanDate(dateToCompare : NSDate) -> Bool
    {
        //Declare Variables
        var isGreater = false

        //Compare Values
        if self.compare(dateToCompare) == NSComparisonResult.OrderedDescending
        {
            isGreater = true
        }

        //Return Result
        return isGreater
    }

    func isLessThanDate(dateToCompare : NSDate) -> Bool
    {
        //Declare Variables
        var isLess = false

        //Compare Values
        if self.compare(dateToCompare) == NSComparisonResult.OrderedAscending
        {
            isLess = true
        }

        //Return Result
        return isLess
    }




    // Date printing converstions




    var dayMonthYear: String {
        let dateMonthYearFormatter: NSDateFormatter = NSDateFormatter()
        let currentLocale: NSLocale = NSLocale.currentLocale()
        let dateMonthYearFormatString: NSString! = NSDateFormatter.dateFormatFromTemplate("EdMMMyyyy",options: 0, locale: currentLocale)
        dateMonthYearFormatter.dateFormat = dateMonthYearFormatString as! String
        return dateMonthYearFormatter.stringFromDate(self)
    }

    var timeDayMonthYear: String {
        let dateMonthYearFormatter: NSDateFormatter = NSDateFormatter()
        let currentLocale: NSLocale = NSLocale.currentLocale()
        let dateMonthYearFormatString: NSString! = NSDateFormatter.dateFormatFromTemplate("EdMMMyyyy' ' HH':'mm",options: 0, locale: currentLocale)
        dateMonthYearFormatter.dateFormat = dateMonthYearFormatString as! String
        return dateMonthYearFormatter.stringFromDate(self)
    }


    var hourMin: String {
        let hourMinFormatter = NSDateFormatter();
        hourMinFormatter.dateFormat = "HH:mm"
        return hourMinFormatter.stringFromDate(self)
    }

    static func rfc3339DateFormatter() -> NSDateFormatter {
        let rfc3339DateFormatterRet = NSDateFormatter()
        let enUSPOSIXLocale: NSLocale = NSLocale(localeIdentifier: "en_US_POSIX")
        rfc3339DateFormatterRet.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
        rfc3339DateFormatterRet.locale = enUSPOSIXLocale
        rfc3339DateFormatterRet.timeZone = NSTimeZone(forSecondsFromGMT: 0)
        return rfc3339DateFormatterRet
    }


    var rfcString: String {
        return NSDate.rfc3339DateFormatter().stringFromDate(self)
    }
    func rfcDate(rfcString: String) -> NSDate {
    return NSDate.rfc3339DateFormatter().dateFromString(rfcString)!
    }

    func changeDate(toDate: NSDate) -> NSDate {

        let rfcToDate = toDate.rfcString
        let rfcSelf = self.rfcString
        let toDateArray : [String] = rfcToDate.componentsSeparatedByString("T")
        let selfArray : [String] = rfcSelf.componentsSeparatedByString("T")
        return rfcDate(toDateArray[0]+"T"+selfArray[1])

    }

    static func test(months: Int = 0, weeks: Int = 0, days: Int = 0, hrs: Int = 0, mins: Int = 0) {

        NSLog("****************** Start Testing of NSDate **************************")
        NSLog("Inputs: months:\(months) weeks:\(weeks) days:\(days) hrs: \(hrs) mins: \(mins)")
        var today = NSDate()
        NSLog("Today is: \(today.timeDayMonthYear)")

        var monthsFromToday = today.xMonths(months)
        NSLog("**  \(months) months from today: \(monthsFromToday.timeDayMonthYear)")
        NSLog("monthsFromToday returns: \(monthsFromToday.monthsFromToday)");
        NSLog("weeksFromToday returns: \(monthsFromToday.weeksFromToday)");
        NSLog("daysFromToday returns: \(monthsFromToday.daysFromToday)");
        NSLog("hoursFromToday returns: \(monthsFromToday.hoursFromToday)");
        NSLog("minsFromToday returns: \(monthsFromToday.minsFromToday)");
        NSLog("relativeDateString returns: \(monthsFromToday.relativeDateString)")

        var weeksFromToday = today.xWeeks(weeks)
        NSLog("**  \(weeks) weeks from today: \(weeksFromToday.timeDayMonthYear)")
        NSLog("weeksFromToday returns: \(weeksFromToday.weeksFromToday)");
        NSLog("relativeDateString returns: \(weeksFromToday.relativeDateString)")


        var daysFromToday = today.xDays(days)
        NSLog("**  \(days) days from today: \(daysFromToday.timeDayMonthYear)")
        NSLog("daysFromToday returns: \(daysFromToday.daysFromToday)");
        NSLog("relativeDateString returns: \(daysFromToday.relativeDateString)")

        var hrsFromToday = today.xHours(hrs)
        NSLog("**  \(hrs) hours from today: \(hrsFromToday.timeDayMonthYear)")
        NSLog("hoursFromToday returns: \(hrsFromToday.hoursFromToday)");
        NSLog("relativeDateString returns: \(hrsFromToday.relativeDateString)")

        var minsFromToday = today.xMins(mins)
        NSLog("**  \(mins) minutes from today: \(minsFromToday.timeDayMonthYear)")
        NSLog("minsFromToday returns: \(minsFromToday.minsFromToday)");
        NSLog("relativeDateString returns: \(minsFromToday.relativeDateString)")




        NSLog("__________________ End Testing of NSDate    _________________________")

    }
}

вот "исправление", которое я сделал. Я добавил функцию santizedDates (), которая возвращает даты "from" и "to", и множитель знака (+1/-1). 1-минутное смещение добавляется к диапазону дат, если сравнение было в будущем . Это работает для тестового случая. Также производит правильный и читаемый человеком вывод из relativeDateString (capitalizeFirst:Bool=false), такой как: "сегодня, 1 час 1 минута в прошлом" . Я разместил вопрос о почему это поведение происходит в первую очередь здесь:

NSCalendar.комплектующие.)(минута, возвращающая несогласованные значения

import Foundation

// https://stackoverflow.com/questions/27339072/working-with-nsdate-components-in-swift


public extension NSDate {


    func xDays(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitDay, value: x, toDate: self, options: nil)!
    }
    func xWeeks(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitWeekOfYear, value: x, toDate: self, options: nil)!
    }
    func xMonths(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitMonth, value: x, toDate: self, options: nil)!
    }
    func xMins(x:Int) -> NSDate {

        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitMinute, value: x, toDate: self, options: nil)!
    }
    func xHours(x:Int) -> NSDate {
        return NSCalendar.currentCalendar().dateByAddingUnit(.CalendarUnitHour, value: x, toDate: self, options: nil)!
    }
    var hoursFromToday: Int{

        var fromDate: NSDate = self
        var toDate: NSDate = NSDate()
        var sign: Int = -1
        (fromDate,toDate, sign) = self.sanitizedDates()

        return (sign * NSCalendar.currentCalendar().components(.CalendarUnitHour, fromDate: fromDate, toDate: toDate, options: nil).hour)
    }
    var weeksFromToday: Int{


        var fromDate: NSDate = self
        var toDate: NSDate = NSDate()
        var sign: Int = -1
        (fromDate,toDate,sign) = self.sanitizedDates()

        return (sign * NSCalendar.currentCalendar().components(.CalendarUnitWeekOfYear, fromDate: fromDate, toDate: toDate, options: nil).weekOfYear)
    }


    var daysFromToday: Int{

        var fromDate: NSDate = self
        var toDate: NSDate = NSDate()
        var sign: Int = -1
        (fromDate,toDate, sign) = self.sanitizedDates()


        return (sign * NSCalendar.currentCalendar().components(.CalendarUnitDay, fromDate: fromDate, toDate: toDate, options: nil).day)
    }
    var monthsFromToday: Int{


        var fromDate: NSDate = self
        var toDate: NSDate = NSDate()
        var sign: Int = -1
        (fromDate,toDate, sign) = self.sanitizedDates()

        return (sign * NSCalendar.currentCalendar().components(.CalendarUnitMonth, fromDate: fromDate, toDate: toDate, options: nil).month)

    }
    var minsFromToday: Int{


        var fromDate: NSDate = self
        var toDate: NSDate = NSDate()
        var sign: Int = -1
        var offset: Int = 0
        (fromDate,toDate,sign) = self.sanitizedDates()


        return ( sign * NSCalendar.currentCalendar().components(.CalendarUnitMinute, fromDate: fromDate, toDate: toDate, options: nil).minute)
    }

    func relativeDateString(capitalizeFirst:Bool = false) -> String {
        let days: Int = daysFromToday
        let mins: Int = minsFromToday % 60

        let tense: String = (minsFromToday > 0) ? " in the future" : " in the past"
        let hrs: Int =  hoursFromToday % 24
        var retString  = (capitalizeFirst) ? "Now" : "now"
        if(minsFromToday != 0) {
            if(days == 0) {
                retString = (capitalizeFirst) ? "Today" : "today"
                retString = (mins != 0 || hrs != 0) ? retString+"," : retString
            }
            else {
                let absDays = abs(days)
                retString = "\(absDays)"
                retString += (absDays > 1) ? " days" : " day"
            }
        if(hrs != 0) {
          let absHrs = abs(hrs)
             retString += " \(absHrs)"
            retString += (absHrs > 1) ? " hours" : " hour"
         }

            if(mins != 0) {
                let absMins = abs(mins)
                retString += " \(absMins)"
                retString += (absMins > 1) ? " minutes" : " minute"
            }

        retString += tense
        }

       return retString
    }

    //Date Comparisons
    //https://stackoverflow.com/questions/26198526/nsdate-comparison-using-swift  


    func isGreaterThanDate(dateToCompare : NSDate) -> Bool
    {
        //Declare Variables
        var isGreater = false

        //Compare Values
        if self.compare(dateToCompare) == NSComparisonResult.OrderedDescending
        {
            isGreater = true
        }

        //Return Result
        return isGreater
    }

    func isLessThanDate(dateToCompare : NSDate) -> Bool
    {
        //Declare Variables
        var isLess = false

        //Compare Values
        if self.compare(dateToCompare) == NSComparisonResult.OrderedAscending
        {
            isLess = true
        }

        //Return Result
        return isLess
    }




    // Date printing converstions




    var dayMonthYear: String {
        let dateMonthYearFormatter: NSDateFormatter = NSDateFormatter()
        let currentLocale: NSLocale = NSLocale.currentLocale()
        let dateMonthYearFormatString: NSString! = NSDateFormatter.dateFormatFromTemplate("EdMMMyyyy",options: 0, locale: currentLocale)
        dateMonthYearFormatter.dateFormat = dateMonthYearFormatString as! String
        return dateMonthYearFormatter.stringFromDate(self)
    }

    var timeDayMonthYear: String {
        let dateMonthYearFormatter: NSDateFormatter = NSDateFormatter()
        let currentLocale: NSLocale = NSLocale.currentLocale()
        let dateMonthYearFormatString: NSString! = NSDateFormatter.dateFormatFromTemplate("EdMMMyyyy' ' HH':'mm",options: 0, locale: currentLocale)
        dateMonthYearFormatter.dateFormat = dateMonthYearFormatString as! String
        return dateMonthYearFormatter.stringFromDate(self)
    }


    var hourMin: String {
        let hourMinFormatter = NSDateFormatter();
        hourMinFormatter.dateFormat = "HH:mm"
        return hourMinFormatter.stringFromDate(self)
    }

    static func rfc3339DateFormatter() -> NSDateFormatter {
        let rfc3339DateFormatterRet = NSDateFormatter()
        let enUSPOSIXLocale: NSLocale = NSLocale(localeIdentifier: "en_US_POSIX")
        rfc3339DateFormatterRet.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
        rfc3339DateFormatterRet.locale = enUSPOSIXLocale
        rfc3339DateFormatterRet.timeZone = NSTimeZone(forSecondsFromGMT: 0)
        return rfc3339DateFormatterRet
    }


    var rfcString: String {
        return NSDate.rfc3339DateFormatter().stringFromDate(self)
    }
    func rfcDate(rfcString: String) -> NSDate {
    return NSDate.rfc3339DateFormatter().dateFromString(rfcString)!
    }

    func changeDate(toDate: NSDate) -> NSDate {

        let rfcToDate = toDate.rfcString
        let rfcSelf = self.rfcString
        let toDateArray : [String] = rfcToDate.componentsSeparatedByString("T")
        let selfArray : [String] = rfcSelf.componentsSeparatedByString("T")
        return rfcDate(toDateArray[0]+"T"+selfArray[1])

    }

    private  func sanitizedDates() -> (fromDate: NSDate, toDate: NSDate, sign: Int ) {
        var toDate: NSDate = self
        var fromDate: NSDate = NSDate()
        var sign: Int = 1
        // For toDates in the past, results are reasonable, except for sign. 
        //In future dates, we to flip dates to make them past dates and add 1 minute for unknown reason.
        if(toDate.isGreaterThanDate(fromDate)) {
           // NSLog("****** Flipping dates ********")
            toDate = fromDate.xMins(-1) // In this case, the results are consistently shorter by a minute
            fromDate = self
            sign  = -1
        }
        return (fromDate,toDate,sign)

    }

    static func test(months: Int = 0, weeks: Int = 0, days: Int = 0, hrs: Int = 0, mins: Int = 0) {

        NSLog("****************** Start Testing of NSDate **************************")
        NSLog("Inputs: months:\(months) weeks:\(weeks) days:\(days) hrs: \(hrs) mins: \(mins)")
        var today = NSDate()
        NSLog("Today is: \(today.timeDayMonthYear)")

        var monthsFromToday = today.xMonths(months)
        NSLog("**  \(months) months from today: \(monthsFromToday.timeDayMonthYear)")
        NSLog("monthsFromToday returns: \(monthsFromToday.monthsFromToday)");
        NSLog("weeksFromToday returns: \(monthsFromToday.weeksFromToday)");
        NSLog("daysFromToday returns: \(monthsFromToday.daysFromToday)");
        NSLog("hoursFromToday returns: \(monthsFromToday.hoursFromToday)");
        NSLog("minsFromToday returns: \(monthsFromToday.minsFromToday)");
        NSLog("relativeDateString returns: \(monthsFromToday.relativeDateString())")

        var weeksFromToday = today.xWeeks(weeks)
        NSLog("**  \(weeks) weeks from today: \(weeksFromToday.timeDayMonthYear)")
        NSLog("weeksFromToday returns: \(weeksFromToday.weeksFromToday)");
        NSLog("relativeDateString returns: \(weeksFromToday.relativeDateString(capitalizeFirst:true))")
        NSLog("minsFromToday returns: \(weeksFromToday.minsFromToday)");
        NSLog("monthsFromToday returns: \(weeksFromToday.monthsFromToday)");
        NSLog("weeksFromToday returns: \(weeksFromToday.weeksFromToday)");
        NSLog("daysFromToday returns: \(weeksFromToday.daysFromToday)");
        NSLog("hoursFromToday returns: \(weeksFromToday.hoursFromToday)");
        NSLog("minsFromToday returns: \(weeksFromToday.minsFromToday)");




        var daysFromToday = today.xDays(days)
        NSLog("**  \(days) days from today: \(daysFromToday.timeDayMonthYear)")
        NSLog("daysFromToday returns: \(daysFromToday.daysFromToday)");
        NSLog("relativeDateString returns: \(daysFromToday.relativeDateString())")
        NSLog("minsFromToday returns: \(daysFromToday.minsFromToday)");
        NSLog("monthsFromToday returns: \(daysFromToday.monthsFromToday)");
        NSLog("weeksFromToday returns: \(daysFromToday.weeksFromToday)");
        NSLog("daysFromToday returns: \(daysFromToday.daysFromToday)");
        NSLog("hoursFromToday returns: \(daysFromToday.hoursFromToday)");
        NSLog("minsFromToday returns: \(daysFromToday.minsFromToday)");




        var hrsFromToday = today.xHours(hrs)
        NSLog("**  \(hrs) hours from today: \(hrsFromToday.timeDayMonthYear)")
        NSLog("hoursFromToday returns: \(hrsFromToday.hoursFromToday)");
        NSLog("minsFromToday returns: \(hrsFromToday.minsFromToday)");
        NSLog("monthsFromToday returns: \(hrsFromToday.monthsFromToday)");
        NSLog("weeksFromToday returns: \(hrsFromToday.weeksFromToday)");
        NSLog("daysFromToday returns: \(hrsFromToday.daysFromToday)");
        NSLog("hoursFromToday returns: \(hrsFromToday.hoursFromToday)");
        NSLog("minsFromToday returns: \(hrsFromToday.minsFromToday)");






        NSLog("relativeDateString returns: \(hrsFromToday.relativeDateString(capitalizeFirst:true))")

        var minsFromToday = today.xMins(mins)
        NSLog("**  \(mins) minutes from today: \(minsFromToday.timeDayMonthYear)")
        NSLog("minsFromToday returns: \(minsFromToday.minsFromToday)");
        NSLog("monthsFromToday returns: \(minsFromToday.monthsFromToday)");
        NSLog("weeksFromToday returns: \(minsFromToday.weeksFromToday)");
        NSLog("daysFromToday returns: \(minsFromToday.daysFromToday)");
        NSLog("hoursFromToday returns: \(minsFromToday.hoursFromToday)");
        NSLog("minsFromToday returns: \(minsFromToday.minsFromToday)");


        NSLog("relativeDateString returns: \(minsFromToday.relativeDateString())")




        NSLog("__________________ End Testing of NSDate    _________________________")

    }
}

вам нужно будет сделать другую переменную под названием " Календарь "(объект NSCalendar) и использовать метод init" currentCalendar". После определения объекта календаря вызовите метод в разделе Календарь rangeOfUnit:NSWeekCalendarUnit inUnit: NSMonthCalendarUnit forDate: date. Затем сделайте NSInteger под названием amountOfWeeks равным дальности выше функция возвращает. Длина диапазона функция возвращает номер недели в этом месяц.


не уверен, что вы хотите, но есть weekday, weekdayOrdinal, weekOfMonth и weekOfYear. (week была прекращена.)