How to Calculate “Time Ago” in Java – 如何在Java中计算“以前的时间&8221;?

最后修改: 2022年 6月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Calculating relative time and the duration between two points in time is a common use case in software systems. For instance, we might want to show the user how much time has passed since an event like posting a new picture on a social media platform. Examples of such “time ago” text are ” “5 minutes ago”, “1 year ago” etc.

计算相对时间和两个时间点之间的持续时间是软件系统中的一个常见用例。例如,我们可能想向用户显示自某一事件(如在社交媒体平台上发布一张新照片)以来已经过去了多少时间。这样的 “时间前 “文本的例子有” “5分钟前”、”1年前 “等。

While the semantics and choosing of the words are completely context-dependent, the overall idea is the same.

虽然语义和选词完全取决于上下文,但总体思路是一样的。

In this tutorial, we’ll explore several solutions for calculating time ago in Java. Due to the introduction of new Date and Time API in Java 8, we’ll discuss the solutions for version 7 and version 8 separately.

在本教程中,我们将探讨在Java中计算时间前的几种解决方案。由于在Java 8中引入了新的日期和时间API,我们将分别讨论版本7和版本8的解决方案。

2. Java Version 7

2.java版本7

There are several classes related to time in Java 7. Moreover, due to the shortcomings of Java 7 Date API, several third-party time and date libraries are also available.

在Java 7中,有几个与时间有关的类。此外,由于Java 7 Date API的缺陷,也有几个第三方的时间和日期库可用。

First, let’s use pure Java 7 to calculate “time ago”.

首先,让我们用纯Java 7来计算 “以前的时间”。

2.1. Pure Java 7

2.1.纯粹的Java 7

We define an enum that holds different time granularities and converts them to milliseconds:

我们定义了一个enum,用于保存不同的时间粒度,并将其转换为毫秒。

public enum TimeGranularity {
    SECONDS {
        public long toMillis() {
            return TimeUnit.SECONDS.toMillis(1);
        }
    }, MINUTES {
        public long toMillis() {
            return TimeUnit.MINUTES.toMillis(1);
        }
    }, HOURS {
        public long toMillis() {
            return TimeUnit.HOURS.toMillis(1);
        }
    }, DAYS {
        public long toMillis() {
            return TimeUnit.DAYS.toMillis(1);
        }
    }, WEEKS {
        public long toMillis() {
            return TimeUnit.DAYS.toMillis(7);
        }
    }, MONTHS {
        public long toMillis() {
            return TimeUnit.DAYS.toMillis(30);
        }
    }, YEARS {
        public long toMillis() {
            return TimeUnit.DAYS.toMillis(365);
        }
    }, DECADES {
        public long toMillis() {
            return TimeUnit.DAYS.toMillis(365 * 10);
        }
    };

    public abstract long toMillis();
}

We’ve used java.util.concurrent.TimeUnit enum which is a powerful tool for time conversion. Using TimeUnit enum, we override the toMillis() abstract method for each value of the TimeGranularity enum so that it returns the number of milliseconds equivalent to each value. For example, for “decade”, it returns the number of milliseconds for 3650 days.

我们使用了java.util.concurrent.TimeUnit enum,这是一个强大的时间转换工具。使用TimeUnit枚举,我们为TimeGranularityenum的每个值覆盖toMillis()抽象方法,以便它返回相当于每个值的毫秒数目。例如,对于 “十年”,它返回相当于3650天的毫秒数。

As a result of defining TimeGranularity enum, we can define two methods. The first one takes a java.util.Date object and an instance of TimeGranularity and returns a “time ago” string:

由于定义了TimeGranularity枚举,我们可以定义两个方法。第一个方法接收一个java.util.Date对象和一个TimeGranularity的实例,并返回一个 “time ago “字符串。

static String calculateTimeAgoByTimeGranularity(Date pastTime, TimeGranularity granularity) {
    long timeDifferenceInMillis = getCurrentTime() - pastTime.getTime();
    return timeDifferenceInMillis / granularity.toMillis() + " " + 
      granularity.name().toLowerCase() + " ago";
}

