14

I override the locale of my app using:

 Locale locale = new Locale("ca");
    Locale.setDefault(locale);
    Configuration config = new Configuration();
    config.locale = locale;
    getBaseContext().getResources().updateConfiguration(config,
            getBaseContext().getResources().getDisplayMetrics());

But it seems that getRelativeTimeSpanString doesn't work with application context, and instead use the system locale.

user1458180
  • 155
  • 2
  • 7

3 Answers3

16

Your solution was almost correct.

However, since the strings are loaded from the system resources, you must update the configuration of the system resources. Below is what I did:

    Configuration configuration = new Configuration(Resources.getSystem().getConfiguration());
        configuration.locale = Locale.GERMANY; // or whichever locale you desire
        Resources.getSystem().updateConfiguration(configuration, null);

(NB: From Jelly Bean MR1 onwards, it is recommended to use

configuration.setLocale(Locale.GERMANY);

to change the locale rather then setting the member directly.)

Thorstenvv
  • 5,376
  • 1
  • 34
  • 28
  • Thank you. Saved me a lot of headaches. – Daniel Jette Apr 04 '17 at 17:51
  • 2
    It seems that on newer platforms (API > 24, maybe others), you have to set Locale.setDefault(Locale.GERMANY) instead. – muscardinus May 12 '17 at 17:04
  • Android Lollipop-specific: Using `Resources.getSystem().updateConfiguration()` was the only way I managed to force `DateUtils` (Uses resources' locale internally) to use a custom locale on API 21 and 22. `context.resources.updateConfiguration()` does not work for older devices – Sebastian Feb 16 '22 at 13:07
6

I had this problem today and took me several hours to "solve".

I recommend you that if you can use getRelativeTimeSpanString(context, long, boolean) instead do so. It was not my case as the resulting string is not the same as getRelativeTimeSpanString(long).

My workaround requires to copy some android internal resources of a couple of strings to your code so it will be quite annoying if you have a lot of languages to support.

I tracked the current source code of Android (4.4 r1) and extracted DateUtils and changed so you can use it like this:

CharSequence desiredString = MyDateUtils.getRelativeTimeSpanString(time, 
                System.currentTimeMillis(), 
                DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR, activity);

That will give you the same result as getRelativeTimeSpanString(long).

To make MyDateUtils i only kept:

public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution, int flags, Context c)

I added the context to the method, then changed the system resources for context resources and all the internal android strings for my own strings (which are the same as android's but couldnt find a way to use them as they were internal).

private static final String getRelativeDayString(Resources r, long day, long today)

Also kept this method as its used by the prior. Here i changed the strings as well.

For copy paste purposes

This is my implementation of MyDateUtils:

import java.util.Locale;
import android.content.Context;
import android.content.res.Resources;
import android.text.format.DateUtils;
import android.text.format.Time;
import com.YOURPACKAGE.R;
/**
 * This class contains various date-related utilities for creating text for things like
 * elapsed time and date ranges, strings for days of the week and months, and AM/PM text     etc.
 */
public class MyDateUtils
{

/**
 * Returns a string describing 'time' as a time relative to 'now'.
 * <p>
 * Time spans in the past are formatted like "42 minutes ago". Time spans in
 * the future are formatted like "in 42 minutes".
 * <p>
 * Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative
 * times, like "42 mins ago".
 *
 * @param time the time to describe, in milliseconds
 * @param now the current time in milliseconds
 * @param minResolution the minimum timespan to report. For example, a time
 *            3 seconds in the past will be reported as "0 minutes ago" if
 *            this is set to MINUTE_IN_MILLIS. Pass one of 0,
 *            MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS,
 *            WEEK_IN_MILLIS
 * @param flags a bit mask of formatting options, such as
 *            {@link #FORMAT_NUMERIC_DATE} or
 *            {@link #FORMAT_ABBREV_RELATIVE}
 */
public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution,
        int flags, Context c) {
    Resources r = c.getResources();
    //boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0;

    boolean past = (now >= time);
    long duration = Math.abs(now - time);

    int resId;
    long count;
    if (duration < DateUtils.MINUTE_IN_MILLIS && minResolution < DateUtils.MINUTE_IN_MILLIS) {
        count = duration / DateUtils.SECOND_IN_MILLIS;
        if (past) {
            resId = R.plurals.num_seconds_ago;
        } else {
            resId = R.plurals.in_num_seconds;
        }
    } else if (duration < DateUtils.HOUR_IN_MILLIS && minResolution < DateUtils.HOUR_IN_MILLIS) {
        count = duration / DateUtils.MINUTE_IN_MILLIS;
        if (past) {
            resId = R.plurals.num_minutes_ago;
        } else {
            resId = R.plurals.in_num_minutes;
        }
    } else if (duration < DateUtils.DAY_IN_MILLIS && minResolution < DateUtils.DAY_IN_MILLIS) {
        count = duration / DateUtils.HOUR_IN_MILLIS;
        if (past) {
            resId = R.plurals.num_hours_ago;
        } else {
            resId = R.plurals.in_num_hours;
        }
    } else if (duration < DateUtils.WEEK_IN_MILLIS && minResolution < DateUtils.WEEK_IN_MILLIS) {
        return getRelativeDayString(r, time, now);
    } else {
        // We know that we won't be showing the time, so it is safe to pass
        // in a null context.
        return DateUtils.formatDateRange(null, time, time, flags);
    }

    String format = r.getQuantityString(resId, (int) count);
    return String.format(format, count);
}



/**
 * Returns a string describing a day relative to the current day. For example if the day is
 * today this function returns "Today", if the day was a week ago it returns "7 days ago", and
 * if the day is in 2 weeks it returns "in 14 days".
 *
 * @param r the resources
 * @param day the relative day to describe in UTC milliseconds
 * @param today the current time in UTC milliseconds
 */
private static final String getRelativeDayString(Resources r, long day, long today) {
    Locale locale = r.getConfiguration().locale;
    if (locale == null) {
        locale = Locale.getDefault();
    }

    // TODO: use TimeZone.getOffset instead.
    Time startTime = new Time();
    startTime.set(day);
    int startDay = Time.getJulianDay(day, startTime.gmtoff);

    Time currentTime = new Time();
    currentTime.set(today);
    int currentDay = Time.getJulianDay(today, currentTime.gmtoff);

    int days = Math.abs(currentDay - startDay);
    boolean past = (today > day);

    // TODO: some locales name other days too, such as de_DE's "Vorgestern" (today - 2).
    if (days == 1) {
        if (past) {
            return r.getString(R.string.yesterday);
        } else {
            return r.getString(R.string.tomorrow);
        }
    } else if (days == 0) {
        return r.getString(R.string.today);
    }

    int resId;
    if (past) {
        resId = R.plurals.num_days_ago;
    } else {
        resId = R.plurals.in_num_days;
    }

    String format = r.getQuantityString(resId, days);
    return String.format(format, days);
}
}

