Skip to content
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ 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`
- Localization: `hace 3 semanas`, `3週間前`, `il y a environ 3 semaines`
- Level of detail customization
- CLI
- Approximate distance, with customizable threshold, ie: `366 days` becomes `1 year ago` instead of `1 year and 1 day ago`
- Approximate distance, with customizable threshold: `366 days` becomes `1 year ago` instead of `1 year and 1 day ago`

In fact, `jekyll-timeago` started as an extension for the [Liquid](https://github.com/Shopify/liquid) template engine, to be used in Jekyll backed sites. But actually, you can use it easily on any Ruby project and even as a tool from the [terminal](#cli)!

Expand Down Expand Up @@ -138,6 +138,19 @@ Use `:array` style for structured data:
=> ["1 year"]
>> timeago(Date.today.prev_day(160), style: :array)
=> ["5 months", "1 week"]
>> timeago(Date.today.prev_day(160), style: :array, locale: :es)
=> ["5 meses", "1 semana"]
```

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)
=> {:months=>5, :weeks=>1}
>> timeago(Date.today.prev_day(160), style: :hash, locale: :es)
=> {:meses=>5, :semanas=>1}
```

#### `only`
Expand All @@ -157,6 +170,8 @@ Use the `only` option to accumulate all time into a single unit. Supported value
=> "52w ago"
>> timeago(Date.today.prev_day(365), only: :months, locale: :es)
=> "hace 12 meses"
>> timeago(Date.today.prev_day(365), only: :months, style: :hash)
=> {:months=>12}
```

## Localization
Expand Down Expand Up @@ -188,6 +203,8 @@ 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 --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
47 changes: 38 additions & 9 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,14 +65,24 @@ 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
if @style == "hash"
return { localized_unit_name(:days) => 0 } if days_passed == 0
return { localized_unit_name(:days) => 1 } if days_passed == 1
return { localized_unit_name(:days) => -1 } if days_passed == -1
elsif @style == "array"
return [t(:today)] if days_passed == 0
return [t(:yesterday)] if days_passed == 1
return [t(:tomorrow)] if days_passed == -1
else
return t(:today) if days_passed == 0
return t(:yesterday) if days_passed == 1
return t(:tomorrow) if days_passed == -1
end

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

if @style == "array"
if @style == "array" || @style == "hash"
slots
else
t(past_or_future, date_range: to_sentence(slots))
Expand All @@ -92,7 +102,15 @@ def translate_unit(unit, count)
end
end

# Builds time ranges with natural unit conversions: ['1 month', '5 days']
# 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)
# If "only" option is specified, calculate total time in that unit
return build_only_slots(days_passed) if @only
Expand All @@ -103,15 +121,26 @@ def build_time_ago_slots(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 current style
if @style == "hash"
result = {}
selected.each { |unit, count| result[localized_unit_name(unit)] = count }
result
else
selected.map { |unit, count| translate_unit(unit, count) }
end
end

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

if @style == "hash"
{ localized_unit_name(unit) => count }
else
[translate_unit(unit, count)]
end
end

# Calculate total time in specified unit
Expand Down
File renamed without changes.
32 changes: 32 additions & 0 deletions spec/jekyll-timeago_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
it 'allows localization' do
expect(timeago(sample_date.prev_day(100), sample_date, locale: :fr)).to eq('il y a environ 3 mois et 1 semaine')
expect(timeago(sample_date.prev_day(100), sample_date, locale: :ru)).to eq('3 месяца и неделю назад')
expect(timeago(sample_date.prev_day(100), sample_date, locale: :it)).to eq('3 mesi e 1 settimana fa')
expect(timeago(sample_date.prev_day(100), sample_date, locale: :pt)).to eq('3 meses e 1 semana atrás')
end

it 'allows short style formatting' do
Expand All @@ -116,6 +118,8 @@
expect(timeago(sample_date.prev_day(365), sample_date, locale: :ru, style: :short)).to eq('1г назад')
expect(timeago(sample_date.prev_day(365), sample_date, locale: :es, style: :short)).to eq('hace 1a')
expect(timeago(sample_date.prev_day(30), sample_date, locale: :de, style: :short)).to eq('vor 1mo')
expect(timeago(sample_date.prev_day(120), sample_date, locale: :ca, style: :short)).to eq('fa 4m')
expect(timeago(sample_date.prev_day(120), sample_date, locale: :ja, style: :short)).to eq('4月前')
end

it 'allows complex combinations with short style' do
Expand All @@ -125,12 +129,26 @@
end

it 'allows array style formatting' do
expect(timeago(sample_date, sample_date, style: :array)).to eq(['today'])
expect(timeago(sample_date.prev_day(365), sample_date, style: :array)).to eq(['1 year'])
expect(timeago(sample_date.prev_day(365), sample_date, "style" => "array")).to eq(['1 year'])
expect(timeago(sample_date.prev_day(160), sample_date, style: :array)).to eq(['5 months', '1 week'])
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, sample_date, style: :hash)).to eq({days: 0})
expect(timeago(sample_date, sample_date, style: :hash, locale: :es)).to eq({días: 0})
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})
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(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 +180,13 @@
# 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})
expect(timeago(sample_date.prev_day(365), sample_date, only: :weeks, style: :hash, locale: :es)).to eq({semanas: 52})
end

it 'allows "only" option with different locales' do
Expand Down Expand Up @@ -203,12 +228,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
Loading