Search code examples
androidtimelocaletranslationpresentation

Expression of time difference


Given a number of seconds/milliseconds, what is the best method to gracefully/cleanly express this time interval in the user's language, using the Android values/strings.xml API?

I am not asking for how to get the days/hours/minutes -- answers for that can be found in how to show milliseconds in days:hours:min:seconds and many other questions. For your information, my code for doing that:

int time1, time0; // given
int seconds, minutes, hours, days;
seconds = time1 - time0;
// TODO handle seconds <= 0
days = seconds / 86400;
hours = (seconds %= 86400) / 3600;
minutes = (seconds %= 3600) / 60;
seconds %= 60;

These are some methods I have brainstormed, but all have their disadvantages. What is the best method that doesn't have these problems?

Method 1: Using printf formats

strings.xml

<string name="format_time" formatted="false">
    %d day(s) %d hour(s) %d minute(s) %d second(s)
</string>

Java code

getString(R.string.format_time, days, hours, minutes, seconds);

Problem

As the (s)s would have inspired you, we don't like to see 0 day(s) 0 hour(s) 0 minute(s) 1 second(s). Neither do we like 1 day(s) 0 hour(s) 0 minute(s) 0 second(s), but just 1 day.

Method 2: Using terms per unit

strings.xml

<string-array name="time_units">
    <item>day(s)</item>
    <item>hour(s)</item>
    <item>minute(s)</item>
    <item>second(s)</item>
</string-array>
<string name="time_value_unit_separator">" "</string>
<string name="time_inter_unit_separator">", "</string>

Java code

String[] units = getResources().getStringArray(R.array.time_units);
int[] values = {days, hours, minutes, seconds};
assert units.length == values.length;
StringBuilder builder = new StringBuilder();
final String valueUnitSeparator = getString(R.string.time_value_unit_separator);
final String unitSeparator = getString(R.string.time_inter_unit_separator);
for(int i = 0; i < units.length; i++){
    if(values[i] > 0)
        builder.append(values[i])
                .append(valueUnitSeparator)
                .append(units[i])
                .append(unitSeparator);
}
return builder.substring(0, builder.length() - unitSeparator.length());

Problems

  1. According to Android Studio, it is discouraged that developers use string concatenation to build texts for user interface. I am unable to find official reasons behind it for reference.
  2. Maybe some languages prefer saying seconds 5 rather than 5 seconds?
  3. Maybe some languages prefer saying 30 seconds, 2 minutes rather than 2 minutes 30 seconds?

Solution

  • Regarding your first part with concatenation, there's no clean way to do it using the Android framework. The "official" reason would be it's slower to concat than it is to use formatting, but you're talking about milliseconds here. Worst case here, you're only going to update the text every one second so it's not a pain and nobody would notice.

    There's a thing called Plurals which you can use to get different strings (i.e. "Hour" for 1 hour and "Hours" for >1 hour). So you could get a utility method like so and just use that:

    public static String getHourString(Context ctx, int hours) {
       if (hours == 0) {
          return "";
       } else {
          return ctx.getResources().getQuantityString(R.plurals.hoursString, hours, hours);
       }
    }
    
    public static String getMinutesString(Context ctx, int mins) {
           if (mins == 0) {
              return "";
           } else {
              return ctx.getResources().getQuantityString(R.plurals.minutesString, mins, mins);
           }
        }
    
    public static String getSecondsString(Context ctx, int secs) {
           if (secs == 0) {
              return "";
           } else {
              return ctx.getResources().getQuantityString(R.plurals.secondsString, secs, secs);
           }
        }
    

    For time formatting, there is the Java time formatting and Joda-Time has some help with date locale as well.

    Though that is for time and date formats. Your method (e.g. 1 minute, 1 second) isn't a standard as far as I know. For your method, you'll have to use Locale Directories and create a string for each language that you want to support. Although, since you have to manually concat the strings, this probably won't help you much.