On values folder string.xml add:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 <string name="yesterday">yesterday</string>
 <string name="tomorrow">tomorrow</string>
 <string name="today">today</string>

    <plurals name="num_seconds_ago">
        <item quantity="one">1 second ago</item>
        <item quantity="other"><xliff:g id="count">%d</xliff:g> seconds ago</item>
    </plurals>
    <plurals name="num_minutes_ago">
        <item quantity="one">1 minute ago</item>
        <item quantity="other"><xliff:g id="count">%d</xliff:g> minutes ago</item>
    </plurals>
    <plurals name="num_hours_ago">
        <item quantity="one">1 hour ago</item>
        <item quantity="other"><xliff:g id="count">%d</xliff:g> hours ago</item>
    </plurals>
    <plurals name="num_days_ago">
        <item quantity="one">yesterday</item>
        <item quantity="other"><xliff:g id="count">%d</xliff:g> days ago</item>
    </plurals>
    <plurals name="in_num_seconds">
        <item quantity="one">in 1 second</item>
        <item quantity="other">in <xliff:g id="count">%d</xliff:g> seconds</item>
    </plurals>
    <plurals name="in_num_minutes">
        <item quantity="one">in 1 minute</item>
        <item quantity="other">in <xliff:g id="count">%d</xliff:g> minutes</item>
    </plurals>
    <plurals name="in_num_hours">
        <item quantity="one">in 1 hour</item>
        <item quantity="other">in <xliff:g id="count">%d</xliff:g> hours</item>
    </plurals>
    <plurals name="in_num_days">
        <item quantity="one">tomorrow</item>
        <item quantity="other">in <xliff:g id="count">%d</xliff:g> days</item>
    </plurals>
</resources>

