3

Momentjs has the calendar() function to pretty print timespans like below.

For example:

"Last Monday at 1:14 PM"

"09/21/2017" (if the date is a while ago)

Is there an equivalent function in Java (Joda-Time if possible)?

moment().subtract(10, 'days').calendar(); // 09/21/2017
moment().subtract(6, 'days').calendar();  // Last Monday at 1:14 PM
moment().subtract(3, 'days').calendar();  // Last Thursday at 1:14 PM
moment().subtract(1, 'days').calendar();  // Yesterday at 1:14 PM
moment().calendar();                      // Today at 1:14 PM
moment().add(1, 'days').calendar();       // Tomorrow at 1:14 PM
moment().add(3, 'days').calendar();       // Wednesday at 1:14 PM
moment().add(10, 'days').calendar();      // 10/11/2017
Community
  • 1
  • 1

2 Answers2

5

java.time

Modern approach uses the industry-leading java.time classes.

ZoneId z = ZoneId.now( “America/Montreal”  ) ;
LocalDate today = LocalDate.now( z ) ;

Use the plus and minus methods to do math.

LocalDate tomorrow = today.plusDays( 1 ) ;

Search Stack Overflow. Nearly every basic date-time Question has already been asked an answered.

Strings

The java.time classes do not generate strings such as “tomorrow” and “Last Monday”. So no direct equivalent of your referenced library. You will have to do the peasant work yourself.

Search for the DateTimeFormatter class, and the DateTimeFormatterBuilder class.

Also, the DayOfWeek enum and its auto-localizing getDisplayName method may be useful.

prettytime

The prettytime library may help you, though I’ve not used it.

Joda-Time

The Joda-Time project is in maintenance mode. The team advises migration to the java.time classes.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • 1
    That's not what I asked. My question was : I want to pretty print dates like "Last Monday at 1:14 PM", "Tomorrow at 1:14 PM", "10/11/2017" (if a long time ago) automatically –  Oct 01 '17 at 06:30
  • @PerrierCitror Ahh, I did not realize those commented strings was the goal. See my edits. – Basil Bourque Oct 01 '17 at 06:32
  • 2
    Hmm looks like there is no direct equivalent... I hate the time formatting in Java... Such a pain man –  Oct 01 '17 at 06:55
0

There's no built-in function for that in Java, but it's not so hard to do it with the existing API's. First you need to get the difference from today (in days), and choose the proper string based on moment.js rules:

Last week         Last Monday at 2:30 AM
The day before    Yesterday at 2:30 AM
The same day      Today at 2:30 AM
The next day      Tomorrow at 2:30 AM
The next week     Sunday at 2:30 AM
Everything else   7/10/2011

In Joda-Time, you can use the org.joda.time.Days class to get the difference in days. One detail is that I'm considering just the date (day/month/year) and ignoring the time (hour/minute/second) to get the difference (but you can adjust it to your needs). The method will be like this:

public String calendar(DateTime dt) {
    StringBuilder sb = new StringBuilder();

    // check difference in days from today, considering just the date (ignoring the hours)
    int days = Days.daysBetween(new LocalDate(), dt.toLocalDate()).getDays();
    if (days == 0) { // today
        sb.append("Today ");
    } else if (days == 1) { // tomorrow
        sb.append("Tomorrow ");
    } else if (days == -1) { // yesterday
        sb.append("Yesterday ");
    } else if (days > 0 && days < 7) { // next week
        sb.append(dt.dayOfWeek().getAsText(Locale.ENGLISH)).append(" ");
    } else if (days < 0 && days > -7) { // last week
        sb.append("Last ").append(dt.dayOfWeek().getAsText(Locale.ENGLISH)).append(" ");
    }

    if (Math.abs(days) < 7) { // difference is less than a week, append current time
        sb.append("at ").append(dt.toString("h:mm a", Locale.ENGLISH));
    } else { // more than a week of difference
        sb.append(dt.toString("M/d/yyyy"));
    }

    return sb.toString();
}

I'm using a org.joda.time.LocalDate to get the difference, so the time will be ignored (if you use a DateTime instead, the difference is only 1 day if it has passed more than 24 hours, so change the code according to what you need).

I also used Math.abs(days) < 7 to consider if the difference is more than a week, but I'm not sure if moment.js considers <= 7.

Anyway, some examples of usage:

DateTime now = new DateTime();
System.out.println(calendar(now.minusDays(10))); // 9/22/2017
System.out.println(calendar(now.minusDays(6))); // Last Tuesday at 9:34 AM
System.out.println(calendar(now.minusDays(3))); // Last Friday at 9:34 AM
System.out.println(calendar(now.minusDays(1))); // Yesterday at 9:34 AM
System.out.println(calendar(now)); // Today at 9:34 AM
System.out.println(calendar(now.plusDays(1))); // Tomorrow at 9:34 AM
System.out.println(calendar(now.plusDays(3))); // Thursday at 9:34 AM
System.out.println(calendar(now.plusDays(10))); // 10/12/2017

