A thoughtful time formatting library that reflects how we think about little ones.
"Newborn" · "2 weeks, 4 days" · "14 months" · "2 years, 3 months" · "4½ years"
Most age-formatting libraries just give you years, or maybe years and months. But parents of young children don't say "0 years" — they say "she's 6 weeks old" or "he just turned 14 months." parent-time formats ages the way parents and pediatricians actually talk, with human-friendly output in 26 languages, 71 locales, and three compactness levels.
| Language | CI | Package | Install | Docs |
|---|---|---|---|---|
| Ada | — | GPRbuild | README | |
| C | — | CMake (freestanding) | README | |
| C# | — | NuGet | README | |
| C++ | — | CMake (FetchContent) |
README | |
| Clojure | — | deps.edn | README | |
| COBOL | — | CMake (GnuCOBOL) | README | |
| Dart | — | pub.dev | README | |
| Elixir | — | Mix | README | |
| Elm | — | elm.json (application) | README | |
| Erlang | — | Rebar3 | README | |
| F# | — | NuGet | README | |
| Fortran | — | CMake (Modern Fortran 2008+) | README | |
| Gleam | — | Gleam build system | README | |
| Go | go get github.com/dnf/parent-time/src/go |
README | ||
| Groovy | — | Gradle | README | |
| Haskell | — | Cabal | README | |
| Java | Gradle/Maven | README | ||
| Kotlin | Gradle/Maven | README | ||
| Lua | — | Plain module (local) | README | |
| OCaml | — | Dune | README | |
| Perl | — | prove -Ilib |
README | |
| PHP | composer require dnf/parent-time |
README | ||
| Python | pip install parent-time |
README | ||
| Ruby | gem install parent_time |
README | ||
| Rust | parent-time = "0.1" |
README | ||
| Scala | — | Gradle | README | |
| Swift | — | SPM | README | |
| TypeScript | npm install parent-time |
README | ||
| Zig | — | Zig build system | README |
All implementations produce identical output for the same inputs. See each language's README for installation, usage, and API details.
A command-line tool for quick lookups, built on the C library:
just cli # build cli/build/parent-timeparent-time age 2020-06-15 # "5½ years"
parent-time age 2020-06-15 -l fr # "5 ans et demi"
parent-time age 2020-06-15 --compact # "5½y"
parent-time age 2020-06-15 -d 2021-01-01 # "6 months"
parent-time stage 2024-01-10 -l es -g f # "Niña pequeña"
parent-time locales # list all 71 locale codes| Flag | Short | Default | Description |
|---|---|---|---|
--locale <code> |
-l |
en |
Locale code (e.g. fr, ja) |
--as-of <date> |
-d |
today | Reference date (YYYY-MM-DD) |
--short |
Short format | ||
--compact |
Compact format | ||
--gender <m|f> |
-g |
Gender for stage labels |
Returns a human-readable age string with granularity that matches the child's developmental stage:
| Age range | Format | Examples |
|---|---|---|
| Born today | "Newborn" |
Newborn |
| 1–6 days | "N days" |
1 day, 5 days |
| 1 week – <3 months | "N weeks, N days" |
2 weeks, 3 weeks, 4 days |
| 3–23 months | "N months" |
3 months, 14 months |
| 24–35 months | "N years, N months" |
2 years, 2 years, 7 months |
| 3–6 years | "N years" / "N½ years" |
3 years, 3½ years, 5½ years |
| 7+ years | "N years" |
7 years, 45 years |
Calendar-month boundaries use day-of-month clamping: a child born on the 31st turns N months old on the last day of a shorter month (e.g., Feb 28/29, Apr 30).
Returns the developmental stage label (AAP/CDC consensus boundaries):
| Stage | Age range |
|---|---|
| Newborn | 0–1 months |
| Infant | 2–11 months |
| Toddler | 1–2 years |
| Child | 3–12 years |
| Teenager | 13–17 years |
| Adult | 18+ years |
71 built-in locales with CLDR-compliant plural rules. See the locale documentation for details on each language.
A core goal of this project is localization, not just internationalization. The library seeks to respect and align with the culture of each language — local customs about children may not always be reflected in sources such as CLDR. We actively welcome feedback and pull requests from native speakers of languages we support, or from native speakers who wish to add languages we do not currently support.
| Language | Code | Language | Code | Language | Code |
|---|---|---|---|---|---|
| Albanian | sq |
German | de |
Polish | pl |
| Amharic | am |
Greek | el |
Portuguese | pt |
| Arabic | ar |
Gujarati | gu |
Punjabi | pa |
| Azerbaijani | az |
Hebrew | he |
Romanian | ro |
| Basque | eu |
Hindi | hi |
Russian | ru |
| Bengali | bn |
Hungarian | hu |
Scottish Gaelic | gd |
| Bosnian | bs |
Indonesian | id |
Serbian | sr |
| Breton | br |
Irish | ga |
Sinhala | si |
| Bulgarian | bg |
Italian | it |
Slovak | sk |
| Burmese | my |
Japanese | ja |
Slovenian | sl |
| Catalan | ca |
Javanese | jv |
Spanish | es |
| Central Atlas Tamazight | tzm |
Kazakh | kk |
Swahili | sw |
| Chinese | zh |
Khmer | km |
Swedish | sv |
| Cornish | kw |
Korean | ko |
Tachelhit | shi |
| Croatian | hr |
Latvian | lv |
Tamil | ta |
| Czech | cs |
Lithuanian | lt |
Telugu | te |
| Danish | da |
Macedonian | mk |
Thai | th |
| Dutch | nl |
Malay | ms |
Turkish | tr |
| English | en |
Maltese | mt |
Ukrainian | uk |
| Estonian | et |
Manx | gv |
Urdu | ur |
| Filipino | fil |
Marathi | mr |
Uzbek | uz |
| Finnish | fi |
Nepali | ne |
Vietnamese | vi |
| French | fr |
Norwegian | nb |
Welsh | cy |
| Georgian | ka |
Persian | fa |
Each implementation supports three compactness levels:
| Level | Example output |
|---|---|
| Normal | "3 months", "2 years, 1 month" |
| Short | "3 mo", "2 yr, 1 mo" |
| Compact | "3m", "2y1m" |
Currently only the English locale defines distinct short/compact forms. Other locales return normal output for all three levels; you can add overrides via the locale's short and compact fields.
Every implementation supports user-defined locale objects. See your language's README for the locale interface definition.
- Zero dependencies. Pure date arithmetic — no runtime deps in any language.
- Deterministic. Every test uses explicit
(dob, asOf)pairs. No flaky tests from "today." - Calendar-month clamping. Born Jan 31 → turns 1 month on Feb 28 (or 29 in a leap year). This matches how hospitals and pediatricians count months.
- "Months" not "1 year" at 12 months. Parents say "he's 14 months" until about 2 years. The library follows this convention.
- ½ marker for 3–6 year olds. Parents say "she's four and a half." The ½ character (U+00BD) kicks in at 6+ months and drops off at age 7.
- "Newborn" at 0 days. Because nobody says "0 days old."
- Stage boundaries follow AAP/CDC. Newborn (<2 mo), Infant (2–11 mo), Toddler (1–2 yr), Child (3–12 yr), Teenager (13–17 yr), Adult (18+).
- Localization via locale objects. No framework, no dependency — just a plain data object with a pluralization callback. Supports any language's plural rules, number formatting, and word order. Culture-aware, not just translation-aware.
All implementations share a canonical set of test cases covering null handling, all six age tiers, month-boundary clamping (including February and leap years), born-on-31st progression, stage labels, compactness levels, and i18n locale support.
The test specification in config/tests.json is the single source of truth. Each implementation translates these cases into its native test framework. When adding a new test case, add it to the specification first, then implement it in all languages.
- Test cases — canonical test specification for all implementations
- Locale configuration — CLDR plural rules, per-language details
- Code generator — how locale files are generated from canonical JSON
- Code quality — linting, type checking, and static analysis configuration
You can develop and test all implementations either natively on macOS/Linux or inside a Docker container.
Requires only Docker. Builds a Linux container with all toolchains and runs the full test suite against your local checkout:
./.hecatron/tools/docker/test.sh # build image + run all tests
./.hecatron/tools/docker/test.sh bash # interactive shell inside the containerYour repo is bind-mounted into the container. Anonymous volumes keep platform-specific build artifacts isolated, so nothing on your host is modified.
Bootstrap scripts install all language toolchains and project dependencies in one step:
# macOS (uses Homebrew)
./.hecatron/tools/bootstrap/macos.sh
# Ubuntu / Debian
./.hecatron/tools/bootstrap/linux.shThe scripts are idempotent — safe to re-run if you need to pick up a missing tool later.
You can also install or uninstall individual languages:
just install go # single language
just install ts py rb # multiple (aliases work)
just install # all languages
just uninstall go # remove a languageA top-level justfile provides a single entry point for building, testing, and linting across all languages:
just test # run all test suites + codegen check
just test go # run one language's tests
just build # build all languages with a build step
just lint # run all available linters
just disable hs ada # skip languages from parallel runs
just enable --all # re-enable all disabled languages
just generate # regenerate locale files from canonical JSON
just check # verify generated files are up-to-date
just --list # see all available commandsLanguage shortcuts: ad, cb, ts, py, rb, rs, kt, gy, lu, ml, ft, c++, cs, fs, sw, hs, ex, erl, clj, el, gm, sc, zg are accepted alongside full names.
All implementations share identical test cases. If you add a test, add it in all directories. See each language's README for build and test commands, or use just test <lang> from the project root.