How to calculate "time ago" in Java?

110,508

Solution 1

Take a look at the PrettyTime library.

It's quite simple to use:

import org.ocpsoft.prettytime.PrettyTime;

PrettyTime p = new PrettyTime();
System.out.println(p.format(new Date()));
// prints "moments ago"

You can also pass in a locale for internationalized messages:

PrettyTime p = new PrettyTime(new Locale("fr"));
System.out.println(p.format(new Date()));
// prints "à l'instant"

As noted in the comments, Android has this functionality built into the android.text.format.DateUtils class.

Solution 2

Have you considered the TimeUnit enum? It can be pretty useful for this kind of thing

    try {
        SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy");
        Date past = format.parse("01/10/2010");
        Date now = new Date();

        System.out.println(TimeUnit.MILLISECONDS.toMillis(now.getTime() - past.getTime()) + " milliseconds ago");
        System.out.println(TimeUnit.MILLISECONDS.toMinutes(now.getTime() - past.getTime()) + " minutes ago");
        System.out.println(TimeUnit.MILLISECONDS.toHours(now.getTime() - past.getTime()) + " hours ago");
        System.out.println(TimeUnit.MILLISECONDS.toDays(now.getTime() - past.getTime()) + " days ago");
    }
    catch (Exception j){
        j.printStackTrace();
    }

Solution 3

I take RealHowTo and Ben J answers and make my own version:

public class TimeAgo {
public static final List<Long> times = Arrays.asList(
        TimeUnit.DAYS.toMillis(365),
        TimeUnit.DAYS.toMillis(30),
        TimeUnit.DAYS.toMillis(1),
        TimeUnit.HOURS.toMillis(1),
        TimeUnit.MINUTES.toMillis(1),
        TimeUnit.SECONDS.toMillis(1) );
public static final List<String> timesString = Arrays.asList("year","month","day","hour","minute","second");

public static String toDuration(long duration) {

    StringBuffer res = new StringBuffer();
    for(int i=0;i< TimeAgo.times.size(); i++) {
        Long current = TimeAgo.times.get(i);
        long temp = duration/current;
        if(temp>0) {
            res.append(temp).append(" ").append( TimeAgo.timesString.get(i) ).append(temp != 1 ? "s" : "").append(" ago");
            break;
        }
    }
    if("".equals(res.toString()))
        return "0 seconds ago";
    else
        return res.toString();
}
public static void main(String args[]) {
    System.out.println(toDuration(123));
    System.out.println(toDuration(1230));
    System.out.println(toDuration(12300));
    System.out.println(toDuration(123000));
    System.out.println(toDuration(1230000));
    System.out.println(toDuration(12300000));
    System.out.println(toDuration(123000000));
    System.out.println(toDuration(1230000000));
    System.out.println(toDuration(12300000000L));
    System.out.println(toDuration(123000000000L));
}}

which will print the following

0 second ago
1 second ago
12 seconds ago
2 minutes ago
20 minutes ago
3 hours ago
1 day ago
14 days ago
4 months ago
3 years ago

