[TUT] Creating comments with timestamps like YouTube

Creating comments with timestamps like YouTube (java.util.Date or JodaTime)

This tutorial shows how you can create timestamped messages like the YouTube comments section. What this means is we will convert the timestamp (date) that each message has been saved at into a human readable and rounded format. For instance if today is 2016/02/14 17:00 and someone had commented at 2016/02/14:12:55 the message would be timestamped with “2 hours ago”.

What we are going to do:

– Create an example app showing a list of items
– Understand the different time possibilities
– Test Drive the creation of our formatted timestamp
– Show an alternative in JodaTime

Ok here .. we .. go

First up is just the setup of the example application. It has a ListView (sure use RecyclerView I don’t care) that will hold the comments and timestamps. Each of our comments in this example has a message and a timestamp.

Comment.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.Date;
 
class Comment {
 
    private final Date timestamp;
    private final String message;
 
    public Comment(Date timestamp, String message) {
        this.timestamp = timestamp;
        this.message = message;
    }
 
    public Date getTimestamp() {
        return timestamp;
    }
 
    public String getMessage() {
        return message;
    }
}

I think you all know the ViewHolder pattern and how ListView’s work, so here’s the code with not much explanation:

list_item_comment.xml

CommentAdapter.java

Tieing it all together is our MainActivity and the data. The data is just a static list of 10 items with lorem ipsum as text. So we create our activity, find our listview, add our adapter and load it with our data. Simple!

MainActivity.java

When this is all coded and running you should get a demo application that looks like this:
timestamps-before

Now lets get down to business, YouTube shows all it’s comments with a timestamp, however this is not the actual specific time that the comment was sent. For example

Screen Shot 2016-02-19 at 3.03.59 pm

It appears as though YouTube will stamp the comments with “just now”, “X minutes ago”, “X hours ago”, “X days ago”, “X months ago” and “X years ago”. This means we need to work out the difference between the current time and the commented time to calculate this string.
Making this calculation sounds straightforward but actually quite tricky to get right. It sounds like a perfect place to write some tests first and make sure what we are doing is right as we are doing it.
If you’ve never done TDD before there is no need to worry, you won’t even realise you are doing it and it’ll make so much sense afterwards!

The first scenario for our YouTube comments is “just now” this is when someone has commented under a minute ago.
A test for this could look like:

1
2
3
4
5
6
7
8
9
@Test
public void givenCommentedUnder60SecondsAgo_thenFormatSaysJustNow() throws Exception {
    TimeStampFormatter formatter = new TimeStampFormatter();
    Date commentedAt = minusSeconds(59, now());
 
    String commentedAtFormatted = formatter.format(commentedAt);
 
    assertEquals("just now", commentedAtFormatted);
}

The only complicated thing here is the creation of the commentedAt variable. It helps if you think of it backwards – we are creating a java.util.Date in the now() method and then we are taking away 59 seconds.
Check it out

1
2
3
4
private Date minusSeconds(int seconds, Date date) {
    date.setTime(date.getTime() - TimeUnit.SECONDS.toMillis(seconds));
    return date;
}

To get this test passing we need to get the current time and get the commented time, if we then take them away from each other and convert this to minutes, if the minutes is under 1 then we return “just now”!

start of TimeStampFormatter.java

1
2
3
4
5
6
7
8
9
10
11
public String format(Date timestamp) {
        long commentedAtMillis = timestamp.getTime();
        long nowMillis = System.currentTimeMillis();
        long millisFromNow = nowMillis - commentedAtMillis;
 
        long minutesFromNow = TimeUnit.MILLISECONDS.toMinutes(millisFromNow);
        if (minutesFromNow < 1) {
            return "just now";
        }
        return "TODO";
}

Great our test passes! This was finding the difference in minutes and checking if it was under one, if we go on to find the difference in hours, days, weeks, years then we can complete our task. Don’t worry I’ve done it for you.

Test it one step of a time of course:

JavaUtilDate_TimeStampFormatter.java

Then the implementation that makes the tests pass. (I wrote this one section at a time after I wrote each test, but I am showing you the whole thing).

TimeStampFormatter.java

This gives us our formatting just like YouTube!

timestamps

There is a little thing to talk about here. We have been making use of TimeUnit to do our conversion from minutes, hours etc to milliseconds. However because Weeks, Months & Years are not units of time, we had to convert them to Days and then multiply this by how many days we think are in these calendar units. i.e. If we want to find the number of Months, we convert our difference in milliseconds to Days and then divide that by what we think is the average days in the month (30). Therefore this is not 100% accurate but accurate enough for our purposes.

When you start getting serious with dates you realise java.util.Date is not fit for purpose and you may move to JodaTime. Wise move! JodaTime has inherent handling of weeks, months years so it is more accurate than our above method, but if you take a close look at the code repository you can see there isn’t much difference to the rest of our code. (But our tests are much simpler!)

Just because you are using JodaTime doesn’t mean you can skip testing that your code works. Lets do the same again with JodaTime.

JodaTime_TimeStampFormatterTest.java

TimeStampFormatter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public String format(DateTime commentedAt) {
    DateTime now = DateTime.now();
    Minutes minutesBetween = Minutes.minutesBetween(commentedAt, now);
    if (minutesBetween.isLessThan(Minutes.ONE)) {
        return "just now";
    }
    Hours hoursBetween = Hours.hoursBetween(commentedAt, now);
    if (hoursBetween.isLessThan(Hours.ONE)) {
        return formatMinutes(minutesBetween.getMinutes());
    }
    Days daysBetween = Days.daysBetween(commentedAt, now);
    if (daysBetween.isLessThan(Days.ONE)) {
        return formatHours(hoursBetween.getHours());
    }
    Weeks weeksBetween = Weeks.weeksBetween(commentedAt, now);
    if (weeksBetween.isLessThan(Weeks.ONE)) {
        return formatDays(daysBetween.getDays());
    }
    Months monthsBetween = Months.monthsBetween(commentedAt, now);
    if (monthsBetween.isLessThan(Months.ONE)) {
        return formatWeeks(weeksBetween.getWeeks());
    }
    Years yearsBetween = Years.yearsBetween(commentedAt, now);
    if (yearsBetween.isLessThan(Years.ONE)) {
        return formatMonths(monthsBetween.getMonths());
    }
    return formatYears(yearsBetween.getYears());
}
 
private String formatMinutes(long minutes) {
    return format(minutes, " minute ago", " minutes ago");
}
 
private String formatHours(long hours) {
    return format(hours, " hour ago", " hours ago");
}
 
private String formatDays(long days) {
    return format(days, " day ago", " days ago");
}
 
private String formatWeeks(long weeks) {
    return format(weeks, " week ago", " weeks ago");
}
 
private String formatMonths(long months) {
    return format(months, " month ago", " months ago");
}
 
private String formatYears(long years) {
    return format(years, " year ago", " years ago");
}
 
private String format(long hand, String singular, String plural) {
    if (hand == 1) {
        return hand + singular;
    } else {
        return hand + plural;
    }
}

I haven’t done anything to do with localisation here, as this is not the example, but if you would like to see how this would be converted just leave a comment.

That’s it, two alternatives using java.util.Date and JodaTime to be able to format a timestamp like the YouTube comments stream.

Code is available on GitHub here.

Any questions just ask!

2 thoughts on “[TUT] Creating comments with timestamps like YouTube

  1. Interesting read! I will definitely use this in an app.
    Only thing missing is an auto-update, so “just now” would automatically become “1 minute ago” as soon as the comment is one minute old.

Comments are closed.