Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
a1d4814
Bump version to 2.0.0
jacobras Jun 22, 2026
d43cbe7
Custom i18n impl
jacobras Jun 20, 2026
3f1893a
Fix setting language tag with region
jacobras Jun 23, 2026
ba6f27d
Fix setting capitalised language tag
jacobras Jun 23, 2026
1048cea
Add tvOS + watchOS support
jacobras Jun 23, 2026
82c6fae
Merge pull request #192 from jacobras/improvement/custom-i18n
jacobras Jun 23, 2026
995c893
Update spelling
jacobras Jun 24, 2026
71b8d42
Fix a bunch of translation issues
jacobras Jun 23, 2026
8787ac1
Improve test stability
jacobras Jun 24, 2026
d2a696a
Update Kdoc
jacobras Jun 24, 2026
db349de
Package time things together
jacobras Jun 24, 2026
b508c5b
Add `Rounding` parameter
jacobras Jun 24, 2026
d1f52d0
Add `UpIfClose` rounding
jacobras Jun 24, 2026
424cde9
Group time tests together
jacobras Jun 24, 2026
74fb39c
Add `units` parameter to limit time units used in formatting
jacobras Jun 24, 2026
c604884
Fix duration formatting of numbers > 1000
jacobras Jun 24, 2026
7055a65
Tweak `UpIfClose` rounding to floor by default
jacobras Jun 26, 2026
79b7bcd
Extract `UpIfClose` rounding params into constants
jacobras Jun 26, 2026
2ff414b
Expand configuration options
jacobras Jun 26, 2026
1a94258
Add tests for new configuration options
jacobras Jun 26, 2026
11c8e18
Implement yesterday/today/tomorrow
jacobras Jun 26, 2026
217aafa
Remove wildcard import
jacobras Jun 26, 2026
4e2a150
Tweak `FormatStyle`, splitting up Short/Narrow and adding Digital var…
jacobras Jun 27, 2026
042eafc
Add Unicode links for translators
jacobras Jun 27, 2026
1c188b0
Make yesterday/today/tomorrow work for `LocalDate`s
jacobras Jun 30, 2026
dbb9ba0
Implement new configurability options
jacobras Jun 29, 2026
750b555
Fix `Rounding.HalfUp`
jacobras Jul 1, 2026
3f94063
Implement digital time formatting
jacobras Jul 1, 2026
6766688
Shorten approximation word
jacobras Jul 1, 2026
f35481b
Rename parameters
jacobras Jul 2, 2026
37d48d4
Support setting global time formatting options
jacobras Jul 2, 2026
c8182e5
Rework demo UI
jacobras Jul 1, 2026
0fa1dbc
Fix timeAgo() with LocalDate returning "now" incorrectly
jacobras Jul 2, 2026
4eda16d
Update README
jacobras Jul 2, 2026
a6c8b79
Only format consecutive parts by default
jacobras Jul 2, 2026
5e61da0
Show version number in web demo
jacobras Jul 2, 2026
641a0bc
Remove outdated workaround
jacobras Jul 2, 2026
8f19f8b
Remove outdated TODO
jacobras Jul 2, 2026
4047213
Update Compose to 1.12.0-beta01
jacobras Jul 2, 2026
4bed65e
Update Gradle to 9.6.1
jacobras Jul 2, 2026
3ee81c0
Fix "about" prefix not correctly added when formatting multiple parts
jacobras Jul 6, 2026
3d39814
Make `IfClose` rounding configurable
jacobras Jul 7, 2026
7c78db9
Reorder list of targets
jacobras Jul 7, 2026
252eb18
Add native targets
jacobras Jul 7, 2026
790e5b8
Update translations
jacobras Jul 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ jobs:
require_tests: true
report_paths: "**/build/test-results/**/TEST-*.xml"

# compileProductionExecutableKotlinJs

- name: Build production JS file for size comparison
run: ./gradlew compileProductionExecutableKotlinJs

Expand Down
89 changes: 58 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