This method divides the difference of current time and the given time by the TimeGranularity value in milliseconds. Consequently, we can roughly calculate the amount of time that has passed since the given time in the specified time granularity.

该方法将当前时间与给定时间的差值除以TimeGranularity值,单位为毫秒。因此,我们可以在指定的时间粒度中大致计算出自给定时间以来所经过的时间量。

We used the getCurrentTime() method in order to get the current time. For testing, we return a fixed point of time and avoid reading time from the local machine. In practice, this method would return the real value of current time using System.currentTimeMillis() or LocalDateTime.now().

我们使用getCurrentTime()方法,以便获得当前时间。为了测试,我们返回一个固定的时间点,避免从本地机器读取时间。在实践中,这个方法将使用System.currentTimeMillis()LocalDateTime.now().返回当前时间的真实数值。

Let’s test the method:

让我们测试一下这个方法。

Assert.assertEquals("5 hours ago", 
  TimeAgoCalculator.calculateTimeAgoByTimeGranularity(
    new Date(getCurrentTime() - (5 * 60 * 60 * 1000)), TimeGranularity.HOURS));

Furthermore, we can also write a method that automatically detects the largest suitable time granularity and returns a more human-friendly output:

此外,我们还可以写一个方法,自动检测最大的合适的时间颗粒度,并返回一个更适合人类的输出。

static String calculateHumanFriendlyTimeAgo(Date pastTime) {
    long timeDifferenceInMillis = getCurrentTime() - pastTime.getTime();
    if (timeDifferenceInMillis / TimeGranularity.DECADES.toMillis() > 0) {
        return "several decades ago";
    } else if (timeDifferenceInMillis / TimeGranularity.YEARS.toMillis() > 0) {
        return "several years ago";
    } else if (timeDifferenceInMillis / TimeGranularity.MONTHS.toMillis() > 0) {
        return "several months ago";
    } else if (timeDifferenceInMillis / TimeGranularity.WEEKS.toMillis() > 0) {
        return "several weeks ago";
    } else if (timeDifferenceInMillis / TimeGranularity.DAYS.toMillis() > 0) {
        return "several days ago";
    } else if (timeDifferenceInMillis / TimeGranularity.HOURS.toMillis() > 0) {
        return "several hours ago";
    } else if (timeDifferenceInMillis / TimeGranularity.MINUTES.toMillis() > 0) {
        return "several minutes ago";
    } else {
        return "moments ago";
    }
}

Now, let’s look at a test to see an example usage:

现在,让我们看一个测试,看一个使用的例子。

Assert.assertEquals("several hours ago", 
  TimeAgoCalculator.calculateHumanFriendlyTimeAgo(new Date(getCurrentTime() - (5 * 60 * 60 * 1000))));

According to the context, we can use different words such as “few”, “several”, “many”, or even the exact value.

根据上下文,我们可以使用不同的词语,如 “少数”、”几个”、”许多”,甚至是确切的数值。

2.2. Joda-Time Library

2.2.Joda-时间库

Before the release of Java 8, Joda-Time was the de facto standard for various time and date-related operations in Java. We can use three classes of the Joda-Time library to calculate “time ago”:

在Java 8发布之前,Joda-Time是Java中各种时间和日期相关操作的事实上的标准。我们可以使用Joda-Time库的三个类来计算 “以前的时间”。

  • org.joda.time.Period which takes two objects of org.joda.time.DateTime and calculates the difference between these two points of time
  • org.joda.time.format.PeriodFormatter which defines the format for printing the Period object
  • org.joda.time.format.PeriodFormatuilder which is a builder class to create a custom PeriodFormatter

We can use these three classes to easily get the exact time between now and a time in the past:

我们可以使用这三个类来轻松地获得现在和过去某个时间之间的确切时间。

