如何在 Java/Android 中的格式化字符串中显示本地化的日期单位?
How to show localized date units in a formatted string in Java/Android?
假设我有几天:
Period p = Period.ofDays(3);
我想用字符串中的 "days" 标签格式化句点,以将此字符串作为输出:
// "3 days"
...但是,我想本地化 "days" 组件,所以我不能使用像下面这样的格式化字符串,否则它只会以英文正确显示:
String.format("%d days", numberOfDays); // Won't localize 'days'
Java/Kotlin/Android 中有哪些 API 表示一段时间,例如语言环境中的小时、天、周、年?如果能让 API 去做,我宁愿不自己本地化这些词。
如一条评论中所建议,您可以使用我的库 Time4J 并使用以下代码:
Period p = Period.ofDays(3);
Locale loc = Locale.ENGLISH; // or any other supported locale
String formatted = PrettyTime.of(loc).print(p); // 3 days
该教程还包含一个 list of currently supported languages。顺便说一句,如果您对规范化或扩展的 ISO 兼容性等其他功能感兴趣,java.time.Period
的 Time4J 等价物将是 net.time4j.Duration<CalendarUnit>
注意:如果您使用的是 Android,那么您应该使用姊妹库 Time4A 而不是 Time4J,但提供的代码是相同的.
当使用具有本地化格式的Kotlin Duration
类型时,因为找不到好的解决方案,我自己写了一个。它基于从 Android 9(针对本地化单元)开始提供的 API,但对于较低的 Android 版本可以回退到英语单元,因此它可以与较低目标的应用程序一起使用。
这是它在使用方面的样子(请参阅 Kotlin Duration 类型以了解第一行):
val duration = 5.days.plus(3.hours).plus(2.minutes).plus(214.milliseconds)
DurationFormat().format(duration) // "5day 3hour 2min"
DurationFormat(Locale.GERMANY).format(duration) // "5T 3Std. 2Min."
DurationFormat(Locale.forLanguageTag("ar").format(duration) // "٥يوم ٣ساعة ٢د"
DurationFormat().format(duration, smallestUnit = DurationFormat.Unit.HOUR) // "5day 3hour"
DurationFormat().format(15.minutes) // "15min"
DurationFormat().format(0.hours) // "0sec"
如您所见,您可以将自定义 locale
指定为 DurationFormat
类型。默认情况下它使用 Locale.getDefault()
。还支持数字符号不同于罗马数字的语言(通过 NumberFormat
)。另外,您可以指定自定义 smallestUnit
,默认设置为 SECOND
,因此不会显示毫秒。请注意,任何值为 0
的单位都将被忽略,如果整个数字为 0
,则将使用值为 0
.
的最小单位
这是用于复制和粘贴的完整DurationFormat
类型(也可作为GitHub gist包括单元测试):
import android.icu.text.MeasureFormat
import android.icu.text.NumberFormat
import android.icu.util.MeasureUnit
import android.os.Build
import java.util.Locale
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.days
import kotlin.time.hours
import kotlin.time.milliseconds
import kotlin.time.minutes
import kotlin.time.seconds
@ExperimentalTime
data class DurationFormat(val locale: Locale = Locale.getDefault()) {
enum class Unit {
DAY, HOUR, MINUTE, SECOND, MILLISECOND
}
fun format(duration: kotlin.time.Duration, smallestUnit: Unit = Unit.SECOND): String {
var formattedStringComponents = mutableListOf<String>()
var remainder = duration
for (unit in Unit.values()) {
val component = calculateComponent(unit, remainder)
remainder = when (unit) {
Unit.DAY -> remainder - component.days
Unit.HOUR -> remainder - component.hours
Unit.MINUTE -> remainder - component.minutes
Unit.SECOND -> remainder - component.seconds
Unit.MILLISECOND -> remainder - component.milliseconds
}
val unitDisplayName = unitDisplayName(unit)
if (component > 0) {
val formattedComponent = NumberFormat.getInstance(locale).format(component)
formattedStringComponents.add("$formattedComponent$unitDisplayName")
}
if (unit == smallestUnit) {
val formattedZero = NumberFormat.getInstance(locale).format(0)
if (formattedStringComponents.isEmpty()) formattedStringComponents.add("$formattedZero$unitDisplayName")
break
}
}
return formattedStringComponents.joinToString(" ")
}
private fun calculateComponent(unit: Unit, remainder: Duration) = when (unit) {
Unit.DAY -> remainder.inDays.toLong()
Unit.HOUR -> remainder.inHours.toLong()
Unit.MINUTE -> remainder.inMinutes.toLong()
Unit.SECOND -> remainder.inSeconds.toLong()
Unit.MILLISECOND -> remainder.inMilliseconds.toLong()
}
private fun unitDisplayName(unit: Unit) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val measureFormat = MeasureFormat.getInstance(locale, MeasureFormat.FormatWidth.NARROW)
when (unit) {
DurationFormat.Unit.DAY -> measureFormat.getUnitDisplayName(MeasureUnit.DAY)
DurationFormat.Unit.HOUR -> measureFormat.getUnitDisplayName(MeasureUnit.HOUR)
DurationFormat.Unit.MINUTE -> measureFormat.getUnitDisplayName(MeasureUnit.MINUTE)
DurationFormat.Unit.SECOND -> measureFormat.getUnitDisplayName(MeasureUnit.SECOND)
DurationFormat.Unit.MILLISECOND -> measureFormat.getUnitDisplayName(MeasureUnit.MILLISECOND)
}
} else {
when (unit) {
Unit.DAY -> "day"
Unit.HOUR -> "hour"
Unit.MINUTE -> "min"
Unit.SECOND -> "sec"
Unit.MILLISECOND -> "msec"
}
}
}
假设我有几天:
Period p = Period.ofDays(3);
我想用字符串中的 "days" 标签格式化句点,以将此字符串作为输出:
// "3 days"
...但是,我想本地化 "days" 组件,所以我不能使用像下面这样的格式化字符串,否则它只会以英文正确显示:
String.format("%d days", numberOfDays); // Won't localize 'days'
Java/Kotlin/Android 中有哪些 API 表示一段时间,例如语言环境中的小时、天、周、年?如果能让 API 去做,我宁愿不自己本地化这些词。
如一条评论中所建议,您可以使用我的库 Time4J 并使用以下代码:
Period p = Period.ofDays(3);
Locale loc = Locale.ENGLISH; // or any other supported locale
String formatted = PrettyTime.of(loc).print(p); // 3 days
该教程还包含一个 list of currently supported languages。顺便说一句,如果您对规范化或扩展的 ISO 兼容性等其他功能感兴趣,java.time.Period
的 Time4J 等价物将是 net.time4j.Duration<CalendarUnit>
注意:如果您使用的是 Android,那么您应该使用姊妹库 Time4A 而不是 Time4J,但提供的代码是相同的.
当使用具有本地化格式的Kotlin Duration
类型时,因为找不到好的解决方案,我自己写了一个。它基于从 Android 9(针对本地化单元)开始提供的 API,但对于较低的 Android 版本可以回退到英语单元,因此它可以与较低目标的应用程序一起使用。
这是它在使用方面的样子(请参阅 Kotlin Duration 类型以了解第一行):
val duration = 5.days.plus(3.hours).plus(2.minutes).plus(214.milliseconds)
DurationFormat().format(duration) // "5day 3hour 2min"
DurationFormat(Locale.GERMANY).format(duration) // "5T 3Std. 2Min."
DurationFormat(Locale.forLanguageTag("ar").format(duration) // "٥يوم ٣ساعة ٢د"
DurationFormat().format(duration, smallestUnit = DurationFormat.Unit.HOUR) // "5day 3hour"
DurationFormat().format(15.minutes) // "15min"
DurationFormat().format(0.hours) // "0sec"
如您所见,您可以将自定义 locale
指定为 DurationFormat
类型。默认情况下它使用 Locale.getDefault()
。还支持数字符号不同于罗马数字的语言(通过 NumberFormat
)。另外,您可以指定自定义 smallestUnit
,默认设置为 SECOND
,因此不会显示毫秒。请注意,任何值为 0
的单位都将被忽略,如果整个数字为 0
,则将使用值为 0
.
这是用于复制和粘贴的完整DurationFormat
类型(也可作为GitHub gist包括单元测试):
import android.icu.text.MeasureFormat
import android.icu.text.NumberFormat
import android.icu.util.MeasureUnit
import android.os.Build
import java.util.Locale
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.days
import kotlin.time.hours
import kotlin.time.milliseconds
import kotlin.time.minutes
import kotlin.time.seconds
@ExperimentalTime
data class DurationFormat(val locale: Locale = Locale.getDefault()) {
enum class Unit {
DAY, HOUR, MINUTE, SECOND, MILLISECOND
}
fun format(duration: kotlin.time.Duration, smallestUnit: Unit = Unit.SECOND): String {
var formattedStringComponents = mutableListOf<String>()
var remainder = duration
for (unit in Unit.values()) {
val component = calculateComponent(unit, remainder)
remainder = when (unit) {
Unit.DAY -> remainder - component.days
Unit.HOUR -> remainder - component.hours
Unit.MINUTE -> remainder - component.minutes
Unit.SECOND -> remainder - component.seconds
Unit.MILLISECOND -> remainder - component.milliseconds
}
val unitDisplayName = unitDisplayName(unit)
if (component > 0) {
val formattedComponent = NumberFormat.getInstance(locale).format(component)
formattedStringComponents.add("$formattedComponent$unitDisplayName")
}
if (unit == smallestUnit) {
val formattedZero = NumberFormat.getInstance(locale).format(0)
if (formattedStringComponents.isEmpty()) formattedStringComponents.add("$formattedZero$unitDisplayName")
break
}
}
return formattedStringComponents.joinToString(" ")
}
private fun calculateComponent(unit: Unit, remainder: Duration) = when (unit) {
Unit.DAY -> remainder.inDays.toLong()
Unit.HOUR -> remainder.inHours.toLong()
Unit.MINUTE -> remainder.inMinutes.toLong()
Unit.SECOND -> remainder.inSeconds.toLong()
Unit.MILLISECOND -> remainder.inMilliseconds.toLong()
}
private fun unitDisplayName(unit: Unit) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val measureFormat = MeasureFormat.getInstance(locale, MeasureFormat.FormatWidth.NARROW)
when (unit) {
DurationFormat.Unit.DAY -> measureFormat.getUnitDisplayName(MeasureUnit.DAY)
DurationFormat.Unit.HOUR -> measureFormat.getUnitDisplayName(MeasureUnit.HOUR)
DurationFormat.Unit.MINUTE -> measureFormat.getUnitDisplayName(MeasureUnit.MINUTE)
DurationFormat.Unit.SECOND -> measureFormat.getUnitDisplayName(MeasureUnit.SECOND)
DurationFormat.Unit.MILLISECOND -> measureFormat.getUnitDisplayName(MeasureUnit.MILLISECOND)
}
} else {
when (unit) {
Unit.DAY -> "day"
Unit.HOUR -> "hour"
Unit.MINUTE -> "min"
Unit.SECOND -> "sec"
Unit.MILLISECOND -> "msec"
}
}
}