如何在Java中计算"time ago"?

jts*_*jts 122 java time datediff date relative-time-span

在Ruby on Rails中,有一个功能允许你采取任何日期并打印出"很久以前"的样子.

例如:

8 minutes ago
8 hours ago
8 days ago
8 months ago
8 years ago
Run Code Online (Sandbox Code Playgroud)

在Java中有一种简单的方法吗?

ata*_*lor 168

看看PrettyTime库.

它使用起来非常简单:

import org.ocpsoft.prettytime.PrettyTime;

PrettyTime p = new PrettyTime();
System.out.println(p.format(new Date()));
// prints "moments ago"
Run Code Online (Sandbox Code Playgroud)

您还可以传入国际化消息的区域设置:

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

正如评论中所述,Android具有内置于android.text.format.DateUtils该类中的此功能.

  • 如果您正在使用android,您可以使用:android.text.format.DateUtils#getRelativeTimeSpanString() (220认同)

Ben*_*n J 66

你考虑过TimeUnit枚举吗?它对于这种事情非常有用

    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();
    }
Run Code Online (Sandbox Code Playgroud)

  • 我认为这不是一个完整的答案,因为时间单位是独立的。例如-毫秒时间仅是分钟时间* 60 *1000。您需要从每个时间单位中减去下一个较大的时间单位(将其转换为较低的时间单位后),以便能够在“时间之前”中使用”字符串。 (4认同)

Ric*_*tta 47

我采用RealHowTo和Ben J的答案并制作我自己的版本:

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));
}}
Run Code Online (Sandbox Code Playgroud)

这将打印以下内容

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
Run Code Online (Sandbox Code Playgroud)

  • 小建议:使用`.append(temp!= 1?“ s”:“”)`而不是`.append(temp&gt; 1?“ s”:“”)`,因为0也应带有`s`后缀 (2认同)

Rea*_*wTo 42

  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) {
        StringBuffer res = new StringBuffer();
        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
         */
    }
}
Run Code Online (Sandbox Code Playgroud)

更多@ 将以毫秒为单位的持续时间格式化为人类可读的格式

  • David Blevins,关于PrettyTime的更多例子:http://stackoverflow.com/questions/3859288/how-to-calculate-time-ago-in-java Big -1再次重新发明轮子而不推荐第三方库: -p (5认同)

Dav*_*ins 9

这是基于RealHowTo的答案,所以如果你喜欢它,也要给他/她一些爱.

此清理版本允许您指定您可能感兴趣的时间范围.

它还处理"和"部分有点不同.我经常发现在使用分隔符连接字符串时,更容易跳过复杂的逻辑,并在完成后删除最后一个分隔符.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

很少的代码让它旋转:

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));
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

这将输出以下内容:

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 
Run Code Online (Sandbox Code Playgroud)

如果有人需要它,这里有一个类将上面的任何字符串转换回毫秒.它对于允许人们在可读文本中指定各种事物的超时非常有用.


小智 9

这有一个简单的方法:

假设您想要20分钟前的时间:

Long minutesAgo = new Long(20);
Date date = new Date();
Date dateIn_X_MinAgo = new Date (date.getTime() - minutesAgo*60*1000);
Run Code Online (Sandbox Code Playgroud)

而已..

  • 在大多数情况下,您想要一个“智能”显示器,即。你告诉的是 x 天前,而不是 5125 分钟前。 (2认同)

Bas*_*que 8

java.time

\n

Habsq 的答案的想法是正确的,但方法是错误的。

\n

对于不依附于年-月-日时间线的时间跨度,请使用Period。对于表示与日历和小时-分钟-秒无关的 24 小时时间块的日期,请使用Duration。混合这两种规模几乎没有任何意义。

\n

Duration

\n

首先使用该类获取UTC中看到的当前时刻Instant

\n
Instant now = Instant.now();  // Capture the current moment as seen in UTC.\nInstant then = now.minus( 8L , ChronoUnit.HOURS ).minus( 8L , ChronoUnit.MINUTES ).minus( 8L , ChronoUnit.SECONDS );\nDuration d = Duration.between( then , now );\n
Run Code Online (Sandbox Code Playgroud)\n

生成小时、分钟和秒的文本。

\n
// Generate text by calling `to\xe2\x80\xa6Part` methods.\nString output = d.toHoursPart() + " hours ago\\n" + d.toMinutesPart() + " minutes ago\\n" + d.toSecondsPart() + " seconds ago";\n
Run Code Online (Sandbox Code Playgroud)\n

转储到控制台。

\n
System.out.println( "From: " + then + " to: " + now );\nSystem.out.println( output );\n
Run Code Online (Sandbox Code Playgroud)\n
\n

从:2019-06-04T11:53:55.714965Z 到:2019-06-04T20:02:03.714965Z

\n
\n
\n

8 小时前

\n
\n
\n

8 分钟前

\n
\n
\n

