lib/jekyll-timeago/core.rb
require 'date'
module Jekyll
module Timeago
module Core
extend self
# Max level of detail: years > months > weeks > days
MAX_DEPTH_LEVEL = 4
# Default level of detail
DEFAULT_DEPTH_LEVEL = 2
# Default threshold
DEFAULT_THRESHOLD = 0
def timeago(from, to = Date.today, options = {})
if to.is_a?(Hash)
options = to
to = Date.today
end
@options = options
from = validate_date(from)
to = validate_date(to)
depth = validate_depth(@options[:depth] || @options["depth"])
threshold = validate_threshold(@options[:threshold] || @options["threshold"])
time_ago_to_now(from, to, depth, threshold)
end
private
def validate_date(date)
Date.parse(date.to_s)
end
def validate_threshold(threshold)
(0.0..1.0).include?(threshold) ? threshold : DEFAULT_THRESHOLD
end
def validate_depth(depth)
(1..MAX_DEPTH_LEVEL).include?(depth) ? depth : DEFAULT_DEPTH_LEVEL
end
def time_ago_to_now(from, to, depth, threshold)
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
past_or_future = from < to ? :past : :future
slots = build_time_ago_slots(days_passed.abs, depth, threshold)
t(past_or_future, date_range: to_sentence(slots))
end
def t(key, options = {})
MiniI18n.t(key, @options.merge(options))
end
# Builds time ranges: ['1 month', '5 days']
# - days_passed: integer in absolute
# - depth: level of detail
# - threshold: minimum fractional difference to keep for next slot
# - current_slots: built time slots
def build_time_ago_slots(days_passed, depth, threshold, current_slots = [])
return current_slots if depth == 0 || days_passed == 0
range = days_to_range(days_passed)
days = days_in(range)
num_elems = (days_passed / days).to_i
current_slots << t(range, count: num_elems)
pending_days = days_passed - (num_elems * days)
if pending_days >= (days_passed * threshold).floor
build_time_ago_slots(pending_days, depth - 1, threshold, current_slots)
else
current_slots
end
end
def days_to_range(days)
case days.abs
when 1..6 then :days
when 7..30 then :weeks
when 31..365 then :months
else :years
end
end
def days_in(range)
case range
when :days then 1
when :weeks then 7
when :months then 30
when :years then 365
end
end
# Array to sentence: ['1 month', '1 week', '5 days'] => "1 month, 1 week and 5 days"
def to_sentence(slots)
if slots.length == 1
slots[0]
else
"#{slots[0...-1].join(t(:words_connector))} #{t(:last_word_connector)} #{slots[-1]}"
end
end
end
end
end