Class: Trifle::Stats::Nocturnal
- Inherits:
-
Object
- Object
- Trifle::Stats::Nocturnal
- Defined in:
- lib/trifle/stats/nocturnal.rb,
lib/trifle/stats/nocturnal/key.rb,
lib/trifle/stats/nocturnal/parser.rb
Overview
rubocop:disable Metrics/ClassLength
Defined Under Namespace
Constant Summary collapse
- UNIT_MAP =
{ 's' => :second, 'm' => :minute, 'h' => :hour, 'd' => :day, 'w' => :week, 'mo' => :month, 'q' => :quarter, 'y' => :year }.freeze
- DAYS_INTO_WEEK =
{ sunday: 0, monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6 }.freeze
Instance Attribute Summary collapse
-
#time ⇒ Object
readonly
Returns the value of attribute time.
Class Method Summary collapse
Instance Method Summary collapse
-
#add(offset, unit) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength.
- #config ⇒ Object
-
#floor(offset, unit) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength.
-
#initialize(time, config: nil) ⇒ Nocturnal
constructor
A new instance of Nocturnal.
Constructor Details
#initialize(time, config: nil) ⇒ Nocturnal
Returns a new instance of Nocturnal.
37 38 39 40 |
# File 'lib/trifle/stats/nocturnal.rb', line 37 def initialize(time, config: nil) @time = time @config = config end |
Instance Attribute Details
#time ⇒ Object (readonly)
Returns the value of attribute time.
35 36 37 |
# File 'lib/trifle/stats/nocturnal.rb', line 35 def time @time end |
Class Method Details
.timeline(from:, to:, offset:, unit:, config: nil) ⇒ Object
22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/trifle/stats/nocturnal.rb', line 22 def self.timeline(from:, to:, offset:, unit:, config: nil) list = [] from = new(from, config: config).floor(offset, unit) to = new(to, config: config).floor(offset, unit) item = from.dup while item <= to list << item candidate = new(item, config: config).add(offset, unit) item = new(candidate, config: config).floor(offset, unit) end list end |
Instance Method Details
#add(offset, unit) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/trifle/stats/nocturnal.rb', line 46 def add(offset, unit) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength raise ArgumentError, "Expected Time object, got #{time.class}" unless time.is_a?(Time) raise ArgumentError, 'Offset must be numeric' unless offset.is_a?(Numeric) raise ArgumentError, "Invalid unit: #{unit}" unless Trifle::Stats::Nocturnal::UNIT_MAP.values.include?(unit) case unit when :second add_elapsed(offset) when :minute add_elapsed(offset * 60) when :hour add_elapsed(offset * 3600) when :day add_days(calendar_offset(offset)) when :week add_days(calendar_offset(offset) * 7) when :month add_months(calendar_offset(offset)) when :quarter add_months(calendar_offset(offset) * 3) when :year add_years(calendar_offset(offset)) end end |
#config ⇒ Object
42 43 44 |
# File 'lib/trifle/stats/nocturnal.rb', line 42 def config @config || Trifle::Stats.default end |
#floor(offset, unit) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/trifle/stats/nocturnal.rb', line 71 def floor(offset, unit) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength raise ArgumentError, "Expected Time object, got #{time.class}" unless time.is_a?(Time) raise ArgumentError, 'Segment size must be positive' unless offset.positive? raise ArgumentError, "Invalid unit: #{unit}" unless Trifle::Stats::Nocturnal::UNIT_MAP.values.include?(unit) source = localized_time case unit when :second # Floor to second segment boundary total_seconds = source.sec floored_seconds = (total_seconds / offset) * offset subtract_elapsed(source, (total_seconds - floored_seconds) + source.subsec) when :minute # Floor to minute segment boundary (segments start from beginning of hour) minutes_from_hour_start = source.min floored_minutes = (minutes_from_hour_start / offset) * offset seconds = ((minutes_from_hour_start - floored_minutes) * 60) + source.sec + source.subsec subtract_elapsed(source, seconds) when :hour # Hour segments are elapsed durations anchored at local day start. day_start = local_midnight(source.to_date, source) segment = offset * 3600 elapsed = source - day_start add_elapsed_to(day_start, (elapsed / segment).floor * segment) when :day # Floor to day segment boundary (segments start from beginning of year) days_from_year_start = source.yday - 1 # yday is 1-indexed, we want 0-indexed floored_days = (days_from_year_start / offset) * offset result_date = Date.new(source.year, 1, 1) + floored_days local_midnight(result_date, source) when :week # Floor to week segment boundary (segments start from beginning of year) year_start_date = Date.new(source.year, 1, 1) # Find the first week boundary of the year based on week_start week_start_offset = DAYS_INTO_WEEK.fetch(config.beginning_of_week) year_start_wday = year_start_date.wday days_to_first_week_start = (week_start_offset - year_start_wday) % 7 first_week_start = year_start_date + days_to_first_week_start # If current time is before first week boundary, use year start if source.to_date < first_week_start local_midnight(year_start_date, source) else weeks_since_first = ((source.to_date - first_week_start).to_i / 7) floored_weeks = (weeks_since_first / offset) * offset result_date = first_week_start + (floored_weeks * 7) local_midnight(result_date, source) end when :month # Floor to month segment boundary (from start of year) months_from_jan = source.month - 1 # 0-indexed floored_months = (months_from_jan / offset) * offset date = Date.new(source.year, floored_months + 1, 1) local_midnight(date, source) when :quarter # Floor to quarter segment boundary current_quarter = ((source.month - 1) / 3) # 0-indexed quarters floored_quarters = (current_quarter / offset) * offset quarter_start_month = (floored_quarters * 3) + 1 date = Date.new(source.year, quarter_start_month, 1) local_midnight(date, source) when :year # Floor to year segment boundary floored_years = (source.year / offset) * offset date = Date.new(floored_years, 1, 1) local_midnight(date, source) end end |