As an example, for another language such as Portuguese, on values-pt folder string.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">


    <string name="yesterday">ontem</string>
    <string name="tomorrow">manhã</string>
    <string name="today">hoje</string>

    <plurals name="num_seconds_ago">
        <item msgid="4869870056547896011" quantity="one">"1 segundo atrás"</item>
        <item msgid="3903706804349556379" quantity="other">"<xliff:g id="COUNT">%d</xliff:g> segundos atrás"</item>
    </plurals>
    <plurals name="num_minutes_ago">
        <item msgid="3306787433088810191" quantity="one">"1 minuto atrás"</item>
        <item msgid="2176942008915455116" quantity="other">"<xliff:g id="COUNT">%d</xliff:g> minutos atrás"</item>
    </plurals>
    <plurals name="num_hours_ago">
        <item msgid="9150797944610821849" quantity="one">"1 hora atrás"</item>
        <item msgid="2467273239587587569" quantity="other">"<xliff:g id="COUNT">%d</xliff:g> horas atrás"</item>
    </plurals>
    <plurals name="num_days_ago">
        <item msgid="861358534398115820" quantity="one">"ontem"</item>
        <item msgid="2479586466153314633" quantity="other">"<xliff:g id="COUNT">%d</xliff:g> dias atrás"</item>
    </plurals>
    <plurals name="in_num_seconds">
        <item msgid="2729745560954905102" quantity="one">"em 1 segundo"</item>
        <item msgid="1241926116443974687" quantity="other">"em <xliff:g id="COUNT">%d</xliff:g> segundos"</item>
    </plurals>
    <plurals name="in_num_minutes">
        <item msgid="8793095251325200395" quantity="one">"em 1 minuto"</item>
        <item msgid="3330713936399448749" quantity="other">"em <xliff:g id="COUNT">%d</xliff:g> minutos"</item>
    </plurals>
    <plurals name="in_num_hours">
        <item msgid="7164353342477769999" quantity="one">"em 1 hora"</item>
        <item msgid="547290677353727389" quantity="other">"Em <xliff:g id="COUNT">%d</xliff:g> horas"</item>
    </plurals>
    <plurals name="in_num_days">
        <item msgid="5413088743009839518" quantity="one">"amanhã"</item>
        <item msgid="5109449375100953247" quantity="other">"em <xliff:g id="COUNT">%d</xliff:g> dias"</item>
    </plurals>

</resources>
Antilope
  • 340
  • 2
  • 10
  • Sadly, that works only for languages that have a simply plural rule like "add s". Which means that in case of Serbian, I'm screwed. – Davor Oct 19 '15 at 12:20
  • +1 for saving my time..you can go further and localize numbers too with, NumberFormat.getInstance(resources.getConfiguration().locale).format(count); Also replace %d with %s – Dhunju_likes_to_Learn May 04 '16 at 22:37
  • to furhter explain, String.format(format, count) uses Locale.getDefault() which returns device's default Locale which could be different than app's Locale – Dhunju_likes_to_Learn May 04 '16 at 22:55
2

I use Custom method to handle this:

public static String getRelativeTimeSpanString(Context context, Date fromdate) {

long then;
then = fromdate.getTime();
Date date = new Date(then);

StringBuffer dateStr = new StringBuffer();

Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
Calendar now = Calendar.getInstance();

int days = daysBetween(calendar.getTime(), now.getTime());
int weeks= days/7;
int minutes = hoursBetween(calendar.getTime(), now.getTime());
int hours = minutes / 60;
if (days == 0) {

    int second = minuteBetween(calendar.getTime(), now.getTime());
    if (minutes > 60) {
        if (hours >= 1 && hours <= 24) {
            dateStr.append(String.format("%s %s %s",
                    hours,
                    context.getString(R.string.hour)
                    ,context.getString(R.string.ago)));
        }
    }else {
        if (second <= 10) {
            dateStr.append(context.getString(R.string.now));
        } else if (second > 10 && second <= 30) {
            dateStr.append(context.getString(R.string.few_seconds_ago));
        } else if (second > 30 && second <= 60) {
            dateStr.append(String.format("%s %s %s",
                    second,
                    context.getString(R.string.seconds)
                    ,context.getString(R.string.ago)));
        } else if (second >= 60 && minutes <= 60) {
            dateStr.append(String.format("%s %s %s",
                    minutes,
                    context.getString(R.string.minutes)
                    ,context.getString(R.string.ago)));
        }
    }
} else

if (hours > 24 && days < 7) {
    dateStr.append(String.format("%s %s %s",
            days,
            context.getString(R.string.days)
            ,context.getString(R.string.ago)));
}else if(weeks == 1){
    dateStr.append(String.format("%s %s %s",
            weeks,
            context.getString(R.string.weeks)
            ,context.getString(R.string.ago)));
}
else {
    /**
     * make formatted createTime by languageTag("fa")
     *
     * @return
     */
    return SimpleDateFormat.getDateInstance(SimpleDateFormat.LONG,new Locale("fra"))
            .format(date).toUpperCase();
}

return dateStr.toString();
}

public static int minuteBetween(Date d1, Date d2) {
return (int) ((d2.getTime() - d1.getTime()) / android.text.format.DateUtils.SECOND_IN_MILLIS);
}

public static int hoursBetween(Date d1, Date d2) {
return (int) ((d2.getTime() - d1.getTime()) / android.text.format.DateUtils.MINUTE_IN_MILLIS);
}

public static int daysBetween(Date d1, Date d2) {
return (int) ((d2.getTime() - d1.getTime()) / android.text.format.DateUtils.DAY_IN_MILLIS);
}

and I set date as a parameter to this method:

 return getRelativeTimeSpanString(context,this.createdTime);

this return string by location as I wanted! hope to help.

Zahra.HY
  • 1,684
  • 1
  • 15
  • 25