markets/jekyll-timeago

View on GitHub
lib/jekyll-timeago/core.rb

Summary

Maintainability
A
0 mins
Test Coverage
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