Solution 4

  public class TimeUtils {
    
      public final static long ONE_SECOND = 1000;
      public final static long SECONDS = 60;
    
      public final static long ONE_MINUTE = ONE_SECOND * 60;
      public final static long MINUTES = 60;
      
      public final static long ONE_HOUR = ONE_MINUTE * 60;
      public final static long HOURS = 24;
      
      public final static long ONE_DAY = ONE_HOUR * 24;
    
      private TimeUtils() {
      }
    
      /**
       * converts time (in milliseconds) to human-readable format
       *  "<w> days, <x> hours, <y> minutes and (z) seconds"
       */
      public static String millisToLongDHMS(long duration) {
        StringBuilder res = new StringBuilder();
        long temp = 0;
        if (duration >= ONE_SECOND) {
          temp = duration / ONE_DAY;
          if (temp > 0) {
            duration -= temp * ONE_DAY;
            res.append(temp).append(" day").append(temp > 1 ? "s" : "")
               .append(duration >= ONE_MINUTE ? ", " : "");
          }
    
          temp = duration / ONE_HOUR;
          if (temp > 0) {
            duration -= temp * ONE_HOUR;
            res.append(temp).append(" hour").append(temp > 1 ? "s" : "")
               .append(duration >= ONE_MINUTE ? ", " : "");
          }
    
          temp = duration / ONE_MINUTE;
          if (temp > 0) {
            duration -= temp * ONE_MINUTE;
            res.append(temp).append(" minute").append(temp > 1 ? "s" : "");
          }
    
          if (!res.toString().equals("") && duration >= ONE_SECOND) {
            res.append(" and ");
          }
    
          temp = duration / ONE_SECOND;
          if (temp > 0) {
            res.append(temp).append(" second").append(temp > 1 ? "s" : "");
          }
          return res.toString();
        } else {
          return "0 second";
        }
      }
    
   
      public static void main(String args[]) {
        System.out.println(millisToLongDHMS(123));
        System.out.println(millisToLongDHMS((5 * ONE_SECOND) + 123));
        System.out.println(millisToLongDHMS(ONE_DAY + ONE_HOUR));
        System.out.println(millisToLongDHMS(ONE_DAY + 2 * ONE_SECOND));
        System.out.println(millisToLongDHMS(ONE_DAY + ONE_HOUR + (2 * ONE_MINUTE)));
        System.out.println(millisToLongDHMS((4 * ONE_DAY) + (3 * ONE_HOUR)
            + (2 * ONE_MINUTE) + ONE_SECOND));
        System.out.println(millisToLongDHMS((5 * ONE_DAY) + (4 * ONE_HOUR)
            + ONE_MINUTE + (23 * ONE_SECOND) + 123));
        System.out.println(millisToLongDHMS(42 * ONE_DAY));
        /*
          output :
                0 second
                5 seconds
                1 day, 1 hour
                1 day and 2 seconds
                1 day, 1 hour, 2 minutes
                4 days, 3 hours, 2 minutes and 1 second
                5 days, 4 hours, 1 minute and 23 seconds
                42 days
         */
    }
}

more @Format a duration in milliseconds into a human-readable format

Solution 5

This is based on RealHowTo's answer so if you like it, give him/her some love too.

This cleaned up version allows you to specify the range of time you might be interested in.

It also handles the " and " part a little differently. I often find when joining strings with a delimiter it's ofter easier to skip the complicated logic and just delete the last delimiter when you're done.

import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

public class TimeUtils {

    /**
     * Converts time to a human readable format within the specified range
     *
     * @param duration the time in milliseconds to be converted
     * @param max      the highest time unit of interest
     * @param min      the lowest time unit of interest
     */
    public static String formatMillis(long duration, TimeUnit max, TimeUnit min) {
        StringBuilder res = new StringBuilder();

        TimeUnit current = max;

        while (duration > 0) {
            long temp = current.convert(duration, MILLISECONDS);

            if (temp > 0) {
                duration -= current.toMillis(temp);
                res.append(temp).append(" ").append(current.name().toLowerCase());
                if (temp < 2) res.deleteCharAt(res.length() - 1);
                res.append(", ");
            }

            if (current == min) break;

            current = TimeUnit.values()[current.ordinal() - 1];
        }

        // clean up our formatting....

        // we never got a hit, the time is lower than we care about
        if (res.lastIndexOf(", ") < 0) return "0 " + min.name().toLowerCase();

        // yank trailing  ", "
        res.deleteCharAt(res.length() - 2);

        //  convert last ", " to " and"
        int i = res.lastIndexOf(", ");
        if (i > 0) {
            res.deleteCharAt(i);
            res.insert(i, " and");
        }

        return res.toString();
    }
}

Little code to give it a whirl:

import static java.util.concurrent.TimeUnit.*;

public class Main {

    public static void main(String args[]) {
        long[] durations = new long[]{
            123,
            SECONDS.toMillis(5) + 123,
            DAYS.toMillis(1) + HOURS.toMillis(1),
            DAYS.toMillis(1) + SECONDS.toMillis(2),
            DAYS.toMillis(1) + HOURS.toMillis(1) + MINUTES.toMillis(2),
            DAYS.toMillis(4) + HOURS.toMillis(3) + MINUTES.toMillis(2) + SECONDS.toMillis(1),
            DAYS.toMillis(5) + HOURS.toMillis(4) + MINUTES.toMillis(1) + SECONDS.toMillis(23) + 123,
            DAYS.toMillis(42)
        };

        for (long duration : durations) {
            System.out.println(TimeUtils.formatMillis(duration, DAYS, SECONDS));
        }

        System.out.println("\nAgain in only hours and minutes\n");

        for (long duration : durations) {
            System.out.println(TimeUtils.formatMillis(duration, HOURS, MINUTES));
        }
    }

}

