Skip to content

Releases: woodie/humane-ruby

v0.5.0

Choose a tag to compare

@woodie woodie released this 10 Jul 20:19

humane v0.5.0

TimeFormatter now matches ActionView's bucket table exactly (through "1 day")

Humane::TimeFormatter.new(approximate: true).string(at: t - (44 * 60 + 30), relative_to: t)
# => "about 1 hour ago"

#string used to divide raw seconds independently per unit (seconds -> minutes ->
hours -> days), which let rounding carry across a bucket boundary on its own --
59:59:59 rounded to "60 minutes ago" instead of "1 hour ago". It now computes
distance_in_minutes (seconds/60, rounded once) and buckets off that, the same way
ActionView's distance_of_time_in_words does. That's what produces its specific,
non-obvious cutoffs: the "about 1 hour" bucket starts at 44 minutes 30 seconds (not
60:00), and "about 2 hours" starts at 89:30, not 90:00.

Two output changes, both additive to the API (no new keyword args):

  • include_seconds: false's collapse cutoff moved from 60 seconds to 30 seconds,
    matching the first row of ActionView's table. "45 seconds ago" is now
    "1 minute ago" instead of "less than a minute ago".
  • approximate narrowed from "about" on any bucket >= 1 hour to exactly the
    hour-scale buckets (1 hour, and 2..24 hours). ActionView's own table has no "about
    1 day", so neither does this anymore -- approximate: true on a 30-hour-old
    timestamp is now "1 day ago", not "about 1 day ago".

Scoped through the "1 day" row of ActionView's table -- week/month/year buckets are
out of scope, matching this library's "narrow, not full-featured" intent. See
humane-ruby issue #1 for the
source table this ports, and docs/COMMENTS.md for the full boundary-by-boundary
rationale.

humane (Go) and humane-swift picked up the identical table change in the same
session -- humane-swift's TimeFormatter moved off RelativeDateTimeFormatter as
its bucketing source in the process, since Foundation's own rounding doesn't hit
these specific cutoffs.

Upgrading

No API changes. Two output changes to check for if you depend on exact wording
below 90 seconds or on approximate's day-scale output -- see above.
scandalous's time_ago helper (the only known approximate: true consumer) is
unaffected: its documented usage is hour-scale ("about 14 hours ago"), not
day-scale.

v0.4.0

Choose a tag to compare

@woodie woodie released this 10 Jul 10:19

humane v0.4.0

Humane::TimeFormatter gains approximate: (additive, not breaking)

Humane::TimeFormatter.new(approximate: true).string(at: t - 15 * 3600, relative_to: t)
# => "about 15 hours ago"

Defaults to false, so existing callers see no change. When true, buckets of an
hour or larger get an "about"/"in about" prefix -- the way ActionView's
distance_of_time_in_words does past its own "about" threshold -- signaling that the
bucket is a rounded value. Sub-hour buckets are untouched either way.

Ported from humane-swift's identically-named, identically-defaulted option
(v0.1.0). The intended use is a static render that can't refresh itself -- a web
response, a cached page -- where a precise-looking "15 hours ago" is more confident
than the underlying value actually is. Contexts that can re-render on demand (a
native app, a live-polling frontend) have no reason to reach for it; the plain,
Foundation-matching default is still the right choice there.

Ruby's version turned out simpler to implement than Swift's: string builds the bare
quantity phrase ("15 hours") before wrapping it in "X ago"/"in X", so prefixing
"about " at that point composes correctly for both directions automatically.
Swift's implementation has to post-process RelativeDateTimeFormatter's
already-complete phrase instead, since Foundation hands back the whole sentence at
once.

Upgrading

Nothing required -- approximate defaults false, matching every existing caller's
current behavior exactly.

scandalous's time_ago helper is the first real consumer, opting in with
approximate: true since its listing page is a static, non-refreshing render -- the
exact case this option exists for.

v0.3.0