static String calculateExactTimeAgoWithJodaTime(Date pastTime) {
    Period period = new Period(new DateTime(pastTime.getTime()), new DateTime(getCurrentTime()));
    PeriodFormatter formatter = new PeriodFormatterBuilder().appendYears()
      .appendSuffix(" year ", " years ")
      .appendSeparator("and ")
      .appendMonths()
      .appendSuffix(" month ", " months ")
      .appendSeparator("and ")
      .appendWeeks()
      .appendSuffix(" week ", " weeks ")
      .appendSeparator("and ")
      .appendDays()
      .appendSuffix(" day ", " days ")
      .appendSeparator("and ")
      .appendHours()
      .appendSuffix(" hour ", " hours ")
      .appendSeparator("and ")
      .appendMinutes()
      .appendSuffix(" minute ", " minutes ")
      .appendSeparator("and ")
      .appendSeconds()
      .appendSuffix(" second", " seconds")
      .toFormatter();
    return formatter.print(period);
}

Let’s see a sample usage:

让我们看一个使用样本。

Assert.assertEquals("5 hours and 1 minute and 1 second", 
  TimeAgoCalculator.calculateExactTimeAgoWithJodaTime(new Date(getCurrentTime() - (5 * 60 * 60 * 1000 + 1 * 60 * 1000 + 1 * 1000))));

It’s also possible to generate a more human-friendly output:

也有可能产生一个更适合人类的输出。

static String calculateHumanFriendlyTimeAgoWithJodaTime(Date pastTime) {
    Period period = new Period(new DateTime(pastTime.getTime()), new DateTime(getCurrentTime()));
    if (period.getYears() != 0) {
        return "several years ago";
    } else if (period.getMonths() != 0) {
        return "several months ago";
    } else if (period.getWeeks() != 0) {
        return "several weeks ago";
    } else if (period.getDays() != 0) {
        return "several days ago";
    } else if (period.getHours() != 0) {
        return "several hours ago";
    } else if (period.getMinutes() != 0) {
        return "several minutes ago";
    } else {
        return "moments ago";
    }
}

We can run a test to see that this method returns a more human-friendly “time ago” string:

我们可以运行一个测试,看看这个方法是否会返回一个更加人性化的 “时间之前 “字符串。

Assert.assertEquals("several hours ago", 
  TimeAgoCalculator.calculateHumanFriendlyTimeAgoWithJodaTime(new Date(getCurrentTime() - (5 * 60 * 60 * 1000))));

Again, we can use different terms such as “one”, “few”, or “several”, depending on the use case.

同样,我们可以使用不同的术语,如 “一个”、”几个 “或 “几个”,这取决于用例。

2.3. Joda-Time TimeZone

2.3.Joda-TimeTimeZone

It’s pretty straightforward to add a time zone in the calculation of “time ago” using the Joda-Time library:

使用Joda-Time库在计算 “以前的时间 “时增加一个时区是非常直接的。

String calculateZonedTimeAgoWithJodaTime(Date pastTime, TimeZone zone) {
    DateTimeZone dateTimeZone = DateTimeZone.forID(zone.getID());
    Period period = new Period(new DateTime(pastTime.getTime(), dateTimeZone), new DateTime(getCurrentTimeByTimeZone(zone)));
    return PeriodFormat.getDefault().print(period);
}

The getCurrentTimeByTimeZone() method returns the value of the current time in the specified timezone. For testing, this method returns a fixed point of time, but in practice, this should return the real value of the current time using Calendar.getInstance(zone).getTimeInMillis() or LocalDateTime.now(zone).

getCurrentTimeByTimeZone()方法返回指定时区的当前时间值。为了测试,该方法返回一个固定的时间点,但在实践中,应该使用Calendar.getInstance(zone).getTimeInMillis() LocalDateTime.now(zone).返回当前时间的真实值。

3. Java 8

3.JAVA 8

