Golang : Human readable time elapsed format such as 5 days ago




Problem:

You need to convert a date time stamp such as this 02 March 1992 10:10:50 into a human readable format such as 5 minutes ago, 3 years ago or 10 days in future. Also, you need to get the elapsed time from past date and amount of time to reach a future date.

How to do that?

Solution:

The code that follows should do the job. Basically, it takes the year, month, day, hour, minute and second differences between now and the given date time stamp. The function in the code example does not depend on time.Since() or time.Until() as these functions return the elapsed duration in nanoseconds instead of individual year, month, day, etc..

Here you go!

UPDATE : See the comment below by Yegor M

https://socketloop.com/tutorials/golang-human-readable-time-elapsed-format-such-as-5-days-ago#comment-4522512474

Use these functions instead.


 func s(x float64) string {
  if int(x) == 1 {
 return ""
  }
  return "s"
 }

 func TimeElapsed(now time.Time, then time.Time, full bool) string {
  var parts []string
  var text string

  year2, month2, day2 := now.Date()
  hour2, minute2, second2 := now.Clock()

  year1, month1, day1 := then.Date()
  hour1, minute1, second1 := then.Clock()

  year := math.Abs(float64(int(year2 - year1)))
  month := math.Abs(float64(int(month2 - month1)))
  day := math.Abs(float64(int(day2 - day1)))
  hour := math.Abs(float64(int(hour2 - hour1)))
  minute := math.Abs(float64(int(minute2 - minute1)))
  second := math.Abs(float64(int(second2 - second1)))

  week := math.Floor(day / 7)

  if year > 0 {
 parts = append(parts, strconv.Itoa(int(year))+" year"+s(year))
  }

  if month > 0 {
 parts = append(parts, strconv.Itoa(int(month))+" month"+s(month))
  }

  if week > 0 {
 parts = append(parts, strconv.Itoa(int(week))+" week"+s(week))
  }

  if day > 0 {
 parts = append(parts, strconv.Itoa(int(day))+" day"+s(day))
  }

  if hour > 0 {
 parts = append(parts, strconv.Itoa(int(hour))+" hour"+s(hour))
  }

  if minute > 0 {
 parts = append(parts, strconv.Itoa(int(minute))+" minute"+s(minute))
  }

  if second > 0 {
 parts = append(parts, strconv.Itoa(int(second))+" second"+s(second))
  }

  if now.After(then) {
 text = " ago"
  } else {
 text = " after"
  }

  if len(parts) == 0 {
 return "just now"
  }

  if full {
 return strings.Join(parts, ", ") + text
  }
  return parts[0] + text
 }

