Hey everybody, let’s talk localization! It’s a passion of mine that I’d like to spend more time on, and I want to share some tips and tricks I’ve picked up in my work. Hopefully, this is only the first of many localization posts to come. Keep an eye out for more in the future!

Today, I want to look at date formatting for Android applications. When you want to display a date in your app, you have to keep in mind that your customers in different locales will expect dates to look the way they’re used to seeing them. But while there are a lot of date formatting options on Android, they aren’t all robust enough to ‘just work’ the way you need them to.

So first, let’s take a look at two common solutions that aren’t as great as they seem. Then I’ll show you a how to get you exactly the format you want every time.

LocalDate, LocalTime, LocalDateTime - from java.time

These newer Java 8 time classes were built to take the place of the old, clunky Date, Calendar, and DateFormat classes - which you should never rely on completely for localized dates because they’re junk. But this new java.time library is supposed to take care of all the problems developers had with the old ones. So… problem solved?

Maybe for Java devs, but this newer library only works with API 26 or higher on Android. Many projects still support older APIs, so these libraries simply aren’t an option them. However, if you don’t have to worry about that, give these a shot! I’ll see if I can spend some time with these in the future and share my thoughts.

SimpleDateFormat - from java.text

A ton of Android projects take advantage of SimpleDateFormat, and why wouldn’t you? Even the Android docs say this class is for formatting and parsing dates in a locale-sensitive manner. Sounds great, right? Well, I think it’s a bit of a trap.

Java’s SimpleDateFormat is a really attractive solution for anyone who previously tried the DateFormat class and found the formatting options to be really restrictive. DateFormat supports a limited number of pre-determined date-time arrangements, and plenty of desirable options are missing - like showing the month and date without the year. (The year is always there! Augh!! Hair pulling madness.) But with SimpleDateFormat, you can use a string value to explicitly build your own date format.

val myFormat = SimpleDateFormat("MMMM dd, yyyy")
// no locale specified, uses Locale.getDefault()

val date = Date()
println(myFormat.parse(date))

/* sample output
en_US, USA -- December 07, 2008
fr_FR, France -- décembre 07, 2008
*/

Super easy, right? Well, if you have keen eyes and you’re familiar with the date format in France, you’ve already noticed the sample output would be incorrect for that region. France puts the day number before the month!

wrong -> décembre 07, 2008
right -> 07 décembre 2008

And that’s the trap. The main reason to use SimpleDateFormat is to create your own format, but when you create your own format, it’s not really ’locale-sensitive’ after all! And that’s a bit of a contradiction, I think.

In the end, your hard-coded string is the problem, and no matter what locale your app is running in, that format never changes to match. Now, there’s a way around this - you can extract your format string into a strings.xml resource file and add a ’translated’ version of the format in the French resource.

<!-- res/values-en/strings.xml -->
<string name="date_format_1">MMMM dd, yyyy</string>

<!-- res/values-fr/strings.xml -->
<string name="date_format_1">dd MMMM yyyy</string>

This works fine, and it might seem reasonable at first. But if your project needs 10 different date formats in 10 different languages… that’s already 100 formatting strings you need to add to your resources and keep track of over time.

If you need that kind of specificity, give it a shot! But I don’t think you do, and I’m about to prove it. Let’s take a look a look at DateUtils - the tool that does all this junk for you.

DateUtils

DateUtils is the absolute bomb for date formatting on Android. It’s flexible, robust, and aboslutely is sensitive to your customer’s active locale. In fact, you’re not even allowed to specify the locale being used when you format with DateUtils, and for the large majority of common date formatting needs, this is absolutely what you want. You want the system to handle most things for you, and DateUtils does this well.

The flexibility in using DateUtils comes from its bitmask formatting flags. They allow you to specify exactly which date or time components you want to appear in your formatted date value, but instead of showing up in the order you choose (like with the string from before), they act as simple on/off switches independent of order or locale. Here’s an example.

