Skip to content
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Main features:

- Compute distance of dates, in words: `1 week and 2 days ago`, `5 months ago`
- Future times: `in 1 year`
- Alternative formats: short (`2y and 1mo ago`) and array (`['2 years', '1 month']`)
- Alternative formats: short (`2y and 1mo ago`), array (`['2 years', '1 month']`), and hash (`{years: 2, months: 1}`)
- Out of the box support for `Jekyll` projects, available as a Liquid Filter and as a Liquid Tag
- Localization: `hace 3 semanas`
- Level of detail customization
Expand Down Expand Up @@ -140,6 +140,26 @@ Use `:array` style for structured data:
=> ["5 months", "1 week"]
```

Use `:hash` style for structured hash data:

```ruby
>> timeago(Date.today.prev_day(365), style: :hash)
=> {:years=>1}
>> timeago(Date.today.prev_day(160), style: :hash)
=> {:years=>0, :months=>5, :weeks=>1}
>> timeago(Date.today.prev_day(500), style: :hash)
=> {:years=>1, :months=>4}
```

Hash keys are automatically localized based on the locale:

```ruby
>> timeago(Date.today.prev_day(500), style: :hash, locale: :es)
=> {:años=>1, :meses=>4}
>> timeago(Date.today.prev_day(160), style: :hash, locale: :fr)
=> {:années=>0, :mois=>5, :semaines=>1}
```

#### `only`

Use the `only` option to accumulate all time into a single unit. Supported values are `:years`, `:months`, `:weeks`, and `:days`:
Expand All @@ -159,6 +179,15 @@ Use the `only` option to accumulate all time into a single unit. Supported value
=> "hace 12 meses"
```

The `only` option also works with hash style and provides localized keys:

```ruby
>> timeago(Date.today.prev_day(365), only: :months, style: :hash)
=> {:months=>12}
>> timeago(Date.today.prev_day(365), only: :months, style: :hash, locale: :es)
=> {:meses=>12}
```

## Localization

By default, `jekyll-timego` already provides translations for some languages. You can check the list [here](lib/locales/). However, you are able to provide your own translations, or even override the originals, easily.
Expand Down Expand Up @@ -188,6 +217,10 @@ il y a environ 2 années et 6 mois
2y ago
> timeago 2016-1-1 2018-1-1 -l es -s short
hace 2a y 1d
> timeago 2016-1-1 2018-1-1 --style hash
{:years=>2, :days=>1}
> timeago 2016-1-1 2018-1-1 --style hash --locale es
{:años=>2, :días=>1}
> timeago 2016-1-1 2018-1-1 --only weeks
104 weeks ago
> timeago 2016-1-1 2018-1-1 --only months -s short
Expand Down
2 changes: 1 addition & 1 deletion bin/timeago
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ parser = OptionParser.new do |opts|
options[:locale] = locale
end

opts.on("-s", "--style STYLE", "Uses the provided style (short, array)") do |style|
opts.on("-s", "--style STYLE", "Uses the provided style (short, array, hash)") do |style|
options[:style] = style
end

Expand Down
53 changes: 42 additions & 11 deletions lib/jekyll-timeago/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module Core
DEFAULT_THRESHOLD = 0

# Available styles
STYLES = %w(short array)
STYLES = %w(short array hash)

# Available "only" options
ONLY_OPTIONS = %w(years months weeks days)
Expand Down Expand Up @@ -65,15 +65,26 @@ def validate_only(only)
def time_ago_to_now
days_passed = (@to - @from).to_i

return t(:today) if days_passed == 0
return t(:yesterday) if days_passed == 1
return t(:tomorrow) if days_passed == -1
# Handle special cases for hash style
if @style == "hash"
case days_passed
when 0 then return { localized_unit_name(:days) => 0 }
when 1 then return { localized_unit_name(:days) => 1 }
when -1 then return { localized_unit_name(:days) => 1 }
end
else
return t(:today) if days_passed == 0
return t(:yesterday) if days_passed == 1
return t(:tomorrow) if days_passed == -1
end
Comment thread
markets marked this conversation as resolved.
Outdated