NOTE : Below is the original source code from 6th May 2017


 package main

 import (
  "fmt"
  "math"
  "strconv"
  "time"
 )

 func RoundTime(input float64) int {
  var result float64

  if input < 0 {
 result = math.Ceil(input - 0.5)
  } else {
 result = math.Floor(input + 0.5)
  }

  // only interested in integer, ignore fractional
  i, _ := math.Modf(result)

  return int(i)
 }

 func TimeElapsed(inputDate string, full bool) string {
  var precise [8]string // this an array, not slice
  var text string
  var future bool // our crystal ball

  layOut := "02/01/2006 15:04:05" // dd/mm/yyyy hh:mm:ss
  formattedDate, err := time.Parse(layOut, inputDate)

  if err != nil {
 panic(err)
  }

  // get years, months and days
  // and get hours, minutes, seconds
  now := time.Now()
  year2, month2, day2 := now.Date()
  hour2, minute2, second2 := now.Clock()

  year1, month1, day1 := formattedDate.Date()
  hour1, minute1, second1 := formattedDate.Clock()

  // are we forecasting the future?
  if (year2 - year1) < 0 {
 future = true
  }

  if (month2 - month1) < 0 {
 future = true
  }
  if (day2 - day1) < 0 {
 future = true
  }
  if (hour2 - hour1) < 0 {
 future = true
  }
  if (minute2 - minute1) < 0 {
 future = true
  }
  if (second2 - second1) < 0 {
 future = true
  }

  // convert negative to positive numbers
  year := math.Abs(float64(int(year2 - year1)))
  month := math.Abs(float64(int(month2 - month1)))
  day := math.Abs(float64(int(day2 - day1)))
  hour := math.Abs(float64(int(hour2 - hour1)))
  minute := math.Abs(float64(int(minute2 - minute1)))
  second := math.Abs(float64(int(second2 - second1)))

  week := math.Floor(day / 7)

  // Ouch!, no if-else short hand - see https://golang.org/doc/faq#Does_Go_have_a_ternary_form

  if year > 0 {
 if int(year) == 1 {
 precise[0] = strconv.Itoa(int(year)) + " year"
 } else {
 precise[0] = strconv.Itoa(int(year)) + " years"
 }
  }

  if month > 0 {
 if int(month) == 1 {
 precise[1] = strconv.Itoa(int(month)) + " month"
 } else {
 precise[1] = strconv.Itoa(int(month)) + " months"
 }
  }

  if week > 0 {
 if int(week) == 1 {
 precise[2] = strconv.Itoa(int(week)) + " week"
 } else {
 precise[2] = strconv.Itoa(int(week)) + " weeks"
 }
  }

  if day > 0 {
 if int(day) == 1 {
 precise[3] = strconv.Itoa(int(day)) + " day"
 } else {
 precise[3] = strconv.Itoa(int(day)) + " days"
 }
  }

  if hour > 0 {
 if int(hour) == 1 {
 precise[4] = strconv.Itoa(int(hour)) + " hour"
 } else {
 precise[4] = strconv.Itoa(int(hour)) + " hours"
 }
  }

  if minute > 0 {
 if int(minute) == 1 {
 precise[5] = strconv.Itoa(int(minute)) + " minute"
 } else {
 precise[5] = strconv.Itoa(int(minute)) + " minutes"
 }
  }

  if second > 0 {
 if int(second) == 1 {
 precise[6] = strconv.Itoa(int(second)) + " second"
 } else {
 precise[6] = strconv.Itoa(int(second)) + " seconds"
 }
  }

  for _, v := range precise {
 if v != "" {
 // no comma after second
 if v[len(v)-5:len(v)-1] != "cond" {
 precise[7] += v + ", "
 } else {
 precise[7] += v
 }
 }
  }

  if !future {
 text = " ago."
  } else {
 text = " in future."
  }

  if full {
 return precise[7] + text
  } else {
 // return the first non-empty position
 for k, v := range precise {
 if v != "" {
 return precise[k] + text
 }
 }
  }
  return "invalid date"
 }

 func main() {
  fmt.Println("The date time stamp now is : ", time.Now())
  fmt.Println("02 March 1992 10:10 full=true is : ", TimeElapsed("02/03/1992 10:10:10", true))
  fmt.Println("02 March 1992 10:10 full=false is : ", TimeElapsed("02/03/1992 10:10:10", false))

  fmt.Println("06 May 2020 17:33 full=false is : ", TimeElapsed("06/05/2020 17:33:10", false))
  fmt.Println("06 May 2020 17:33 full=true is : ", TimeElapsed("06/05/2020 17:33:10", true))

  fmt.Println("06 May 2017 17:33 full=false is : ", TimeElapsed("06/05/2017 17:33:10", false))
  fmt.Println("06 May 2017 17:33 full=true is : ", TimeElapsed("06/05/2017 17:33:10", true))
 }

Sample output:

The date time stamp now is : 2017-05-06 20:46:22.391247331 +0800 MYT

02 March 1992 10:10 full=true is : 25 years, 2 months, 4 days, 10 hours, 36 minutes, 12 seconds ago.

02 March 1992 10:10 full=false is : 25 years ago.

06 May 2020 17:33 full=false is : 3 years in future.

06 May 2020 17:33 full=true is : 3 years, 3 hours, 13 minutes, 12 seconds in future.

06 May 2017 17:33 full=false is : 3 hours ago.

06 May 2017 17:33 full=true is : 3 hours, 13 minutes, 12 seconds ago.

Happy time traveling!

References:

https://golang.org/pkg/time/#Time.Clock

https://www.socketloop.com/tutorials/golang-get-current-time-from-the-internet-time-server-ntp-example

http://stackoverflow.com/questions/1416697/converting-timestamp-to-time-ago-in-php-e-g-1-day-ago-2-days-ago

  See also : Golang : Get local time and equivalent time in different time zone





By Adam Ng

IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.


Advertisement