Which will output the following:

0 seconds
5 seconds 
1 day and 1 hour 
1 day and 2 seconds 
1 day, 1 hour and 2 minutes 
4 days, 3 hours, 2 minutes and 1 second 
5 days, 4 hours, 1 minute and 23 seconds 
42 days 

Again in only hours and minutes

0 minutes
0 minutes
25 hours 
24 hours 
25 hours and 2 minutes 
99 hours and 2 minutes 
124 hours and 1 minute 
1008 hours 

And in case anyone ever needs it, here's a class that will convert any string like the above back into milliseconds. It's pretty useful for allowing people to specify timeouts of various things in readable text.

Share:
110,508
jts
Author by

jts

Updated on July 08, 2022

Comments

  • jts
    jts almost 2 years

    In Ruby on Rails, there is a feature that allows you to take any Date and print out how "long ago" it was.

    For example:

    8 minutes ago
    8 hours ago
    8 days ago
    8 months ago
    8 years ago
    

    Is there an easy way to do this in Java?

  • David Blevins
    David Blevins about 13 years
    I ended up using a revised version of this. Posted my revisions for you.
  • Somatik
    Somatik over 12 years
    In case you are working on android you can use this: android.text.format.DateUtils#getRelativeTimeSpanString()
  • Ajay S
    Ajay S about 10 years
    Can you please add some more description to your answer, link only answer is not good for now.
  • zakmck
    zakmck almost 10 years
    David Blevins, more examples about PrettyTime: stackoverflow.com/questions/3859288/… Big -1 for reinventing the wheel once more and not recommending a 3rd party library :-p
  • Piotr
    Piotr almost 10 years
    Really cool. And it is really easy to add other time units such as week(s)
  • Weblance
    Weblance over 9 years
    @Somatik if you need to get this on a non-android platform, you can view that class on AOSP.
  • PhiLho
    PhiLho over 9 years
    In most cases, you want a "smart" display, ie. instead of 5125 minutes ago, you tell x days ago.
  • Hardik Parmar
    Hardik Parmar over 8 years
    @ataylor how this use in Android ??
  • fangzhzh
    fangzhzh over 8 years
    this one deserves more upvotes. First of all, no library needed. It's still clean, elegant and easy to change.
  • Diogo Gomes
    Diogo Gomes about 8 years
    small typo: inside your code your are referencing the static properties "Lists" instead of "TimeAgo". Lists.times.get(i) should be TimeAgo.get(i)... and so on
  • Riccardo Casatta
    Riccardo Casatta about 8 years
    Static variable name typo fixed.
  • Codeversed
    Codeversed about 8 years
    getRelativeTimeSpanString is not ideal for all situations and that is why I created my own class based on lots of the examples here. See my solution below: stackoverflow.com/a/37042254/468360
  • Nativ
    Nativ almost 8 years
    I don't think that this is a complete answer since the time units are independent. E.g - the milliseconds time is just minutes time * 60 * 1000. You need to decrease from each time unit that next bigger time unit(after converting it to the lower time unit) in order to be able to use it in a "time ago" string.
  • BlackPearl
    BlackPearl almost 8 years
    @RiccardoCasatta I entered DateTime in this format 2016-06-07 07:20:33, it always return 16days ago, why?
  • Riccardo Casatta
    Riccardo Casatta almost 8 years
    The function take a parameter which is a time delta expressed in millisecond. You should pass differences between dates not a date alone. You are probably passing a timestamp (which is second from 1970) to the function.
  • Disapamok
    Disapamok over 7 years
    I'm using this library to get ago time in this timestamp : "2016-09-24 09:57:12 AM", But this returns "9 Months Ago". (Current time is : 2016-09-24 10:53 AM). PrettyTime pt = new PrettyTime(new Locale("en")); System.out.print(pt.format(date_format.parse("2016-09-24 09:57:12 AM"))); Working on simple EE project with glassfish server. @ataylor
  • berkus
    berkus over 7 years
    Little suggestion: use .append(temp != 1 ? "s" : "") instead of .append(temp > 1 ? "s" : "") because 0 should also have s suffix
  • Swift
    Swift over 7 years
    @Benj - Does it right ? above solution ? because one time is in 12 hour format and another time is in 24 hour format. Let me know your feedback for my query. Thank you in advanced.
  • user2590928
    user2590928 about 7 years
    Nice solution, very easy to modify if you'd like other text like "Yesterday" or adapt so it can give periods relative to a future date ie "In 3 weeks"
  • Basil Bourque
    Basil Bourque about 7 years
    Those Duration methods report the entire duration as a total number of hours and as a total number of minutes. In Java 8 the class oddly lacked any methods to get each part of hour and minutes and seconds. Java 9 brings those methods, to…Part.
  • Shajeel Afzal
    Shajeel Afzal almost 7 years
    Is toDuration method taking the duration parameter as milliseconds? If Yes, then passing 1502531374441 is returning 47 years ago to me but it is my current time.
  • Riccardo Casatta
    Riccardo Casatta almost 7 years
    @ShajeelAfzal yes, the duration parameter is in millisecond but it's a difference between times not an absolute value. What you are getting is the time which has passed from the 1st of January 1970 the date when the unix timestamp started
  • Jonathan Laliberte
    Jonathan Laliberte over 6 years
    this is incorrect though... each unit is independent of eachother like already mentioned.
  • silentsudo
    silentsudo over 5 years
    Thanks A TON for this.
  • Wajid
    Wajid over 5 years
    In case you are working on android you can use this: android.text.format.DateUtils.getRelativeTimeSpanString(mill‌​iseconds)
  • Wajid
    Wajid over 5 years
    In case you are working on android you can use this: android.text.format.DateUtils.getRelativeTimeSpanString(mill‌​iseconds)
  • Wajid
    Wajid over 5 years
    In case you are working on android you can use this: android.text.format.DateUtils.getRelativeTimeSpanString(mill‌​iseconds)
  • Wajid
    Wajid over 5 years
    android does it for you In case you are working on android you can use this: android.text.format.DateUtils.getRelativeTimeSpanString(mill‌​iseconds)
  • Madbreaks
    Madbreaks over 5 years
    That's an Android library, not a Java library.
  • TheRealChx101
    TheRealChx101 about 5 years
    I always get times in the future, like "1 hour from now" when the time passed is actually before.
  • Basil Bourque
    Basil Bourque almost 5 years
    You are using terrible old date-time classes that were supplanted years ago by the java.time classes.
  • Ticherhaz FreePalestine
    Ticherhaz FreePalestine almost 5 years
    @BasilBourque I still cannot find the latest way to do this.
  • Ticherhaz FreePalestine
    Ticherhaz FreePalestine almost 5 years
    Thank you so much!
  • Ticherhaz FreePalestine
    Ticherhaz FreePalestine almost 5 years
    @BasilBourque I've added some code for me to understand it. github.com/ticherhaz/tarikhmasa
  • Haya Akkad
    Haya Akkad almost 5 years
    It's very useful, Thank you very much.
  • user1735921
    user1735921 over 4 years
    for something so simple there is no point of using a library, can just make one util class inside
  • Basil Bourque
    Basil Bourque over 4 years
    Using Duration with its to…Part methods would be much easier, as shown in my Answer.
  • Basil Bourque
    Basil Bourque over 4 years
    The terrible Calendar class was supplanted years ago by the modern java.time classes with the adoption of JSR 310. Poor advice in 2019.
  • Basil Bourque
    Basil Bourque over 4 years
    You must have meant var, not val.
  • Ole V.V.
    Ole V.V. almost 4 years
    Thanks for wanting to contribute. It's not exactly what was asked, but maybe someone can use it. Neither that someone nor anyone else should want to use SimpleDateFormat and Calendar, though. Those classes are poorly designed and long outdated. Instead read the answers that are employing java.time, the modern Java date and time API.
  • MDT
    MDT over 2 years
    accuracy for yesterday should be based on day factor, since 48 hours can be between 3 days and things done on day 1 will be labeled as yesterday which in reality is day 3 or (3 days ago).
  • Bill K
    Bill K over 2 years
    This is a good answer--rather than adding my own I'll just +1 and mention here that Duration has a "Parse" method that lets you specify values more easily, like "P2D" is 2 days, "P2D2M" is 2 days and 2 minutes. This gives a straight-forward Instance.now().minus(Duraiton.parse("P2H")) for "2 Hours Ago"