![Android](http://img.shields.io/badge/-android-6EDB8D.svg?style=flat)
![iOS](http://img.shields.io/badge/-ios-CDCDCD.svg?style=flat)
![tvOS](http://img.shields.io/badge/-tvos-808080.svg?style=flat)
![watchOS](http://img.shields.io/badge/-watchos-D32D41.svg?style=flat)
![JS](http://img.shields.io/badge/-js-F8DB5D.svg?style=flat)
![wasm](https://img.shields.io/badge/-wasm-624DE9.svg?style=flat)

Expand All @@ -17,7 +19,7 @@ The library is published to Maven Central.

```kotlin
dependencies {
implementation("nl.jacobras:Human-Readable:1.13.0")
implementation("nl.jacobras:Human-Readable:2.0.0") // Not yet published!
}
```

Expand All @@ -40,7 +42,39 @@ HumanReadable.duration(7.days) // "1 week"
HumanReadable.duration(544.hours) // "3 weeks"
```

**Note**: The formatter switches to a bigger unit (minute, hour, day, ...) as soon as it can. See [Precision](#datetime-precision).
### ✍️ Formatting options

The formatter switches to a bigger unit (minute, hour, day, ...) as soon as it can.

There are a number of configuration options available for both `timeAgo()` and `duration()`.

```kotlin
HumanReadable.timeAgo(
instant = now - 134.minutes,
formatting = FormatStyle(
date = FormatStyle.Date.Long, // or Short: "1 hr, 50 min" or Narrow: "1h 50m"
time = FormatStyle.Time.Regular, // or Digital: "01:50:00"
indicateApproximation = true // will prefix "about" if the formatted time is not exact (i.e. a part was dropped or rounded)
),
parts = PartsConfig(
max = 2, // "1 hour, 50 minutes"
smallestDuration = 10.minutes, // anything smaller will return "less than 10 minutes"
subpartCutOffs = mapOf(TimeUnit.Hours to 12.hours), // drops subparts, e.g. "11 hours, 40 minutes" and then "12 hours"
onlyConsecutiveParts = true // whether "1 hour, 5 seconds" can be returned (as seconds are not the next unit after hours)
),
units = setOf(TimeUnit.Hours), // limits the output to these units, e.g. "391 days"
rounding = Rounding.HalfUp // or Floor to round down, or UpIfClose to round up on 55 seconds/55 minutes/23 hours/13 days
)
```

The above parameters can be set globally via `HumanReadable.config.time`.

```kotlin
HumanReadable.config.time.units = setOf(TimeUnit.Hours)
HumanReadable.timeAgo(now - 2.days) // "48 hours ago"
```

Visit the [interactive demo](#features) to see more examples in action.

### 📂 File size

Expand All @@ -54,7 +88,7 @@ HumanReadable.fileSize(21_947_282_882, decimals = 2) // "20.44 GB" in English /

### 🔢 Number abbreviation

Available since version 1.8, localized since 1.10.
Available since version 1.8, localised since 1.10.

```kotlin
HumanReadable.abbreviation(3_000) // "3K"
Expand All @@ -67,7 +101,7 @@ HumanReadable.abbreviation(2_500_000, decimals = 1) // "2.5M"
Available since version 1.10.

```kotlin
// English/default
// English
HumanReadable.number(1_000_000.34) // "1,000,000.34"

// French
Expand All @@ -93,48 +127,34 @@ HumanReadable.distance(value = 5350, unit = DistanceUnit.Foot) // "1.0 mi"
HumanReadable.distance(value = 28512, unit = DistanceUnit.Foot, decimals = 2) // "5.40 mi"
```

**Note:** numbers in meters and feet are always formatted with 0 decimals. The passed in
**Note:** numbers in meters and feet are always formatted with zero decimals. The passed in
number of decimals is only used for the larger units kilometers and miles.

## Date/time precision
The formatter switches to a bigger unit (minute, hour, day, ...) as soon as it can.
For example:

* `59.seconds` is "59 seconds" but `60.seconds` becomes "1 minute"
* `6.days` is "6 days" but `7.days` becomes "1 week"
* `29.days` is "29 days" but `30.days` becomes "1 month"

There's also some rounding involved:

* `8.days` and `10.days` are "1 week", but `11.days` already becomes "2 weeks"

This behaviour may become configurable in future releases.

## Localisation

The library uses the small [Libres](https://github.com/Skeptick/libres) library for its string resources. It detects the
current locale by default, but it's changeable on runtime.
See [Libres: Changing Localization](https://github.com/Skeptick/libres/blob/master/docs/LOCALIZATION.md#changing-localization).

You don't need to manually import Libres, as Gradle already pulls it in along with HumanReadable.
The library uses an internal i18n mechanism. It detects the current locale by default, but it's changeable at
runtime via `HumanReadable.config.languageTag`:

```kotlin
HumanReadable.timeAgo(instant) // "3 days ago"

LibresSettings.languageCode = "nl"
HumanReadable.config.languageTag = "nl"
HumanReadable.timeAgo(instant) // "3 dagen geleden"

LibresSettings.languageCode = "fr"
HumanReadable.config.languageTag = "fr"
HumanReadable.timeAgo(instant) // "il y a 3 jours"
```

If the requested locale is not supported, the library will fall back to `HumanReadable.fallbackLanguageTag`, which by
default is set to English.

### Supported languages

* Arabic (since 1.13.0)
* Czech
* Chinese (since 1.3.0)
* Dutch
* English (**default**)
* **English**
* Finnish (since 1.7.0)
* French
* German
Expand All @@ -157,7 +177,14 @@ HumanReadable.timeAgo(instant) // "il y a 3 jours"

Missing a language? Feel free to open an issue about it. Or, add it yourself:

1. Fork the code and navigate to [src/commonMain/libres/strings/](https://github.com/jacobras/Human-Readable/tree/main/src/commonMain/libres/strings)
2. Add a file named `time_units_[LANGUAGE CODE].xml` (see [Unicode: CLDR chart](https://www.unicode.org/cldr/charts/42/supplemental/language_plural_rules.html) for the code & plural categories).
3. If the language deviates from English data units (like French does), also add `data_units_[LANGUAGE CODE].xml`.
4. Open a PR.
1. Fork the code and navigate to `src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations`
2. Add a file named `XxStrings.kt` (where `Xx` is
the [language code](https://www.unicode.org/cldr/charts/48/supplemental/language_plural_rules.html)). Follow the
example of other translations.
3. Add a new entry to the `translations` map in
`src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations.kt`.
4. Open a PR.

Follow the Unicode spec at <https://st.unicode.org/cldr-apps/v#/en/Duration/>
or <https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-units-full/main/en/units.json>, if you prefer the
JSON format.
49 changes: 24 additions & 25 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.compose.compiler) apply false
id("com.vanniktech.maven.publish") version "0.37.0"
id("io.github.skeptick.libres") version "1.2.4"
signing
}

group = "nl.jacobras"
version = "1.13.0"
version = project.property("nl.jacobras.humanreadable.version") as String

mavenPublishing {
publishToMavenCentral()
Expand Down Expand Up @@ -49,6 +48,7 @@ kotlin {
explicitApi()
applyDefaultHierarchyTemplate()

// Targets, alphabetically sorted
iosX64()
iosArm64()
iosSimulatorArm64()
Expand All @@ -61,15 +61,6 @@ kotlin {
}
}
}
wasmJs {
browser {
testTask {
useKarma {
useChromeHeadless()
}
}
}
}
jvm {
testRuns.named("test") {
executionTask.configure {
Expand All @@ -84,23 +75,35 @@ kotlin {
}
}
}
linuxArm64()
linuxX64()
macosArm64()
mingwX64()
tvosArm64()
tvosSimulatorArm64()
wasmJs {
browser {
testTask {
useKarma {
useChromeHeadless()
}
}
}
}
watchosArm32()
watchosArm64()
watchosSimulatorArm64()

sourceSets {
val commonMain by getting {
dependencies {
implementation(libs.kotlinX.datetime)
}
commonMain.dependencies {
implementation(libs.kotlinX.datetime)
}
commonTest.dependencies {
implementation(kotlin("test"))
implementation(libs.assertK)
}
val wasmJsMain by getting
val appleAndJsMain by creating {
dependsOn(commonMain)
appleMain.get().dependsOn(this)
jsMain.get().dependsOn(this)
wasmJsMain.dependsOn(this)
webMain.dependencies {
implementation(libs.kotlinX.browser)
}
}
}
Expand All @@ -111,10 +114,6 @@ tasks.withType<AbstractPublishToMaven>().configureEach {
mustRunAfter(signingTasks)
}

libres {
generatedClassName = "HumanReadableRes"
}

signing {
setRequired {
!gradle.taskGraph.allTasks.any { it is PublishToMavenLocal }
Expand Down
8 changes: 8 additions & 0 deletions demo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.jetbrainsCompose)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.buildconfig)
}

kotlin {
Expand All @@ -28,6 +29,9 @@ kotlin {
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.compose.adaptive)
implementation(libs.compose.adaptive.layout)
implementation(libs.compose.adaptive.navigation)

implementation(libs.kotlinX.datetime)
implementation(rootProject)
Expand All @@ -37,4 +41,8 @@ kotlin {

rootProject.plugins.withType<YarnPlugin> {
rootProject.the<YarnRootExtension>().yarnLockMismatchReport = YarnLockMismatchReport.NONE
}

buildConfig {
buildConfigField("VERSION", project.property("nl.jacobras.humanreadable.version") as String)
}
Loading
Loading