Search code examples
iosswift

Describe a period of elapsed time in a 'fuzzy' way


I'm trying to write code to simply describe how much time has elapsed since an event, but described in only one term, eg. years, months, days, hours, minutes...

It shouldn't show the exact elapsed time, just a general sense of how long ago it was.

I've written this code but it feels like there should be a much cleaner and simpler way of doing this. Any suggestions?

func fuzzyDistanceToNow(includeJustNow blIncludeJustNow: Bool, appendString stAppendString: String) -> String {
    
        let dDistance = abs(self.distance(to: Date())) //Distance to now
        
        let iYears = trunc(dDistance/31536000)
        let iMonths = trunc(dDistance/2628000)
        let iDays = trunc(dDistance/86400)
        let iHours = trunc(dDistance/3600)
        let iMinutes = trunc(dDistance/60)
        let iSeconds = trunc(dDistance) //Not really needed
        
        var sReturn: String
        
        if iYears >= 2 {
            sReturn = iYears.formatted()+" years"
        } else if iYears == 1 {
            sReturn = "1 year"
        } else if iMonths >= 2 {
            sReturn = iMonths.formatted()+" months"
        } else if iMonths == 1 {
            sReturn = "1 month"
        } else if iDays >= 2 {
            sReturn = iDays.formatted()+" days"
        } else if iDays == 1 {
            sReturn = "1 day"
        } else if iHours >= 2 {
            sReturn = iHours.formatted()+" hours"
        } else if iHours == 1 {
            sReturn = "1 hour"
        } else if iMinutes >= 2 {
            sReturn = iMinutes.formatted()+" minutes"
        } else if iMinutes == 1 {
            sReturn = "1 minute"
        } else if blIncludeJustNow {
            sReturn = "just now"
        } else {
            sReturn = ""
        }
        
        if stAppendString != "" && sReturn != "just now" {
            sReturn += stAppendString
        }
        return sReturn
    }
    
}

I've written the above code, but it doesn't feel particularly efficient. Are there some functions or shortcuts I'm missing?


Solution

  • Sweeper already told you the solution in a comment, but it needs to be logged as an answer.

    You want RelativeDateTimeFormatter. The docs describe it as "A formatter that creates locale-aware string representations of a relative date or time."

    This code:

    let formatter = RelativeDateTimeFormatter()
    
    for _ in 1...10 {
        let value = Double.random(in: -1_000_000...1_000_000)
        print(formatter.localizedString(fromTimeInterval: value))
    }
    

    Generates output like this:

    19 hours ago
    3 hours ago
    in 5 days
    2 days ago
    in 1 week
    2 days ago
    in 6 days
    1 week ago
    23 hours ago
    in 3 days
    

    Edit:

    Editing the code that generates TimeInterval values to cover a larger range of values:

    for _ in 1...10 {
        let power = Double(Int.random(in: 1...9) )
        let mantissa = Double.random(in: 0...1)
        let value = pow(10.0, power) * mantissa
        print( formatter.localizedString(fromTimeInterval: value))
    }
    

    Yields a wider range of time spans:

    in 2 seconds
    in 4 days
    in 11 minutes
    in 1 year
    in 1 week
    in 22 hours
    in 9 minutes
    in 30 years
    in 5 seconds
    in 1 minute
    

    Or, if you also set the formatter's dateTimeStyle to .named:

    formatter.dateTimeStyle = .named
    

    You get output like this:

    in 2 months
    tomorrow
    in 1 minute
    now
    in 2 hours
    in 23 hours
    in 15 hours
    next month
    in 18 hours
    in 5 months
    

    (For dates that are 1 time unit before or after the current date, .named gives you output like "last year" and "tomorrow".)