past_or_future = @from < @to ? :past : :future
slots = build_time_ago_slots(days_passed.abs)

if @style == "array"
slots
elsif @style == "hash"
build_time_ago_slots(days_passed.abs, format_as_hash: true)
Comment thread
markets marked this conversation as resolved.
Outdated
else
t(past_or_future, date_range: to_sentence(slots))
end
Expand All @@ -92,26 +103,46 @@ def translate_unit(unit, count)
end
end

# Builds time ranges with natural unit conversions: ['1 month', '5 days']
def build_time_ago_slots(days_passed)
# Get localized unit name for hash keys (always plural form)
def localized_unit_name(unit)
# Extract the unit name from the plural form translation
translated = t(unit, count: 2)
# Remove any count prefix (e.g. "2 años" -> "años")
translated.gsub(/^\d+\s+/, '').to_sym
end

# Builds time ranges with natural unit conversions: ['1 month', '5 days'] or {:months => 1, :days => 5}
def build_time_ago_slots(days_passed, format_as_hash: false)
# If "only" option is specified, calculate total time in that unit
return build_only_slots(days_passed) if @only
return build_only_slots(days_passed, format_as_hash: format_as_hash) if @only

# Calculate components with natural unit conversions
components = calculate_natural_components(days_passed)

# Select components based on depth and threshold
selected = select_components(components, days_passed)

# Convert to translated strings
selected.map { |unit, count| translate_unit(unit, count) }
# Format output based on requested format
if format_as_hash
result = {}
selected.each { |unit, count| result[localized_unit_name(unit)] = count }
result
else
# Convert to translated strings
selected.map { |unit, count| translate_unit(unit, count) }
end
end

# Build time slots when "only" option is specified
def build_only_slots(days_passed)
def build_only_slots(days_passed, format_as_hash: false)
unit = @only.to_sym
count = calculate_total_in_unit(days_passed, unit)
[translate_unit(unit, count)]

if format_as_hash
{ localized_unit_name(unit) => count }
else
[translate_unit(unit, count)]
end
end

# Calculate total time in specified unit
Expand Down
58 changes: 58 additions & 0 deletions spec/jekyll-timeago_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@

it 'allows threshold configuration' do
expect(timeago(sample_date.prev_day(366), sample_date, threshold: 0.1)).to eq('1 year ago')
expect(timeago(sample_date.prev_day(366), sample_date, threshold: 0.1, style: :hash)).to eq({years: 1})
end

it 'applies rounding rules for natural language' do
Expand Down Expand Up @@ -131,6 +132,46 @@
expect(timeago(sample_date.prev_day(160), sample_date, style: :array, locale: :es)).to eq(['5 meses', '1 semana'])
end

it 'allows hash style formatting' do
expect(timeago(sample_date.prev_day(365), sample_date, style: :hash)).to eq({years: 1})
expect(timeago(sample_date.prev_day(365), sample_date, "style" => "hash")).to eq({years: 1})
expect(timeago(sample_date.prev_day(160), sample_date, style: :hash)).to eq({months: 5, weeks: 1})
expect(timeago(sample_date.prev_day(500), sample_date, style: :hash)).to eq({years: 1, months: 4})
expect(timeago(sample_date.prev_day(10), sample_date, style: :hash)).to eq({weeks: 1, days: 3})
end

it 'allows hash style with localized keys' do
expect(timeago(sample_date.prev_day(365), sample_date, style: :hash, locale: :es)).to eq({años: 1})
expect(timeago(sample_date.prev_day(160), sample_date, style: :hash, locale: :es)).to eq({meses: 5, semanas: 1})
expect(timeago(sample_date.prev_day(500), sample_date, style: :hash, locale: :fr)).to eq({années: 1, mois: 4})
expect(timeago(sample_date.prev_day(10), sample_date, style: :hash, locale: :fr)).to eq({semaines: 1, jours: 3})
end

it 'allows hash style with special cases' do
expect(timeago(sample_date, sample_date, style: :hash)).to eq({days: 0})
expect(timeago(sample_date.prev_day, sample_date, style: :hash)).to eq({days: 1})
expect(timeago(sample_date.next_day, sample_date, style: :hash)).to eq({days: 1})
end