Java 8 introduced a new improved Date and Time API, which adopted many ideas from the Joda-Time library. We can use native java.time.Duration and java.time.Period classes to calculate “time ago”:

Java 8引入了一个新的改进的日期和时间API,它采用了Joda-Time库的许多想法。我们可以使用本地的java.time.Durationjava.time.Period类来计算 “以前的时间”:

static String calculateTimeAgoWithPeriodAndDuration(LocalDateTime pastTime, ZoneId zone) {
    Period period = Period.between(pastTime.toLocalDate(), getCurrentTimeByTimeZone(zone).toLocalDate());
    Duration duration = Duration.between(pastTime, getCurrentTimeByTimeZone(zone));
    if (period.getYears() != 0) {
        return "several years ago";
    } else if (period.getMonths() != 0) {
        return "several months ago";
    } else if (period.getDays() != 0) {
        return "several days ago";
    } else if (duration.toHours() != 0) {
        return "several hours ago";
    } else if (duration.toMinutes() != 0) {
        return "several minutes ago";
    } else if (duration.getSeconds() != 0) {
        return "several seconds ago";
    } else {
        return "moments ago";
    }
}

The above code snippet supports time zone and uses native Java 8 API only.

上面的代码片段支持时区,并且只使用本地的Java 8 API。

4. PrettyTime Library

4.漂亮时光库

PrettyTime is a powerful library that specifically offers “time ago” functionality with i18n support. Also, it’s highly customizable, easy to use, and can be used with both Java versions 7 and 8.

PrettyTime是一个强大的库,专门提供了 “time ago “功能,并支持i18n。此外,它还具有高度的可定制性,易于使用,并且可以与Java 7和8版本一起使用。

First, let’s add its dependency to our pom.xml:

首先,让我们把它的依赖性添加到我们的pom.xml

<dependency>
    <groupId>org.ocpsoft.prettytime</groupId>
    <artifactId>prettytime</artifactId>
    <version>3.2.7.Final</version>
</dependency>

Now getting “time ago” in a human-friendly format is pretty easy:

现在,以人类友好的格式获得 “以前的时间 “是相当容易的。

String calculateTimeAgoWithPrettyTime(Date pastTime) {
    PrettyTime prettyTime = new PrettyTime();
    return prettyTime.format(pastTime);
}

5. Time4J Library

5.Time4J图书馆

Finally, Time4J is another great library for the manipulation of time and date data in Java. It has a PrettyTime class which can be used to calculate time ago.

最后,Time4J是另一个在Java中操作时间和日期数据的伟大库。它有一个PrettyTime类,可以用来计算时间前。

Let’s add its dependencies:

让我们添加其依赖性

<dependency>
    <groupId>net.time4j</groupId>
    <artifactId>time4j-base</artifactId>
    <version>5.9</version>
</dependency>
<dependency>
    <groupId>net.time4j</groupId>
    <artifactId>time4j-sqlxml</artifactId>
    <version>5.8</version>
</dependency>

After adding this dependency, calculating the time ago is pretty straightforward:

加入这个依赖关系后,计算前的时间是非常直接的。

String calculateTimeAgoWithTime4J(Date pastTime, ZoneId zone, Locale locale) {
    return PrettyTime.of(locale).printRelative(pastTime.toInstant(), zone);
}

Same as the PrettyTime library, Time4J also supports i18n out of the box.

和PrettyTime库一样,Time4J也支持i18n的开箱。

6. Conclusion

6.结语

In this article, we discussed different methods for calculating time ago in Java.

在这篇文章中,我们讨论了Java中计算时间前的不同方法。

There are solutions for both pure Java and third-party libraries. Since a new Date and Time API was introduced in Java 8, pure java solutions are different for versions before and after 8.

既有纯Java的解决方案,也有第三方库的解决方案。由于在Java 8中引入了新的日期和时间API,纯Java的解决方案在8之前和之后的版本中是不同的。

As always, the source code for the examples is available over on GitHub.

像往常一样,这些例子的源代码可以在GitHub上找到