patternjavaModerate
Conditionals for time specific data
Viewed 0 times
conditionalstimeforspecificdata
Problem
This is a fun piece. I have this block of code here:
I want to reduce the number of
I don't want to use enums since this is on a mobile device, and enums are heavier on memory. I was thinking that maybe ternary operators are best here. Thoughts?
The negative is intentional. It's with an API that I am working with and they use negatives to imply future events.
Input from API:
This is the following example input from the API:
It is passed to the following code block, and it is expected to return something like:
if (minutes >= 60 && hours = 24 && days -24) {
return -hours + " hours from now";
} else if (hours = -30) {
return -days + " days from now";
} else if (minutes >= -60 && minutes 30 && days -365) {
if (-days/30 == 1) {
return "Next month";
}
return -(days/30) + " months from now";
} else if (days >= 365) {
if (days/365 == 1) {
return "Last year";
}
return days/365 + " years ago";
} else if (days <= -365) {
if (-days/365 == 1) {
return "Next year";
}
return -(days/365) + " years from now";
} else {
return minutes + " minutes ago";
}I want to reduce the number of
if-else statements. The ways I was thinking of tackling this was:- Store these into a hashmap and then call it \$O(1)\$ time
- Use enums
- Ternary operators
- Switch statements
I don't want to use enums since this is on a mobile device, and enums are heavier on memory. I was thinking that maybe ternary operators are best here. Thoughts?
The negative is intentional. It's with an API that I am working with and they use negatives to imply future events.
Input from API:
This is the following example input from the API:
"time": "2015-03-06 20:00:00 EST"It is passed to the following code block, and it is expected to return something like:
"45 minutes ago"
"56 seconds ago"
"1 hour from now"
"2 days from now"
"2 months from now"
"Next year"
"Two years from now"
"A month ago"
"28 days ago"
"5 mins from now"
//etc.Solution
I'd just say, WTF is this doing?
You're testing
Maybe it's impossible as the variables all get computed from a single time interval. But you didn't show us.
I guess it's better for my sanity not to try too hard to figure out what it exactly does. So let me assume that all variable come from a single time interval. Then a condition like
can be expressed like
This doesn't buy us much as the order is still confusing:
Again, not trying to find a system there.
What about something like
I'm not claiming it's correct, but it's short and damn simple, so you find and fix all bugs in a few seconds.
So what I dislike is
I always prefer to keep things simple from the very beginning. Otherwise, it can easily happen that you get a cool idea, but can't apply it because of the need to preserve compatibility with some quirks in the original.
Another disadvantage of starting with complicated stuff is that you may miss some simplifications the way I did above. Concerning correctness, all the divisions and comparisons with 356 and 30 are slightly wrong.
Note also that the task is not exactly defined. Given the dates
And maybe also "next century" and "next millennium", but both are disputable (and irrelevant most of the time).
There's no clear solution for this and you should specify the behavior precisely and cover it by tests. The most straightforward solution would be to look at the calendar year first, which would give us the answer "next year". Pretty impractical, but every other solution needs some arbitrary choices.
I'd probably start with
so that no
Now you can write
This leads to a rather lengthy but trivial method. Now you may want to split it as suggested by h.j.k. This is not easy, as the parts above only conditionally return something. Using a guard condition, this can be solved like this
After extracting the method, you'll see that it's about the same for every time unit, so you may want to generalize it to
Note that thi
You're testing
minutes and hours first as if they were the most important things. So with 10 minutes, 1 hours and 1000 days, you output something like "1 hours ago".Maybe it's impossible as the variables all get computed from a single time interval. But you didn't show us.
I guess it's better for my sanity not to try too hard to figure out what it exactly does. So let me assume that all variable come from a single time interval. Then a condition like
minutes >= 60 && hours < 24can be expressed like
1 <= hours && hours < 24This doesn't buy us much as the order is still confusing:
- " hours ago"
- " days ago"
- " minutes from now"
- " months ago"
Again, not trying to find a system there.
What about something like
String suffix = minutes = 365) {
return (Math.abs(days) / 365) + " years " + suffix;
} else if (Math.abs(days) >= 2*30) {
return (Math.abs(days) / 12) + " months " + suffix;
} else if (Math.abs(days) >= 30) {
return days = 1) {
return Math.abs(days) + " days " + suffix;
} else if (Math.abs(hours) >= 1) {
return Math.abs(hours) + " hours " + suffix;
} else {
return Math.abs(minutes) + " minutes " + suffix;
}I'm not claiming it's correct, but it's short and damn simple, so you find and fix all bugs in a few seconds.
So what I dislike is
- The high count of cases, which could be cut in half using the
absidea stolen from Hosch250.
- Placing two tests in each if when one is enough. You need no range tests if you do it systematically.
- Even with range tests, there should be a clean order.
I always prefer to keep things simple from the very beginning. Otherwise, it can easily happen that you get a cool idea, but can't apply it because of the need to preserve compatibility with some quirks in the original.
Another disadvantage of starting with complicated stuff is that you may miss some simplifications the way I did above. Concerning correctness, all the divisions and comparisons with 356 and 30 are slightly wrong.
Note also that the task is not exactly defined. Given the dates
2000-12-31T23:23:00 and 2001-01-01T00:11:22, multiple answers are correct:- "48 minutes from now"
- "nearly one hour from now"
- "tomorrow"
- "next month"
- "next year"
And maybe also "next century" and "next millennium", but both are disputable (and irrelevant most of the time).
There's no clear solution for this and you should specify the behavior precisely and cover it by tests. The most straightforward solution would be to look at the calendar year first, which would give us the answer "next year". Pretty impractical, but every other solution needs some arbitrary choices.
I'd probably start with
String timeDifferenceString(long currentTimeMillis, long otherMillis) {
final boolean isPast = otherMillis < currentTimeMillis;
final Calendar first = Calendar.getInstance();
first.setTimeInMillis(Math.min(currentTimeMillis, otherMillis));
final Calendar second = Calendar.getInstance();
second.setTimeInMillis(Math.max(currentTimeMillis, otherMillis));
return timeDifferenceString(first, second, isPast);
}so that no
Math.abs is needed anymore. Obviously, Joda-Time or the corresponding JDK8 classes are a better choice than Calendar, but let's keep it simple for this answer.Now you can write
private String timeDifferenceString(Calendar first, Calendar second, boolean isPast) {
final String agoOrFromNow = isPast ? "ago" : "from now";
final String lastOrNext = isPast ? "last " : "next ";
final int years = second.get(Calendar.YEAR) - first.get(Calendar.YEAR);
if (years > 1) {
return years + " years " + agoOrFromNow;
} else if (years > 0) {
return lastOrNext + "year";
}
final int months = second.get(Calendar.MONTH) - first.get(Calendar.MONTH);
if (months > 1) {
return months + " months " + agoOrFromNow;
} else if (years > 0) {
return lastOrNext + "month";
}
...
return "now";
}This leads to a rather lengthy but trivial method. Now you may want to split it as suggested by h.j.k. This is not easy, as the parts above only conditionally return something. Using a guard condition, this can be solved like this
if (years > 0) {
if (years > 1) {
return years + " years " + agoOrFromNow;
} else if (years > 0) {
return lastOrNext + "year";
} else {
return "this year";
}
}After extracting the method, you'll see that it's about the same for every time unit, so you may want to generalize it to
private String timeDifferenceString(int count, String unitName, boolean isPast) {
if (count > 1) {
return count + " " + unitName + "s " + agoOrFromNow(isPast);
} else if (count > 0) {
return lastOrNext(isPast) + " " + unitName;
} else {
return "this " + unitName;
}
}Note that thi
Code Snippets
minutes >= 60 && hours < 241 <= hours && hours < 24String suffix = minutes < 0 ? "ago" : "from now";
if (Math.abs(days) >= 365) {
return (Math.abs(days) / 365) + " years " + suffix;
} else if (Math.abs(days) >= 2*30) {
return (Math.abs(days) / 12) + " months " + suffix;
} else if (Math.abs(days) >= 30) {
return days < 0 ? "Last month" : "Next month";
} else if (Math.abs(days) >= 1) {
return Math.abs(days) + " days " + suffix;
} else if (Math.abs(hours) >= 1) {
return Math.abs(hours) + " hours " + suffix;
} else {
return Math.abs(minutes) + " minutes " + suffix;
}String timeDifferenceString(long currentTimeMillis, long otherMillis) {
final boolean isPast = otherMillis < currentTimeMillis;
final Calendar first = Calendar.getInstance();
first.setTimeInMillis(Math.min(currentTimeMillis, otherMillis));
final Calendar second = Calendar.getInstance();
second.setTimeInMillis(Math.max(currentTimeMillis, otherMillis));
return timeDifferenceString(first, second, isPast);
}private String timeDifferenceString(Calendar first, Calendar second, boolean isPast) {
final String agoOrFromNow = isPast ? "ago" : "from now";
final String lastOrNext = isPast ? "last " : "next ";
final int years = second.get(Calendar.YEAR) - first.get(Calendar.YEAR);
if (years > 1) {
return years + " years " + agoOrFromNow;
} else if (years > 0) {
return lastOrNext + "year";
}
final int months = second.get(Calendar.MONTH) - first.get(Calendar.MONTH);
if (months > 1) {
return months + " months " + agoOrFromNow;
} else if (years > 0) {
return lastOrNext + "month";
}
...
return "now";
}Context
StackExchange Code Review Q#94718, answer score: 15
Revisions (0)
No revisions yet.