8 秒前

\n
\n

Period

\n

首先获取当前日期。

\n

时区对于确定日期至关重要。对于任何特定时刻,全球各地的日期都会因地区而异。例如,法国巴黎午夜过后几分钟是新的一天,而Montr\xc3\xa9al Qu\xc3\xa9bec中仍然是 \xe2\x80\x9cyesterday\xe2\x80\x9d 。

\n

如果未指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能在运行时随时更改(!),因此您的结果可能会有所不同。最好明确指定您想要/预期的时区作为参数。如果重要,请与您的用户确认该区域。

\n

以、、 或 等格式指定正确的时区名称。切勿使用 2-4 个字母的缩写,例如或,因为它们不是真实时区,不是标准化的,甚至不是唯一的(!)。Continent/RegionAmerica/MontrealAfrica/CasablancaPacific/AucklandESTIST

\n
ZoneId z = ZoneId.of( "America/Montreal" ) ;  \nLocalDate today = LocalDate.now( z ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

重新创建八天、八月和几年前的日期。

\n
LocalDate then = today.minusYears( 8 ).minusMonths( 8 ).minusDays( 7 ); // Notice the 7 days, not 8, because of granularity of months. \n
Run Code Online (Sandbox Code Playgroud)\n

计算经过的时间。

\n
Period p = Period.between( then , today );\n
Run Code Online (Sandbox Code Playgroud)\n

拼搭一串“很久以前”的作品。

\n
String output = p.getDays() + " days ago\\n" + p.getMonths() + " months ago\\n" + p.getYears() + " years ago";\n
Run Code Online (Sandbox Code Playgroud)\n

转储到控制台。

\n
System.out.println( "From: " + then + " to: " + today );\nSystem.out.println( output );\n
Run Code Online (Sandbox Code Playgroud)\n
\n

从: 2010-09-27 到: 2019-06-04

\n
\n
\n

8 天前

\n
\n
\n

8个月前

\n
\n
\n

8年前

\n
\n
\n

关于java.time

\n

java.time框架内置于 Java 8 及更高版本中这些类取代了麻烦的旧遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat

\n

要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

\n

Joda -Time项目现在处于维护模式,建议迁移到java.time类。

\n

您可以直接与数据库交换java.time对象。使用与JDBC 4.2或更高版本兼容的JDBC 驱动程序。不需要字符串,不需要类。java.sql.*

\n

从哪里获取 java.time 类?

\n\n

ThreeTen -Extra项目通过附加类扩展了 java.time。该项目是 java.time 未来可能添加的内容的试验场。您可能会在这里找到一些有用的类,例如Interval、、、等等。YearWeekYearQuarter

\n


Cod*_*sed 7

基于这里的一堆答案,我为我的用例创建了以下内容。

用法示例:

String relativeDate = String.valueOf(
                TimeUtils.getRelativeTime( 1000L * myTimeInMillis() ));
Run Code Online (Sandbox Code Playgroud)
import java.util.Arrays;
import java.util.List;

import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * Utilities for dealing with dates and times
 */
public class TimeUtils {

    public static final List<Long> times = Arrays.asList(
        DAYS.toMillis(365),
        DAYS.toMillis(30),
        DAYS.toMillis(7),
        DAYS.toMillis(1),
        HOURS.toMillis(1),
        MINUTES.toMillis(1),
        SECONDS.toMillis(1)
    );

    public static final List<String> timesString = Arrays.asList(
        "yr", "mo", "wk", "day", "hr", "min", "sec"
    );

    /**
     * Get relative time ago for date
     *
     * NOTE:
     *  if (duration > WEEK_IN_MILLIS) getRelativeTimeSpanString prints the date.
     *
     * ALT:
     *  return getRelativeTimeSpanString(date, now, SECOND_IN_MILLIS, FORMAT_ABBREV_RELATIVE);
     *
     * @param date String.valueOf(TimeUtils.getRelativeTime(1000L * Date/Time in Millis)
     * @return relative time
     */
    public static CharSequence getRelativeTime(final long date) {
        return toDuration( Math.abs(System.currentTimeMillis() - date) );
    }

    private static String toDuration(long duration) {
        StringBuilder sb = new StringBuilder();
        for(int i=0;i< times.size(); i++) {
            Long current = times.get(i);
            long temp = duration / current;
            if (temp > 0) {
                sb.append(temp)
                  .append(" ")
                  .append(timesString.get(i))
                  .append(temp > 1 ? "s" : "")
                  .append(" ago");
                break;
            }
        }
        return sb.toString().isEmpty() ? "now" : sb.toString();
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

如果你想找一个简单的"今天","昨天"或"x天前".

private String getDaysAgo(Date date){
    long days = (new Date().getTime() - date.getTime()) / 86400000;

    if(days == 0) return "Today";
    else if(days == 1) return "Yesterday";
    else return days + " days ago";
}
Run Code Online (Sandbox Code Playgroud)


hab*_*bsq 5

java.time

使用Java 8及更高版本中内置的java.time框架.

LocalDateTime t1 = LocalDateTime.of(2015, 1, 1, 0, 0, 0);
LocalDateTime t2 = LocalDateTime.now();
Period period = Period.between(t1.toLocalDate(), t2.toLocalDate());
Duration duration = Duration.between(t1, t2);

System.out.println("First January 2015 is " + period.getYears() + " years ago");
System.out.println("First January 2015 is " + period.getMonths() + " months ago");
System.out.println("First January 2015 is " + period.getDays() + " days ago");
System.out.println("First January 2015 is " + duration.toHours() + " hours ago");
System.out.println("First January 2015 is " + duration.toMinutes() + " minutes ago");
Run Code Online (Sandbox Code Playgroud)


Men*_*ild 5

关于内置解决方案:

Java没有任何内置支持格式化相对时间,也没有Java-8及其新包java.time.如果你只需要英语而不需要其他任何东西,那么只有手工制作的解决方案才可以接受 - 请参阅@RealHowTo的答案(尽管它有一个很大的缺点,就是不考虑将即时增量转换为当地时间的时区单位!).无论如何,如果你想避免本土复杂的解决方法,特别是对于其他语言环境,那么你需要一个外部库.

在后一种情况下,我建议使用我的库Time4J(或Android上的Time4A).它提供了最大的灵活性和大多数i18n功率.net.time4j.PrettyTime类有七种方法printRelativeTime...(...)用于此目的.使用测试时钟作为时间源的示例:

TimeSource<?> clock = () -> PlainTimestamp.of(2015, 8, 1, 10, 24, 5).atUTC();
Moment moment = PlainTimestamp.of(2015, 8, 1, 17, 0).atUTC(); // our input
String durationInDays =
  PrettyTime.of(Locale.GERMAN).withReferenceClock(clock).printRelative(
    moment,
    Timezone.of(EUROPE.BERLIN),
    TimeUnit.DAYS); // controlling the precision
System.out.println(durationInDays); // heute (german word for today)
Run Code Online (Sandbox Code Playgroud)

另一个使用java.time.Instant输入的例子:

String relativeTime = 
  PrettyTime.of(Locale.ENGLISH)
    .printRelativeInStdTimezone(Moment.from(Instant.EPOCH));
System.out.println(relativeTime); // 45 years ago
Run Code Online (Sandbox Code Playgroud)

该库通过其最新版本(v4.17)支持80种语言以及一些特定国家/地区的语言环境(特别是西班牙语,英语,阿拉伯语,法语).i18n数据主要基于最新的CLDR版本v29.使用此库的其他重要原因是对多个规则(通常与其他语言环境中的英语不同),缩写格式样式(例如:"1秒前")以及考虑时区的表达方式的良好支持.Time4J甚至意识到这些奇特的细节,比如计算相对时间的闰秒(不是很重要,但它形成了与期望视野相关的信息).与Java-8兼容性由于类似java.time.Instant或类型的容易获得的转换方法而存在java.time.Period.

有什么缺点吗?只有两个.

  • 该库不小(也因为它的i18n数据库很大).
  • API尚不为人所知,因此社区知识和支持尚不可用,否则提供的文档非常详细和全面.

(紧凑型)替代品:

如果您寻找更小的解决方案并且不需要这么多功能并且愿意容忍与i18n-data相关的可能的质量问题,那么:

  • 我会推荐ocpsoft/PrettyTime(实际支持32种语言(很快34?)只适合工作java.util.Date- 请参阅@ataylor的答案).具有巨大社区背景的行业标准CLDR(来自Unicode联盟)不幸地不是i18n数据的基础,因此数据的进一步增强或改进可能需要一段时间......

  • 如果你在Android上,那么助手类android.text.format.DateUtils是一个纤薄的内置替代品(请参阅此处的其他评论和答案,缺点是它不支持数年和数月.而且我确信只有很少有人喜欢这个助手类的API风格.

  • 如果您是Joda-Time的粉丝,那么您可以查看它的类PeriodFormat(在版本v2.9.4中支持14种语言,另一方面:Joda-Time肯定也不紧凑,所以我在这里提到它只是为了完整性).这个库不是真正的答案,因为根本不支持相对时间.您至少需要附加文字"之前"(并从生成的列表格式中手动剥离所有较低的单位 - 笨拙).与Time4J或Android-DateUtils不同,它没有特别支持缩写或从相对时间到绝对时间表示的自动切换.与PrettyTime一样,它完全依赖于Java社区的私有成员对其i18n数据的未经证实的贡献.


Luk*_*sen 5

对于安卓:

public static String getTimeAgoFormat(long timestamp) {
    return android.text.format.DateUtils.getRelativeTimeSpanString(timestamp).toString();
}
Run Code Online (Sandbox Code Playgroud)