法语本地化时间格式

3

Python的Time4J代码示例对应的是什么?

// duration in seconds normalized to hours, minutes and seconds
Duration<?> dur = Duration.of(337540, ClockUnit.SECONDS).with(Duration.STD_CLOCK_PERIOD);

// custom duration format => hh:mm:ss
String s1 = Duration.Formatter.ofPattern("hh:mm:ss").format(dur);
System.out.println(s1); // output: 93:45:40

// localized duration format for french
String s2 = PrettyTime.of(Locale.FRANCE).print(dur, TextWidth.WIDE);
System.out.println(s2); // output: 93 heures, 45 minutes et 40 secondes

获取 93:45:40 很容易:

#!/usr/bin/env python3
from datetime import timedelta

dur = timedelta(seconds=337540)
print(dur) # -> 3 days, 21:45:40
fields = {}
fields['hours'], seconds = divmod(dur // timedelta(seconds=1), 3600)
fields['minutes'], fields['seconds'] = divmod(seconds, 60)
print("%(hours)02d:%(minutes)02d:%(seconds)02d" % fields) # -> 93:45:40

但是我如何在Python中模拟PrettyTime.of(Locale.FRANCE).print(dur, TextWidth.WIDE)的Java代码(不硬编码单位)?

4个回答

1
如果您正在使用Kotlin,我刚刚遇到了一个类似的问题,涉及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"

如您所见,您可以指定自定义localeDurationFormat类型。默认情况下,它使用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"
        }
    }
}

1

babel模块可以让您接近所需的输出:

from babel.dates import format_timedelta # $ pip install babel

print(", ".join(format_timedelta(timedelta(**{unit: fields[unit]}),
                                 granularity=unit.rstrip('s'),
                                 threshold=fields[unit] + 1,
                                 locale='fr')
                for unit in "hours minutes seconds".split()))
# -> 93 heures, 45 minutes, 40 secondes

它会自动处理语言环境和复数形式,例如对于 dur = timedelta(seconds=1),它会生成:
0 heure, 0 minute, 1 seconde

也许更好的解决方案是使用标准工具(如 gettext)手动翻译格式字符串。

我不熟悉Python,但是babel库说明它引用CLDR数据并支持复数形式(法语中的零使用单数,正如您在输出示例中所看到的)。手动建模所有可能的区域设置的复数形式将很困难,因此如果您考虑到除了法语以外的其他内容,我建议您使用babel。您示例中唯一缺失的功能就是列表模式(逗号与“et”),但最终您可以向后扫描输出,并仅替换最后一个逗号为“ et”(不幸的是只对法语有效)。 - Meno Hochschild
@MenoHochschild: (1) 在翻译过程中,复数形式的处理很好(基于gettext的手动解决方案)。有一个标准模型(零、一、少数、多数、其他)。(2) babel 解决方案的问题在于它硬编码了可能适用于西方地区但在一般情况下可能失败的模式(TextWidth.WIDE)-- 手动翻译允许根据需要调整格式。 - jfs
如果我理解正确,babel的问题在于单位模式包括所有单位,即使前导持续时间为零(例如你的示例中的小时和分钟的一秒)。要解决这个问题,如果babel提供直接访问时间差模式字符串以进行进一步处理,那就很好了(Time4J使用此方式-包含列表模式)。然而,在http://babel.pocoo.org/docs/api/dates/上,我错过了这个选项。如果您只使用法语,那么手动解决方法可能比使用babel更好,抱歉。 - Meno Hochschild
你可以向 Babel 团队提出问题,或许他们会倾听你的意见。请参阅 https://github.com/mitsuhiko/babel/issues。 - Meno Hochschild
@MenoHochschild:使用if fields[unit]在genexpr中可以轻松处理零值。问题在于,我必须传递granularitythreshold参数以获得更精确的答案,并且它修复了单位(它们是小时、分钟、秒),而", ".join则修复了格式。如果我只调用format_timedelta(timedelta(seconds=337540), locale='fr'),那么结果分别为'4 jours''1 seconde'对应于timedelta(seconds=1),如果我不需要精确的答案,那就可以了。我想避免明确的格式,比如"hh:mm:ss",这可能在某些语言环境下不合适。 - jfs

0
使用 pendulum模块
>>> import pendulum
>>> it = pendulum.interval(seconds=337540)
>>> it.in_words(locale='fr_FR')
'3 jours 21 heures 45 minutes 40 secondes'

0
这个humanize包可能会有所帮助。它有一个法语本地化版本,或者您可以添加自己的本地化。适用于Python 2.7和3.3。

humanize.naturaltime(dur)fr_FR区域设置下产生的结果为"il y a 3 jours",但这不是所期望的输出。 - jfs
你应该调用 humanize.naturaldelta(dur) 来避免 "il y a",虽然你仍然无法获得更详细的细分。 - meuh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接