场景还原

服务端需要从客户端得到一个类似2020-05-24的日期格式, 那么Android客户端最简单的方式就是获取当前时间,然后用SimpleDateFormat格式化就行了, 如下

fun main() {
    println(format())
}

fun format(): String {
    val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
    val calendar = Calendar.getInstance(TimeZone.getDefault())
    calendar.timeInMillis = System.currentTimeMillis()
    return dateFormat.format(calendar.time)
}

问题复现

将上面这段代码在PC端运行, 会得到2020-05-24, 符合我们的预期

再将这段代码放到Android环境中运行, 也是2020-05-24,依旧符合预期

但是再将Android系统的语言和地区切换到缅甸, 将会得到၂၀၂၀-၀၅-၂၄, 即下面这张图

分析

Locale这个类中, 其实Android的文档已经提醒我们可能出现的bug处。

Locale文档

Be wary of the default locale

Note that there are many convenience methods that automatically use the default locale, but using them may lead to subtle bugs.

The default locale is appropriate for tasks that involve presenting data to the user. In this case, you want to use the user's date/time formats, number formats, rules for conversion to lowercase, and so on. In this case, it's safe to use the convenience methods.

The default locale is not appropriate for machine-readable output. The best choice there is usually {@code Locale.US} – this locale is guaranteed to be available on all devices, and the fact that it has no surprising special cases and is frequently used (especially for computer-computer communication) means that it tends to be the most efficient choice too.

A common mistake is to implicitly use the default locale when producing output meant to be machine-readable. This tends to work on the developer's test devices (especially because so many developers use en_US), but fails when run on a device whose user is in a more complex locale.

For example, if you're formatting integers some locales will use non-ASCII decimal digits. As another example, if you're formatting floating-point numbers some locales will use {@code ','} as the decimal point and {@code '.'} for digit grouping. That's correct for human-readable output, but likely to cause problems if presented to another computer ({@link Double#parseDouble} can't parse such a number, for example). You should also be wary of the {@link String#toLowerCase} and {@link String#toUpperCase} overloads that don't take a {@code Locale}: in Turkey, for example, the characters {@code 'i'} and {@code 'I'} won't be converted to {@code 'I'} and {@code 'i'}. This is the correct behavior for Turkish text (such as user input), but inappropriate for, say, HTTP headers.

把文档的话概括一下,就是Locale这个类提供了很多便捷的方法,但是很多方法默认是Locale.getDefault(),可能在一些地区会产生问题,因为大部分开发会使用是Locale.US

解决方案

val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())

to

val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)

原因是这个接口的参数我们是地区无关的,所以直接传Locale.US反而不会出错。