println(
  DateUtils.formatDateTime(
    context,    // needs a Context
    myDate,     // the Date object to format
    DateUtils.FORMAT_SHOW_DATE  // the format flag for showind 'date' (includes year)
  )
}

/* output:
en_US -> September 25, 2005
fr_FR -> 25 septembre 2005
zh_CN -> 2005年9月25日
*/

Let’s break this down a bit:

  • DateUtils.formatDateTime outputs a String value
  • First param is a Context
  • Second param is the Date you want to format
  • Third param is the set of format flags you want to apply to the Date

And that’s it! No explicit format strings, no translated format resources, no locale problems! It just works.

Let’s see how you can use the bit flags to remove the year now.

DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR

/* output:
en_US -> September 25
fr_FR -> 25 septembre
zh_CN -> 9月25日
*/

The or bit operator takes both of the flags, combines them, and outputs a format with both flags enabled. This works with any number of formatting flags, including things like word abbreviations, numeric dates, showing weekdays, showing time, hiding the month, etc. Here are all the available flags for you.

DateUtils is truly the most flexible, easy-to-use, built-in date formatter I’ve come across for Android projects. If you aren’t using it yet, you should really think about switching to it today.

DateUtils and Kotlin Extension Functions

Want to supercharge your use of DateUtils? Use it in some Kotlin extension functions! Here are some fantastic samples from the Github project linked at the top of this post. These are ready for use in your project today!

Show only the time:

fun Date.asTime(context: Context): String =
    DateUtils.formatDateTime(context, time, DateUtils.FORMAT_SHOW_TIME)

Show date with year, numeric and abbreviation options:

fun Date.asDateWithYear(
    context: Context,
    numeric: Boolean = false,
    abbreviated: Boolean = false
): String {
    var formatFlags = DateUtils.FORMAT_SHOW_DATE
    if (numeric) formatFlags = formatFlags or DateUtils.FORMAT_NUMERIC_DATE
    if (abbreviated) formatFlags = formatFlags or DateUtils.FORMAT_ABBREV_MONTH

    return DateUtils.formatDateTime(context, time, formatFlags)
}

Dynamically show the time (if date is today) or the date (if date is not today):

fun Date.asTimeOrRecentDate(): String =
    DateUtils.formatSameDayTime(
        time,
        Date().time,
        DateFormat.LONG,
        DateFormat.SHORT
    ).toString()

And something super flexible!

fun Date.flexFormatted(
    context: Context,
    showWeekday: Boolean = false,
    showMonth: Boolean = true,
    showMonthDay: Boolean = true,
    showYear: Boolean = false,
    showTime: Boolean = false,
    numeric: Boolean = false,
    abbreviated: Boolean = false,
): String {
    var formatFlags = 0 // starts blank
    if (showWeekday) formatFlags = formatFlags or DateUtils.FORMAT_SHOW_WEEKDAY
    if (showMonth) formatFlags = formatFlags or DateUtils.FORMAT_SHOW_DATE
    if (!showMonthDay) formatFlags = formatFlags or DateUtils.FORMAT_NO_MONTH_DAY
    if (!showYear) formatFlags = formatFlags or DateUtils.FORMAT_NO_YEAR
    if (showTime) formatFlags = formatFlags or DateUtils.FORMAT_SHOW_TIME
    if (numeric) formatFlags = formatFlags or DateUtils.FORMAT_NUMERIC_DATE
    if (abbreviated) formatFlags = formatFlags or DateUtils.FORMAT_ABBREV_MONTH or DateUtils.FORMAT_ABBREV_WEEKDAY

    return DateUtils.formatDateTime(context, time, formatFlags)
}

Thanks for reading! I hope this little trip into the world of localization was as fun for you as it was for me. Please - ask questions, give opinions, and share your ideas in the comments! I’d love to swap date formatting stories with you. Cheers!