Choose a tag to compare

@woodie woodie released this 10 Jul 09:57

humane v0.3.0

collapse_minute renamed to include_seconds (breaking)

Humane::TimeFormatter.new's option is now include_seconds: (default false),
replacing collapse_minute: (default true).

This isn't just a rename -- the polarity inverts too. collapse_minute: true (the
old default) suppressed seconds below a minute; include_seconds: false (the new
default) does the exact same thing, so the default behavior is unchanged. But
collapse_minute: false and include_seconds: true are the two ends of that same
inversion, not equivalents of each other.

The rename borrows ActionView's own name for this concept -- distance_of_time_in_words
has an include_seconds: option that defaults false for the same reason this one
does. Reusing the name isn't just convenience: the defaults independently agree
(Rails defaults to suppressing seconds too), which is a stronger signal this is the
right name than either language picking its own term would have been.

collapse_minute read ambiguously once humane-swift's approximate option
(ActionView-inspired "about"/"in about" prefixing on hour-plus buckets) entered the
picture -- "collapse" suggested the same kind of rounding-language operation
approximate performs, when this option has only ever been a granularity floor
below a minute. include_seconds doesn't compete for that word.

Upgrading

  • No explicit collapse_minute:/include_seconds: argument: no change needed,
    behavior is identical.
  • collapse_minute: false: change to include_seconds: true.
  • collapse_minute: true: remove the argument entirely (now the default), or change
    to include_seconds: false if being explicit is preferred.

scandalous is the one known consumer and doesn't pass this option explicitly, so no
follow-up is required there -- worth double-checking before bumping its humane pin
past "~> 0.1", since that pin also hasn't picked up the v0.2.0 wording change yet
(see that release's own upgrade note).

v0.2.0

Choose a tag to compare

@woodie woodie released this 09 Jul 00:57

humane v0.2.0

Humane::TimeFormatter wording change (breaking)

Humane::TimeFormatter#string now renders future times as "in X"
("in 3 minutes", "in 15 hours", "in less than a minute"), matching
Swift's RelativeDateTimeFormatter output exactly, instead of v0.1.0's
symmetric "X from now" wording.

v0.1.0 documented the symmetric wording as "a deliberate departure" from
RelativeDateTimeFormatter. On reflection that departure didn't earn its
keep: this gem exists to match what Swift/Finder-adjacent APIs actually do --
the same bar Humane::SizeFormatter was held to -- and
RelativeDateTimeFormatter's real output is asymmetric. "X ago" past-side
wording is unchanged.

Upgrading

Any code matching on the literal string "X from now" needs to switch to
"in X". scandalous is the one known consumer; it needs a follow-up update
before bumping its humane pin past "~> 0.1".

v0.1.0

Choose a tag to compare

@woodie woodie released this 08 Jul 21:12

humane v0.1.0

Initial release

humane formats file sizes and relative dates the way macOS Finder does, modeled on Swift's ByteCountFormatter and RelativeDateTimeFormatter: small, configurable formatter classes with a single string method, rather than helper methods mixed into a view. Extracted from real usage in scandalous and its Go counterpart lambada, which were carrying hand-rolled, drifting copies of the same logic.

Humane::SizeFormatter

string(from_byte_count:) formats byte counts with 1000-based math and capitalized unit labels ("80 KB", "1.5 MB"), matching Finder's own file-size display — not SI's lowercase "kB", and not the 1024-based math Rails' number_to_human_size uses under the same "KB" label.

Humane::TimeFormatter

string(at:, relative_to:) formats one time relative to another as "X ago" / "X from now", with no "about" prefix on the hour bucket. collapse_minute: (defaults to true) buckets anything under a minute as "less than a minute ago" / "less than a minute from now".

This first release is scoped to just these two formatters. See the README for usage and docs/COMMENTS.md for design rationale, including why at:/relative_to: was chosen over Swift's for:/relativeTo: labels.