Working with NSDate components in Swift

后端 未结 4 465
梦毁少年i
梦毁少年i 2021-02-08 04:19

I have a date that I need to split in some components. for example

let components = NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitHour
let date = c         


        
4条回答
  •  无人共我
    2021-02-08 04:28

    I liked the API in Leo's solution and used it. However I ran into problem when I ran it (Jun 13, 2015 9:33 AM PST). The symptoms were:

    1. When the date is in the future, xFromToday function were returning results for -(t-delta) (for example for 1 month in the future the XFromToday functions would return (0, -4, -29, -719, -43199) for x=(month,week,days, hrs, mins) . The relativeDates string would return "1 weeks from today"

    2. When the date is in the past, the results would be for -t, except for relative date string. For example, for one month in the past, I would get (1, 4, 31, 744, 44640). Relative date string was: "4 weeks and 744 hrs"

    I cannot paste the test output due to confidentiality, but the code with NSDate.test() is pasted. It also has few other stuff borrowed from another post (quoted in code) and some formatting stuff I wrote.

    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    _________________________")
    
        }
    }
    

    Here is the "fix" I did. I have added a santizedDates() function that returns "from" and "to" dates , and a sign multiplier (+1/-1). A 1-minute offset is added to date span if the comparison was in the future . It works for the test case. Also produces correct and human readable output from relativeDateString(capitalizeFirst:Bool=false) such as: "today, 1 hour 1 minute in the past" . I have posted a question as to why this behavior happens in the first place here:

    NSCalendar.components().minute returning inconsistent values

    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    _________________________")
    
        }
    }
    

提交回复
热议问题