
Localization library for date/time objects, offering localizer classes, options and localize() extensions, with standalone vs absolute formatting for months, weekdays, time zones, dates and durations.
Multiplatform localization library for Kotlin date/time objects, either from stdlib or kotlinx-datetime.
The library uses a different localization backend depending on the platform:
| Platform | Localization backend |
|---|---|
| JVM | ICU4J |
| Android | android.icu¹ |
| JS (Browser + Node) | Intl¹ |
| WASM (Browser) | Intl¹ |
All other platforms are unsupported at the moment.
¹ As not all features are supported by all localization platforms, some small amount of locale data is bundled with the library itself to backfill the missing APIs.
[!NOTE] While the library strives to provide a uniform API that mostly returns consistent values, subtle differences between various localization backends exist. You should not rely on localized strings being identical between platforms.
Add to your dependencies:
dependencies {
implementation("dev.mmauro:datetime-polyglot:<version>")
}See latest version in badge above or look directly at Maven Central page.
This library uses semantic versioning. At the current 0.y stage, expect minor breaking changes at every increment of y. Full API stability guarantees will come with the 1.0 milestone.
Snapshot builds are published on every commit in mainline.
To use, add in your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven("https://central.sonatype.com/repository/maven-snapshots")
}
}Then add to your dependencies:
dependencies {
implementation("dev.mmauro:datetime-polyglot:<version>")
}See latest version in badge above or look at maven-metadata.xml.
In general, each type of data that can be localized will have:
localize() extension function on the data type that hides the construction of the localizer classAnything marked with a 🧪 means that the feature is experimental: it could be changed or dropped at any time. An opt-in annotation is required to use such features.
These should be used when the component to localize is standalone (e.g. calendar header), and should not be mixed with other date components.
| Data type | Localizer class / Extension function | Examples |
|---|---|---|
Month |
MonthLocalizer Month.localize()
|
January Jan J 1
|
DayOfWeek |
DayOfWeekLocalizer DayOfWeek.localize()
|
Monday Mon Mo M
|
TimeZone |
TimeZoneLocalizer TimeZone.localize()
|
America/Los_Angeles PT Pacific Time Los Angeles Time
|
These should be used when you want to localize an absolute date/time object to show the user. Avoid concatenating values from these localizers, always use the output of a localizer in full.
If you need only partial information, convert first to the appropriate type and then localize that.
For instance, if you have an Instant but are only interested in the time component, you should first convert to
LocalDateTime, then get the LocalTime part, and finally localize it.
Absolute localizers can be split into two sub-categories: zoned and local.
Local localizers handle just local values such as LocalDate, where no time zone information is present.
Zoned localizers handle an instance of
Zoned, which holds the
data in conjunction with a
TimeZone.
| Data type | Localizer class / Extension function | Examples |
|---|---|---|
LocalDateTime |
LocalDateTimeLocalizer LocalDateTime.localize()
|
1/8/26 9:05 PM Jan 8, 2026, 9 at night January 8, 2026 at 9:31:45 PM Thursday, January 8, 2026 at 21:05
|
LocalDate |
LocalDateLocalizer LocalDate.localize()
|
1/8/26 Jan 8, 2026 January 8, 2026 Thursday, January 8, 2026
|
LocalTime |
LocalTimeLocalizer LocalTime.localize()
|
9:05 PM 9:05:08 PM 21:05 21:05:08.123 9 at night
|
YearMonth |
YearMonthLocalizer YearMonth.localize()
|
January 2026 Jan 26 01/2026
|
Year (Int) |
YearLocalizer No extension function |
2026 26 2026 AD 2026 Anno Domini
|
Duration |
DurationLocalizer / 🧪 TickingDurationLocalizer Duration.localize() / 🧪 Duration.localizeTicking()
|
1h 5m 1 hr, 5 min 1 hour, 5 minutes
|
Note: all zoned localizers always have the timezone name in their output. If you do not wish to show it to the user, you should convert your date to the appropriate local value and use a local localizer instead.
| Data type | Localizer class / Extension function | Examples |
|---|---|---|
Zoned<Instant> |
ZonedInstantLocalizer Zoned<Instant>.localize()
|
1/8/26 9:05 PM PST Jan 8, 2026, 9 at night Pacific Daylight Time January 8, 2026 at 9:31:45 PM GMT-07:00 Thursday, January 8, 2026 at 21:05 Los Angeles Time
|
Zoned<LocalDate> |
🧪 ZonedLocalDateLocalizer 🧪 Zoned<LocalDate>.localize()
|
1/8/26, Pacific Time Jan 8, 2026, PT Thursday, January 8, 2026, Los Angeles Time
|
Zoned<YearMonth> |
🧪 ZonedYearMonthLocalizer 🧪 Zoned<YearMonth>.localize()
|
January 2026, Los Angeles Time Jan 26, PT 01/2026, Pacific Time
|
Zoned Year (Zoned<Int>) |
🧪 ZonedYearLocalizer No extension function |
2026, Los Angeles Time 26 AD, PT 2026 Anno Domini, Pacific Time
|
These should be used whenever you always want to localize a date/time object in a relative way. Avoid concatenating values from these localizers, always use the output of a localizer in full.
All these functions return
a TickingValue,
class that holds the localized value and a "next tick" duration, which indicates for how long the value is valid for.
When the "next tick" expires, it means that the localized value now needs recomputation.
Most of these classes
implement
PolyglotReferenceValueLocalizer,
which allows to pass in a reference point (usually the current time) to use for relative localization.
As a convenience, the
localizeAsFlow
extension function will return a
Flow that
automatically emits a new relative value when it is due.
| Data type | Has reference point | Localizer class / Extension function | Examples |
|---|---|---|---|
Duration |
No |
RelativeDurationLocalizer Duration.localizeRelative()
|
10 minutes ago in 1 hour 4h ago
|
Instant |
Yes |
RelativeInstantLocalizer Instant.localizeRelative() Instant.localizeRelativeAsFlow()
|
10 minutes ago in 1 hour 4h ago
|
LocalDateTime |
Yes |
RelativeDateAbsoluteTimeLocalizer LocalDateTime.localizeRelativeDateAbsoluteTime() LocalDateTime.localizeRelativeDateAbsoluteTimeAsFlow()
|
yesterday at 9:00 PM next Sunday at 4:00 AM in 15 days, 3:00 AM 21 days ago at 7:00 PM
|
LocalDate |
Yes |
RelativeLocalDateLocalizer LocalDate.localizeRelative() LocalDate.localizeRelativeAsFlow()
|
today yesterday this Monday next Friday in 54 days 5 days ago
|
YearMonth |
Yes |
RelativeYearMonthLocalizer YearMonth.localizeRelative() YearMonth.localizeRelativeAsFlow()
|
this month last mo. next month 4 months ago in 34mo
|
Year (Int) |
Yes |
RelativeYearLocalizer No extension functions |
last yr. this year next year 1 year ago in 5y
|
Dynamic localizers provide a convenient way to dynamically choose between a relative and absolute localization depending on the distance from the reference of the value.
They all re-use the above absolute and relative localizers, and they all implement
PolyglotReferenceValueLocalizer.
| Data type | Localizer class / Extension function | Examples |
|---|---|---|
LocalDateTime |
DynamicDateAbsoluteTimeLocalizer LocalDateTime.localizeDynamicDateAbsoluteTime() LocalDateTime.localizeDynamicDateAbsoluteTimeAsFlow()
|
yesterday at 9:00 AM next Sunday at 12:00 PM January 1 2026 at 8 in the morning 1/1/26, 8 PM
|
LocalDate |
DynamicLocalDateLocalizer LocalDate.localizeDynamic() LocalDate.localizeDynamicAsFlow()
|
yesterday in 5 days January 1 2026 01/01/2026
|
YearMonth |
DynamicYearMonthLocalizer YearMonth.localizeDynamic() YearMonth.localizeDynamicAsFlow()
|
last month in 5 mo July 2026 07/2026
|
Year (Int) |
DynamicYearLocalizer No extension functions |
last year in 5y 2026 2026 AD
|
Multiplatform localization library for Kotlin date/time objects, either from stdlib or kotlinx-datetime.
The library uses a different localization backend depending on the platform:
| Platform | Localization backend |
|---|---|
| JVM | ICU4J |
| Android | android.icu¹ |
| JS (Browser + Node) | Intl¹ |
| WASM (Browser) | Intl¹ |
All other platforms are unsupported at the moment.
¹ As not all features are supported by all localization platforms, some small amount of locale data is bundled with the library itself to backfill the missing APIs.
[!NOTE] While the library strives to provide a uniform API that mostly returns consistent values, subtle differences between various localization backends exist. You should not rely on localized strings being identical between platforms.
Add to your dependencies:
dependencies {
implementation("dev.mmauro:datetime-polyglot:<version>")
}See latest version in badge above or look directly at Maven Central page.
This library uses semantic versioning. At the current 0.y stage, expect minor breaking changes at every increment of y. Full API stability guarantees will come with the 1.0 milestone.
Snapshot builds are published on every commit in mainline.
To use, add in your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven("https://central.sonatype.com/repository/maven-snapshots")
}
}Then add to your dependencies:
dependencies {
implementation("dev.mmauro:datetime-polyglot:<version>")
}See latest version in badge above or look at maven-metadata.xml.
In general, each type of data that can be localized will have:
localize() extension function on the data type that hides the construction of the localizer classAnything marked with a 🧪 means that the feature is experimental: it could be changed or dropped at any time. An opt-in annotation is required to use such features.
These should be used when the component to localize is standalone (e.g. calendar header), and should not be mixed with other date components.
| Data type | Localizer class / Extension function | Examples |
|---|---|---|
Month |
MonthLocalizer Month.localize()
|
January Jan J 1
|
DayOfWeek |
DayOfWeekLocalizer DayOfWeek.localize()
|
Monday Mon Mo M
|
TimeZone |
TimeZoneLocalizer TimeZone.localize()
|
America/Los_Angeles PT Pacific Time Los Angeles Time
|
These should be used when you want to localize an absolute date/time object to show the user. Avoid concatenating values from these localizers, always use the output of a localizer in full.
If you need only partial information, convert first to the appropriate type and then localize that.
For instance, if you have an Instant but are only interested in the time component, you should first convert to
LocalDateTime, then get the LocalTime part, and finally localize it.
Absolute localizers can be split into two sub-categories: zoned and local.
Local localizers handle just local values such as LocalDate, where no time zone information is present.
Zoned localizers handle an instance of
Zoned, which holds the
data in conjunction with a
TimeZone.
| Data type | Localizer class / Extension function | Examples |
|---|---|---|
LocalDateTime |
LocalDateTimeLocalizer LocalDateTime.localize()
|
1/8/26 9:05 PM Jan 8, 2026, 9 at night January 8, 2026 at 9:31:45 PM Thursday, January 8, 2026 at 21:05
|
LocalDate |
LocalDateLocalizer LocalDate.localize()
|
1/8/26 Jan 8, 2026 January 8, 2026 Thursday, January 8, 2026
|
LocalTime |
LocalTimeLocalizer LocalTime.localize()
|
9:05 PM 9:05:08 PM 21:05 21:05:08.123 9 at night
|
YearMonth |
YearMonthLocalizer YearMonth.localize()
|
January 2026 Jan 26 01/2026
|
Year (Int) |
YearLocalizer No extension function |
2026 26 2026 AD 2026 Anno Domini
|
Duration |
DurationLocalizer / 🧪 TickingDurationLocalizer Duration.localize() / 🧪 Duration.localizeTicking()
|
1h 5m 1 hr, 5 min 1 hour, 5 minutes
|
Note: all zoned localizers always have the timezone name in their output. If you do not wish to show it to the user, you should convert your date to the appropriate local value and use a local localizer instead.
| Data type | Localizer class / Extension function | Examples |
|---|---|---|
Zoned<Instant> |
ZonedInstantLocalizer Zoned<Instant>.localize()
|
1/8/26 9:05 PM PST Jan 8, 2026, 9 at night Pacific Daylight Time January 8, 2026 at 9:31:45 PM GMT-07:00 Thursday, January 8, 2026 at 21:05 Los Angeles Time
|
Zoned<LocalDate> |
🧪 ZonedLocalDateLocalizer 🧪 Zoned<LocalDate>.localize()
|
1/8/26, Pacific Time Jan 8, 2026, PT Thursday, January 8, 2026, Los Angeles Time
|
Zoned<YearMonth> |
🧪 ZonedYearMonthLocalizer 🧪 Zoned<YearMonth>.localize()
|
January 2026, Los Angeles Time Jan 26, PT 01/2026, Pacific Time
|
Zoned Year (Zoned<Int>) |
🧪 ZonedYearLocalizer No extension function |
2026, Los Angeles Time 26 AD, PT 2026 Anno Domini, Pacific Time
|
These should be used whenever you always want to localize a date/time object in a relative way. Avoid concatenating values from these localizers, always use the output of a localizer in full.
All these functions return
a TickingValue,
class that holds the localized value and a "next tick" duration, which indicates for how long the value is valid for.
When the "next tick" expires, it means that the localized value now needs recomputation.
Most of these classes
implement
PolyglotReferenceValueLocalizer,
which allows to pass in a reference point (usually the current time) to use for relative localization.
As a convenience, the
localizeAsFlow
extension function will return a
Flow that
automatically emits a new relative value when it is due.
| Data type | Has reference point | Localizer class / Extension function | Examples |
|---|---|---|---|
Duration |
No |
RelativeDurationLocalizer Duration.localizeRelative()
|
10 minutes ago in 1 hour 4h ago
|
Instant |
Yes |
RelativeInstantLocalizer Instant.localizeRelative() Instant.localizeRelativeAsFlow()
|
10 minutes ago in 1 hour 4h ago
|
LocalDateTime |
Yes |
RelativeDateAbsoluteTimeLocalizer LocalDateTime.localizeRelativeDateAbsoluteTime() LocalDateTime.localizeRelativeDateAbsoluteTimeAsFlow()
|
yesterday at 9:00 PM next Sunday at 4:00 AM in 15 days, 3:00 AM 21 days ago at 7:00 PM
|
LocalDate |
Yes |
RelativeLocalDateLocalizer LocalDate.localizeRelative() LocalDate.localizeRelativeAsFlow()
|
today yesterday this Monday next Friday in 54 days 5 days ago
|
YearMonth |
Yes |
RelativeYearMonthLocalizer YearMonth.localizeRelative() YearMonth.localizeRelativeAsFlow()
|
this month last mo. next month 4 months ago in 34mo
|
Year (Int) |
Yes |
RelativeYearLocalizer No extension functions |
last yr. this year next year 1 year ago in 5y
|
Dynamic localizers provide a convenient way to dynamically choose between a relative and absolute localization depending on the distance from the reference of the value.
They all re-use the above absolute and relative localizers, and they all implement
PolyglotReferenceValueLocalizer.
| Data type | Localizer class / Extension function | Examples |
|---|---|---|
LocalDateTime |
DynamicDateAbsoluteTimeLocalizer LocalDateTime.localizeDynamicDateAbsoluteTime() LocalDateTime.localizeDynamicDateAbsoluteTimeAsFlow()
|
yesterday at 9:00 AM next Sunday at 12:00 PM January 1 2026 at 8 in the morning 1/1/26, 8 PM
|
LocalDate |
DynamicLocalDateLocalizer LocalDate.localizeDynamic() LocalDate.localizeDynamicAsFlow()
|
yesterday in 5 days January 1 2026 01/01/2026
|
YearMonth |
DynamicYearMonthLocalizer YearMonth.localizeDynamic() YearMonth.localizeDynamicAsFlow()
|
last month in 5 mo July 2026 07/2026
|
Year (Int) |
DynamicYearLocalizer No extension functions |
last year in 5y 2026 2026 AD
|