The output is (considering that today is October 2nd 2017, and I ran the code at 9:34 AM in my local time):

9/22/2017
Last Tuesday at 9:34 AM
Last Friday at 9:34 AM
Yesterday at 9:34 AM
Today at 9:34 AM
Tomorrow at 9:34 AM
Thursday at 9:34 AM
10/12/2017

You can also modify the method to take a reference date to compare with (instead of using a hardcoded new LocalDate() inside the method).


Java new Date/Time API

Joda-Time is in maintainance mode and is being replaced by the new APIs, so I don't recommend start a new project with it. Even in joda's website it says: "Note that Joda-Time is considered to be a largely “finished” project. No major enhancements are planned. If using Java SE 8, please migrate to java.time (JSR-310).".

If you're using Java 8, consider using the new java.time API. It's easier, less bugged and less error-prone than the old APIs.

If you're using Java 6 or 7, you can use the ThreeTen Backport, a great backport for Java 8's new date/time classes. And for Android, you'll also need the ThreeTenABP (more on how to use it here).

The code below works for both. The only difference is the package names (in Java 8 is java.time and in ThreeTen Backport (or Android's ThreeTenABP) is org.threeten.bp), but the classes and methods names are the same.

This API is very similar to Joda-Time, so the code has minor differences:

static DateTimeFormatter HOUR_FORMAT = DateTimeFormatter.ofPattern("h:mm a", Locale.ENGLISH);

static DateTimeFormatter MDY_FORMAT = DateTimeFormatter.ofPattern("M/d/yyyy");

public String calendar(ZonedDateTime dt) {
    StringBuilder sb = new StringBuilder();

    // check difference in days from today, considering just the date (ignoring the hours) 
    long days = ChronoUnit.DAYS.between(LocalDate.now(), dt.toLocalDate());
    if (days == 0) { // today
        sb.append("Today ");
    } else if (days == 1) { // tomorrow
        sb.append("Tomorrow ");
    } else if (days == -1) { // yesterday
        sb.append("Yesterday ");
    } else if (days > 0 && days < 7) { // next week
        sb.append(dt.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.ENGLISH)).append(" ");
    } else if (days < 0 && days > -7) { // last week
        sb.append("Last ").append(dt.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.ENGLISH)).append(" ");
    }

    if (Math.abs(days) < 7) {  // difference is less than a week, append current time
        sb.append("at ").append(dt.format(HOUR_FORMAT));
    } else { // more than a week of difference
        sb.append(dt.format(MDY_FORMAT));
    }

    return sb.toString();
}

I used a ZonedDateTime, which is an equivalent of Joda's DateTime (it represents a date and time in a timezone).

When calling DAYS.between, I converted it to a LocalDate, so the comparison considers only the date (day/month/year). If I used a ZonedDateTime instead, the time would also be considered, so the result would be 1 day only if it has passed more than 24 hours (so you can change it according to your needs).

Note that I also had to create 2 instances of DateTimeFormatter. I created them outside of the method, so they can be reused (no need to create them all the time inside the method). Joda-Time didn't need it because the toString method in Joda's objects can take a pattern and internally creates a formatter, while in java.time API you must create the formatter explicity.

Using it is also similar to Joda-Time:

ZonedDateTime now = ZonedDateTime.now();
System.out.println(calendar(now.minusDays(10))); // 9/22/2017
System.out.println(calendar(now.minusDays(6))); // Last Tuesday at 9:34 AM
System.out.println(calendar(now.minusDays(3))); // Last Friday at 9:34 AM
System.out.println(calendar(now.minusDays(1))); // Yesterday at 9:34 AM
System.out.println(calendar(now)); // Today at 9:34 AM
System.out.println(calendar(now.plusDays(1))); // Tomorrow at 9:34 AM
System.out.println(calendar(now.plusDays(3))); // Thursday at 9:34 AM
System.out.println(calendar(now.plusDays(10))); // 10/12/2017

Just a note about timezones. In java.time API, all the date classes has a no-arg now() method that gets the current date/time in the JVM's default timezone.

Although it's very conveninent, it also has some drawbacks, because the default timezone can be changed without notice, even at runtime. It's better to specify which timezone you want, if possible.

The API uses IANA timezones names (always in the format Region/City, like America/New_York or Europe/Paris). Avoid using the 3-letter abbreviations (like CET or EST) because they are ambiguous and not standard.

You can get a list of available timezones (and choose the one that fits best your system) by calling ZoneId.getAvailableZoneIds().

To get the current date/time in a specific timezone, use the ZoneId class:

// get the current date/time in a specific timezone
ZonedDateTime.now(ZoneId.of("Europe/Paris"));