Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Working with NSDate components in Swift

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

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 will be the number of days and hours from aDate to now. But if i want the number of weeks instead of days

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"

date.week does not exist. So how I could resolve this?

like image 896
Giorgio Avatar asked Dec 07 '14 02:12

Giorgio


3 Answers

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"

like image 172
Leo Dabus Avatar answered Nov 10 '22 14:11

Leo Dabus


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

    }
}
like image 45
Jitendra Kulkarni Avatar answered Nov 10 '22 13:11

Jitendra Kulkarni


You're going to need to make another variable called "calendar" (NSCalendar object) and using the init "currentCalendar" method. Once you have the calendar object defined, call the method under calendar rangeOfUnit:NSWeekCalendarUnit inUnit:NSMonthCalendarUnit forDate:date. Then make a NSInteger called amountOfWeeks equal to the range the above function returns. The length of the range the function returns will be the number of weeks in that month.

like image 1
SierraMike Avatar answered Nov 10 '22 14:11

SierraMike