An experimental i18n library to generate "time ago" text for applications.
0

Configure Feed

Select the types of activity you want to include in your feed.

OCaml 90.8%
Dune 3.7%
Other 5.5%
1 1 0

Clone this repository

https://tangled.org/gdiazlo.tngl.sh/since https://tangled.org/did:plc:zorcgir63bkehpr3lplk6bd6
git@tangled.org:gdiazlo.tngl.sh/since git@tangled.org:did:plc:zorcgir63bkehpr3lplk6bd6

For self-hosted knots, clone URLs may differ based on your setup.



README.md

since#

since formats Unix timestamps as localized relative-time messages.

Examples include:

  • "a few seconds ago"
  • "5 minutes ago"
  • "in 2 weeks"
  • "hace 3 horas"

Translations are compiled from lib/locales.tsv with ocsigen-i18n.

Install#

opam install since

For local development:

dune build
dune runtest

Usage#

let now = Unix.gettimeofday ()

let five_minutes_ago = now -. (5. *. 60.)

let message = Since.format ~now five_minutes_ago
(* "5 minutes ago" *)

Pass a locale explicitly:

let message = Since.format ~locale:Since.es ~now five_minutes_ago
(* "hace 5 minutos" *)

Format future timestamps:

let in_two_weeks = now +. (14. *. 24. *. 60. *. 60.)

let message = Since.format ~locale:Since.en ~now in_two_weeks
(* "in 2 weeks" *)

Look up a locale from a string:

let locale =
  match Since.locale_of_string "pt-BR" with
  | Some locale -> locale
  | None -> Since.en

let message = Since.format ~locale ~now five_minutes_ago

Inspect the bucket without rendering:

let direction, grain = Since.classify (now -. five_minutes_ago)
(* direction = Since.Past, grain = Since.Minutes 5 *)

Use calendar for date arithmetic, then pass the Unix timestamp to since:

let now = CalendarLib.Calendar.now ()

let next_month =
  CalendarLib.Calendar.add now (CalendarLib.Calendar.Period.month 1)

let message =
  Since.format ~locale:Since.en
    ~now:(CalendarLib.Calendar.to_unixfloat now)
    (CalendarLib.Calendar.to_unixfloat next_month)
(* "in a month" *)

CLI#

The installed since command uses climate for argument parsing, calendar for date parsing, and this library for rendering.

since 2024-10-11
# 2 years ago

since 24/10/2023 --locale es
# hace 3 años

since 2025-10-21T10:45Z --locale en
# 7 months ago

Supported CLI inputs include Unix timestamp seconds, YYYY-MM-DD, DD/MM/YYYY, YYYY-MM-DD HH:MM:SS, RFC3339-like values such as 2025-10-21T10:45Z, HH:MM:SS, 3:04PM, stamp values such as Jan 02 15:04:05, RubyDate, RFC822/RFC822Z, and RFC1123/RFC1123Z with numeric zones.

Timezone-name layouts such as MST are not parsed by the CLI.

Locales#

Built-in locale values include:

Since.en
Since.es
Since.pt
Since.fr
Since.de
Since.it
Since.nl
Since.pl
Since.ru
Since.ro
Since.sv
Since.nb
Since.da
Since.fi
Since.el
Since.cs
Since.hu
Since.ja
Since.zh_cn
Since.zh_tw

Use Since.all_locales to iterate over them.

API#

val format : ?locale:Since.locale -> now:float -> float -> string
val classify : float -> Since.direction * Since.grain
val locale_of_string : string -> Since.locale option
val locale_code : Since.locale -> string

Both now and the timestamp passed to format are Unix epoch seconds.