it 'allows hash style with special cases and localized keys' do
expect(timeago(sample_date, sample_date, style: :hash, locale: :es)).to eq({días: 0})
expect(timeago(sample_date.prev_day, sample_date, style: :hash, locale: :es)).to eq({días: 1})
expect(timeago(sample_date.next_day, sample_date, style: :hash, locale: :fr)).to eq({jours: 1})
end

it 'allows hash style with future times' do
expect(timeago(sample_date.next_day(7), sample_date, style: :hash)).to eq({weeks: 1})
expect(timeago(sample_date.next_day(365), sample_date, style: :hash)).to eq({years: 1})
expect(timeago(sample_date.next_day(1000), sample_date, style: :hash)).to eq({years: 2, months: 9})
end

it 'allows hash style with depth control' do
expect(timeago(sample_date.prev_day(500), sample_date, style: :hash, depth: 1)).to eq({years: 1})
expect(timeago(sample_date.prev_day(500), sample_date, style: :hash, depth: 2)).to eq({years: 1, months: 4})
expect(timeago(sample_date.prev_day(500), sample_date, style: :hash, depth: 3)).to eq({years: 1, months: 4, weeks: 2})
expect(timeago(sample_date.prev_day(500), sample_date, style: :hash, depth: 4)).to eq({years: 1, months: 4, weeks: 2, days: 1})
end

it 'allows "only" option to accumulate time into single unit' do
# Test "only: :days"
expect(timeago(sample_date.prev_day(7), sample_date, only: :days)).to eq('7 days ago')
Expand Down Expand Up @@ -162,6 +203,16 @@
# Test with array style
expect(timeago(sample_date.prev_day(365), sample_date, only: :weeks, style: :array)).to eq(['52 weeks'])
expect(timeago(sample_date.prev_day(30), sample_date, only: :months, style: :array)).to eq(['1 month'])

# Test with hash style
expect(timeago(sample_date.prev_day(365), sample_date, only: :weeks, style: :hash)).to eq({weeks: 52})
expect(timeago(sample_date.prev_day(30), sample_date, only: :months, style: :hash)).to eq({months: 1})
expect(timeago(sample_date.prev_day(365), sample_date, only: :days, style: :hash)).to eq({days: 365})
expect(timeago(sample_date.prev_day(365), sample_date, only: :years, style: :hash)).to eq({years: 1})

# Test with hash style and localized keys
expect(timeago(sample_date.prev_day(365), sample_date, only: :weeks, style: :hash, locale: :es)).to eq({semanas: 52})
expect(timeago(sample_date.prev_day(30), sample_date, only: :months, style: :hash, locale: :fr)).to eq({mois: 1})
end

it 'allows "only" option with different locales' do
Expand Down Expand Up @@ -203,12 +254,19 @@
expect(`bin/timeago 2016-1-1 2018-1-1 --locale ru --style short`).to match("2г и 1д назад")
end

it 'with hash style' do
expect(`bin/timeago 2016-1-1 2018-1-1 --style hash`).to match("{:years=>2, :days=>1}")
expect(`bin/timeago 2016-1-1 2018-1-1 -s hash`).to match("{:years=>2, :days=>1}")
expect(`bin/timeago 2016-1-1 2016-2-1 --style hash`).to match("{:months=>1, :days=>1}")
end

it 'with only option' do
expect(`bin/timeago 2016-1-1 2018-1-1 --only weeks`).to match("104 weeks ago")
expect(`bin/timeago 2016-1-1 2018-1-1 -o months`).to match("24 months ago")
expect(`bin/timeago 2016-1-1 2016-2-1 --only days`).to match("31 days ago")
expect(`bin/timeago 2016-1-1 2018-1-1 -l fr --only months`).to match("il y a environ 24 mois")
expect(`bin/timeago 2016-1-1 2018-1-1 --only weeks -s short`).to match("104w ago")
expect(`bin/timeago 2016-1-1 2018-1-1 --only weeks --style hash`).to match("{:weeks=>104}")
end
end
end