
Facilitates shared data types across projects, addressing duplication issues. Includes types for Location, Locale, Currency, and potentially Duration shortcuts for types like Distance.
A Kotlin Multiplatform library of strongly-typed, shareable data types — money and currency, locales, coordinates and telemetry, GeoJSON geometry, RFC-compliant identifiers, and arbitrary-precision math. It exists because KMP projects kept redefining the same types incompatibly; these are the shared ones, with no platform-specific API leaking through.
Targets: Android, JVM, iOS, JS, Wasm and Linux.
Published to Maven Central as io.github.aughtone:types.
// build.gradle.kts
implementation("io.github.aughtone:types:4.1.0")Or through a version catalog:
# gradle/libs.versions.toml
[versions]
aughtone-types = "4.1.0"
[libraries]
aughtone-types = { module = "io.github.aughtone:types", version.ref = "aughtone-types" }// build.gradle.kts
implementation(libs.aughtone.types)[!IMPORTANT] v4.1.0 Breaking Changes:
Outcomeis aligned withkotlin.Result.dataOrNull,dataOrThrowanddataOrElseare renamedgetOrNull,getOrThrowandgetOrElse, with no deprecated aliases, and the callbacks handed toonFailure,fold,recoverandgetOrElsenow receive theThrowablerather than theOutcome.Failure. Check the callbacks first: they still compile. A body readingit.messagekeeps building but now gets the nullableThrowable.messageinstead of the non-nullOutcome.Failure.message, so an exception with no message renders the textnull. Useit.message ?: it.toString().isSuccess,isFailure,exceptionOrNull()andgetOrDefaultare new.Currencynow compares by its ISO 4217 code alone, so aSetor map keyed byCurrencymay hold one entry where it held two, andMoneyamounts from different sources sort instead of throwing.Gigabyteis nowGiBandGigabitGbit, keepingGBandGbas alternatives, so rendering.symbolchanges for those two. Also:localizedDisplayNamereturned English for every locale on the JS and Wasm targets in 3.4.0 and 4.0.0 and now returns real translations, so anything rendering locale names on web changes output.v4.0.0 Breaking Changes: A breaking release.
Outcome.ErrorandLocale.toLanguageTag()are removed;Moneyequality is now numeric so5.1equals5.10;DistanceandSpeedthrow instead of clamping to zero;GeoBoundingBoxleaves the geometry hierarchy;GeoFeature.propertiesbecomesJsonObject; invalid GeoJSON is now rejected; andUnitOfMeasure.findFirstrefuses ambiguous symbols rather than guessing. Several change behaviour without a compile error — see the changelog before upgrading.v3.4.0
Outcome.Errorrenamed: The failure case ofOutcomeis nowOutcome.Failure, and the factory isOutcome.failure(...). The old names shipped as deprecated aliases in 3.4.0 and are removed in 4.0.0.Outcome$Errorno longer exists as a class, so upgrading from 3.3.0 needs a clean and rebuild rather than a code change.v3.3.0 Locale Display Names: Eleven locale
displayNamevalues are corrected — renamed countries (Czechia,North Macedonia,Türkiye), incomplete or abbreviated country names, and dated language exonyms (Farsi→Persian,Azeri→Azerbaijani). No API changed, but snapshot tests and cached UI strings holding the old names will need updating. See the changelog for the full table.v3.2.0 Behavioral Fixes: Still worth reading if you are coming from 3.1.x — that release corrected long-standing bugs whose output or validation changes for existing code.
UrlEncoder.encodeis now true RFC 3986 percent-encoding — useencodeFormDatafor the previousapplication/x-www-form-urlencodedbehavior.Url/Uri/Urn/GeoUristring output is now well-formed, andUrn/GeoUriconstruction now rejects invalid input. Critical arbitrary-precision fixes also land inBigInteger/BigDecimaldivision andBankersValue. See the changelog for the full list.v3.0.0 Breaking Change: All GeoJSON geometry types (e.g.,
Point,Polygon) have been renamed with aGeoprefix (e.g.,GeoPoint,GeoPolygon).Moneynow usesBigDecimalfor its internal value to support sub-minor units, andTelemetryhas moved to thequantitativepackage.
docs/knowledge/, work in Issues.| Category | Type | Standard / Compliance | Description |
|---|---|---|---|
| Financial | Money |
Banker's Rounding | Arbitrary-precision monetary values with BigDecimal storage. |
Currency |
ISO 4217 | Global currency definitions with scale factors. | |
| Localization | Locale |
BCP 47 | Universal language, region, and script identifiers. |
| Quantitative | Coordinates |
WGS84 | Geodetic latitude and longitude degrees. |
Distance |
SI (Meters) | Linear distance with accuracy support. | |
Speed |
SI (mps) | Rate of motion in meters per second. | |
Altitude |
SI (Meters) | Vertical distance above/below reference. | |
Azimuth |
Degrees | Compass bearing (0-360°). | |
Telemetry |
Unified Domain | Comprehensive model with coordinates, azimuth, speed, and altitude. | |
| Geospatial | GeoJson |
RFC 7946 |
GeoPoint, GeoFeature, and GeoFeatureCollection models. Coordinates.toGeoPoint() reorders to GeoJSON's longitude-first position. |
| SI Units | UnitOfMeasure |
SI / Imperial | Definitions for meters, liters, bytes, etc. |
MetricPrefix |
SI Prefixes | Scaling factors from Quetta to Quecto. |
|
| Identifiers | Url |
RFC 3986 | Uniform Resource Locators (Web). |
Urn |
RFC 8141 | Uniform Resource Names (Persistent IDs). | |
GeoUri |
RFC 5870 | Geographic 'geo' URI scheme. | |
| Mathematics | BigInteger |
Pure Kotlin | Arbitrary-precision integer math support. |
BigDecimal |
Pure Kotlin | Arbitrary-precision decimal math with rounding support. | |
| Utilities | BitSet |
Multiplatform | Space-efficient storage for bit-level flags. |
BankersValue |
Half-to-Even | Precision math with bias-free rounding rules. | |
| Control Flow | Outcome |
Sealed (KMP-safe) | Success-or-failure result Swift can read as data, unlike kotlin.Result. Reads like Result: getOrNull, getOrThrow, getOrElse, fold, map, recover. |
Locale.current or localeFor("fr-CH").Money(12.50, Currency.Usd) or Money(BigDecimal("1.23456"), Currency.Eur).Telemetry(coords, speed = 2.5.mps, azimuth = 90.degrees).100.meters or 5.kilometers.UnitOfMeasure.Litre.symbol ("L"), MetricPrefix.Kilo.Url("https://pkg.dev"), Urn("urn:uuid:...").GeoUri(45.5, -122.6) (RFC 5870).BigInteger("999999999999999999999999") or BigDecimal("123.456").BigDecimal("1.255").setScale(2, RoundingMode.HALF_EVEN) -> 1.26.runOutcome { parse(input) } returns Outcome.Success or Outcome.Failure; when over the two, or use fold, map, recover, getOrNull, getOrElse.kotlin.Result it is a sealed class, so Swift and JavaScript callers can read the failure as data. It otherwise reads as Result does — same members, same callback arguments — so it stands in for one where Result cannot cross the language boundary. See ADR-0004.GeoPoint(45.5, -122.6, 100.0).toGeoJson() (RFC 7946).Locale.displayName from the bundled resource data is always English. To show a locale's name in the end user's own language:
localeFor("bn")?.localizedDisplayName() // "bengali" for a French user, "ベンガル語" for a Japanese userRather than bundling a full translation matrix (~90 × 90 names) into every app, this delegates to the CLDR data each platform already ships — java.util.Locale (JVM/Android), NSLocale (Apple), Intl.DisplayNames (JS/Wasm). Bundled resource files aren't viable everywhere: browsers can't read files synchronously, and klibs can't deliver resources into an iOS app bundle.
This is the one place the library does not promise every platform the same answer, and the reason is measured rather than assumed. Of the locale names that the JVM, Apple and a browser can all produce, they disagree on 40.8% between Apple and the browser, and 9.6% between the JVM and Apple. Not near-misses: nl-BE is "Nederlands (België)" on two platforms and "Vlaams" on the third. That is CLDR version skew between operating systems and browsers, and delegation cannot remove it.
So the goal here is completeness — every locale gets a name in the reader's language — and not consistency, which would mean bundling the full translation matrix into every artifact. That costs roughly 107 KB gzipped on web and mobile to fix a problem those platforms do not have, since they already ship the data. Elsewhere in this library identical behaviour everywhere is the point; for display names the price was judged wrong, deliberately.
What that means for you:
localizedDisplayName never returns null — the worst case is the English displayName. That fallback is silent, so use localizedDisplayNameOrNull when showing the wrong language matters more than showing nothing: it returns null where the platform has no data, instead of quietly handing back English.Intl.DisplayNames, available since roughly 2020; older environments fall back to English.See ADR-0005 for the decision and the measurements, ADR-0003 for the original delegation rationale, and issue #20 for gap-filling work.
To ensure mathematical precision and behavior consistency, this library employs rigorous Differential Parity Testing against standard baseline libraries:
java.math.BigInteger and java.math.BigDecimal).BigInteger and BigDecimal are benchmarked against the JDK types. Figures are relative to java.math, so 1.0x is parity and below 1.0x is faster than the JDK.
| Values of 40 digits or more | vs java.math
|
|---|---|
| Division, remainder, modulo | 0.5x – 0.8x |
| Bitwise operations | 0.5x – 0.8x |
Text conversion (toString, parsing) |
0.8x – 3.4x |
Scaling, rounding, BigDecimal division |
0.9x – 1.2x |
| Add, subtract, multiply, shifts | 1.2x – 2.9x |
Small values are the exception, and deliberately so: the JDK keeps any BigDecimal under 19 digits in a long, so it answers toDouble on one by reading a field. There is no equivalent fast path here, and short-value operations run from several times to two orders of magnitude slower as a result. Two other costs are worth knowing: modPow is around 5x, lacking Montgomery reduction, and comparing two BigDecimal values of different scale is quick only when their magnitudes differ enough to settle it without aligning them — equal scales, the common case, beat the JDK.
Run them yourself with ./gradlew :benchmarks:benchmark. Ratios hold reasonably across machines; absolute throughput does not, so the benchmarks report both.
A Kotlin Multiplatform library of strongly-typed, shareable data types — money and currency, locales, coordinates and telemetry, GeoJSON geometry, RFC-compliant identifiers, and arbitrary-precision math. It exists because KMP projects kept redefining the same types incompatibly; these are the shared ones, with no platform-specific API leaking through.
Targets: Android, JVM, iOS, JS, Wasm and Linux.
Published to Maven Central as io.github.aughtone:types.
// build.gradle.kts
implementation("io.github.aughtone:types:4.1.0")Or through a version catalog:
# gradle/libs.versions.toml
[versions]
aughtone-types = "4.1.0"
[libraries]
aughtone-types = { module = "io.github.aughtone:types", version.ref = "aughtone-types" }// build.gradle.kts
implementation(libs.aughtone.types)[!IMPORTANT] v4.1.0 Breaking Changes:
Outcomeis aligned withkotlin.Result.dataOrNull,dataOrThrowanddataOrElseare renamedgetOrNull,getOrThrowandgetOrElse, with no deprecated aliases, and the callbacks handed toonFailure,fold,recoverandgetOrElsenow receive theThrowablerather than theOutcome.Failure. Check the callbacks first: they still compile. A body readingit.messagekeeps building but now gets the nullableThrowable.messageinstead of the non-nullOutcome.Failure.message, so an exception with no message renders the textnull. Useit.message ?: it.toString().isSuccess,isFailure,exceptionOrNull()andgetOrDefaultare new.Currencynow compares by its ISO 4217 code alone, so aSetor map keyed byCurrencymay hold one entry where it held two, andMoneyamounts from different sources sort instead of throwing.Gigabyteis nowGiBandGigabitGbit, keepingGBandGbas alternatives, so rendering.symbolchanges for those two. Also:localizedDisplayNamereturned English for every locale on the JS and Wasm targets in 3.4.0 and 4.0.0 and now returns real translations, so anything rendering locale names on web changes output.v4.0.0 Breaking Changes: A breaking release.
Outcome.ErrorandLocale.toLanguageTag()are removed;Moneyequality is now numeric so5.1equals5.10;DistanceandSpeedthrow instead of clamping to zero;GeoBoundingBoxleaves the geometry hierarchy;GeoFeature.propertiesbecomesJsonObject; invalid GeoJSON is now rejected; andUnitOfMeasure.findFirstrefuses ambiguous symbols rather than guessing. Several change behaviour without a compile error — see the changelog before upgrading.v3.4.0
Outcome.Errorrenamed: The failure case ofOutcomeis nowOutcome.Failure, and the factory isOutcome.failure(...). The old names shipped as deprecated aliases in 3.4.0 and are removed in 4.0.0.Outcome$Errorno longer exists as a class, so upgrading from 3.3.0 needs a clean and rebuild rather than a code change.v3.3.0 Locale Display Names: Eleven locale
displayNamevalues are corrected — renamed countries (Czechia,North Macedonia,Türkiye), incomplete or abbreviated country names, and dated language exonyms (Farsi→Persian,Azeri→Azerbaijani). No API changed, but snapshot tests and cached UI strings holding the old names will need updating. See the changelog for the full table.v3.2.0 Behavioral Fixes: Still worth reading if you are coming from 3.1.x — that release corrected long-standing bugs whose output or validation changes for existing code.
UrlEncoder.encodeis now true RFC 3986 percent-encoding — useencodeFormDatafor the previousapplication/x-www-form-urlencodedbehavior.Url/Uri/Urn/GeoUristring output is now well-formed, andUrn/GeoUriconstruction now rejects invalid input. Critical arbitrary-precision fixes also land inBigInteger/BigDecimaldivision andBankersValue. See the changelog for the full list.v3.0.0 Breaking Change: All GeoJSON geometry types (e.g.,
Point,Polygon) have been renamed with aGeoprefix (e.g.,GeoPoint,GeoPolygon).Moneynow usesBigDecimalfor its internal value to support sub-minor units, andTelemetryhas moved to thequantitativepackage.
docs/knowledge/, work in Issues.| Category | Type | Standard / Compliance | Description |
|---|---|---|---|
| Financial | Money |
Banker's Rounding | Arbitrary-precision monetary values with BigDecimal storage. |
Currency |
ISO 4217 | Global currency definitions with scale factors. | |
| Localization | Locale |
BCP 47 | Universal language, region, and script identifiers. |
| Quantitative | Coordinates |
WGS84 | Geodetic latitude and longitude degrees. |
Distance |
SI (Meters) | Linear distance with accuracy support. | |
Speed |
SI (mps) | Rate of motion in meters per second. | |
Altitude |
SI (Meters) | Vertical distance above/below reference. | |
Azimuth |
Degrees | Compass bearing (0-360°). | |
Telemetry |
Unified Domain | Comprehensive model with coordinates, azimuth, speed, and altitude. | |
| Geospatial | GeoJson |
RFC 7946 |
GeoPoint, GeoFeature, and GeoFeatureCollection models. Coordinates.toGeoPoint() reorders to GeoJSON's longitude-first position. |
| SI Units | UnitOfMeasure |
SI / Imperial | Definitions for meters, liters, bytes, etc. |
MetricPrefix |
SI Prefixes | Scaling factors from Quetta to Quecto. |
|
| Identifiers | Url |
RFC 3986 | Uniform Resource Locators (Web). |
Urn |
RFC 8141 | Uniform Resource Names (Persistent IDs). | |
GeoUri |
RFC 5870 | Geographic 'geo' URI scheme. | |
| Mathematics | BigInteger |
Pure Kotlin | Arbitrary-precision integer math support. |
BigDecimal |
Pure Kotlin | Arbitrary-precision decimal math with rounding support. | |
| Utilities | BitSet |
Multiplatform | Space-efficient storage for bit-level flags. |
BankersValue |
Half-to-Even | Precision math with bias-free rounding rules. | |
| Control Flow | Outcome |
Sealed (KMP-safe) | Success-or-failure result Swift can read as data, unlike kotlin.Result. Reads like Result: getOrNull, getOrThrow, getOrElse, fold, map, recover. |
Locale.current or localeFor("fr-CH").Money(12.50, Currency.Usd) or Money(BigDecimal("1.23456"), Currency.Eur).Telemetry(coords, speed = 2.5.mps, azimuth = 90.degrees).100.meters or 5.kilometers.UnitOfMeasure.Litre.symbol ("L"), MetricPrefix.Kilo.Url("https://pkg.dev"), Urn("urn:uuid:...").GeoUri(45.5, -122.6) (RFC 5870).BigInteger("999999999999999999999999") or BigDecimal("123.456").BigDecimal("1.255").setScale(2, RoundingMode.HALF_EVEN) -> 1.26.runOutcome { parse(input) } returns Outcome.Success or Outcome.Failure; when over the two, or use fold, map, recover, getOrNull, getOrElse.kotlin.Result it is a sealed class, so Swift and JavaScript callers can read the failure as data. It otherwise reads as Result does — same members, same callback arguments — so it stands in for one where Result cannot cross the language boundary. See ADR-0004.GeoPoint(45.5, -122.6, 100.0).toGeoJson() (RFC 7946).Locale.displayName from the bundled resource data is always English. To show a locale's name in the end user's own language:
localeFor("bn")?.localizedDisplayName() // "bengali" for a French user, "ベンガル語" for a Japanese userRather than bundling a full translation matrix (~90 × 90 names) into every app, this delegates to the CLDR data each platform already ships — java.util.Locale (JVM/Android), NSLocale (Apple), Intl.DisplayNames (JS/Wasm). Bundled resource files aren't viable everywhere: browsers can't read files synchronously, and klibs can't deliver resources into an iOS app bundle.
This is the one place the library does not promise every platform the same answer, and the reason is measured rather than assumed. Of the locale names that the JVM, Apple and a browser can all produce, they disagree on 40.8% between Apple and the browser, and 9.6% between the JVM and Apple. Not near-misses: nl-BE is "Nederlands (België)" on two platforms and "Vlaams" on the third. That is CLDR version skew between operating systems and browsers, and delegation cannot remove it.
So the goal here is completeness — every locale gets a name in the reader's language — and not consistency, which would mean bundling the full translation matrix into every artifact. That costs roughly 107 KB gzipped on web and mobile to fix a problem those platforms do not have, since they already ship the data. Elsewhere in this library identical behaviour everywhere is the point; for display names the price was judged wrong, deliberately.
What that means for you:
localizedDisplayName never returns null — the worst case is the English displayName. That fallback is silent, so use localizedDisplayNameOrNull when showing the wrong language matters more than showing nothing: it returns null where the platform has no data, instead of quietly handing back English.Intl.DisplayNames, available since roughly 2020; older environments fall back to English.See ADR-0005 for the decision and the measurements, ADR-0003 for the original delegation rationale, and issue #20 for gap-filling work.
To ensure mathematical precision and behavior consistency, this library employs rigorous Differential Parity Testing against standard baseline libraries:
java.math.BigInteger and java.math.BigDecimal).BigInteger and BigDecimal are benchmarked against the JDK types. Figures are relative to java.math, so 1.0x is parity and below 1.0x is faster than the JDK.
| Values of 40 digits or more | vs java.math
|
|---|---|
| Division, remainder, modulo | 0.5x – 0.8x |
| Bitwise operations | 0.5x – 0.8x |
Text conversion (toString, parsing) |
0.8x – 3.4x |
Scaling, rounding, BigDecimal division |
0.9x – 1.2x |
| Add, subtract, multiply, shifts | 1.2x – 2.9x |
Small values are the exception, and deliberately so: the JDK keeps any BigDecimal under 19 digits in a long, so it answers toDouble on one by reading a field. There is no equivalent fast path here, and short-value operations run from several times to two orders of magnitude slower as a result. Two other costs are worth knowing: modPow is around 5x, lacking Montgomery reduction, and comparing two BigDecimal values of different scale is quick only when their magnitudes differ enough to settle it without aligning them — equal scales, the common case, beat the JDK.
Run them yourself with ./gradlew :benchmarks:benchmark. Ratios hold reasonably across machines; absolute throughput does not, so the benchmarks report both.