class BillingCycle::BillingCycle

Utility class for calculating the next billing date for a subscription.

Attributes

created_at[RW]
interval[RW]

Public Class Methods

new(created_at, interval) click to toggle source

Initialize a billing cycle. @param created_at [Time] The date/time the subscription was created @param interval [ActiveSupport::Duration] The interval the subscription is paid

# File lib/billing_cycle/billing_cycle.rb, line 13
def initialize(created_at, interval)
  @created_at = created_at
  @interval = interval
end

Public Instance Methods

next_due_at(now = Time.zone.now) click to toggle source

Returns the next billing date based on the subscription @param now [Time] The current time @return [Time] The next billing date/time

# File lib/billing_cycle/billing_cycle.rb, line 21
def next_due_at(now = Time.zone.now)
  # The next billing date is always the original date if "now" is earlier than the original date
  return created_at if now <= created_at

  number_of_cycles = number_of_cycles_since_created(now)
  next_due_at = calculate_due_at(number_of_cycles)

  # The number of cycles since the billing cycle was created only gets us to "now"
  # so we need probably need to add another cycle to get the next billing date.
  while next_due_at < now
    number_of_cycles += interval_value
    next_due_at = calculate_due_at(number_of_cycles)
  end

  next_due_at
end
percent_elapsed(now = Time.zone.now) click to toggle source

Returns the percentage of time that's elapsed between the previous due date and the next due date. @param now [Time] The current time @return [Float] The percentage as a fraction of 1.0

# File lib/billing_cycle/billing_cycle.rb, line 41
def percent_elapsed(now = Time.zone.now)
  time_elapsed(1.second, now) / seconds_in_cycle(now)
end
percent_remaining(now = Time.zone.now) click to toggle source

Returns the percentage of time that's remaining between the previous due date and the next due date. @param now [Time] The current time @return [Float] The percentage as a fraction of 1.0

# File lib/billing_cycle/billing_cycle.rb, line 48
def percent_remaining(now = Time.zone.now)
  time_remaining(1.second, now) / seconds_in_cycle(now)
end
previous_due_at(now = Time.zone.now) click to toggle source

Returns the previous billing date based on the subscription @param now [Time] The current time @return [Time] The previous billing date/time

# File lib/billing_cycle/billing_cycle.rb, line 55
def previous_due_at(now = Time.zone.now)
  # There was no previous billing date before the original billing date
  return nil if now <= created_at

  number_of_cycles = number_of_cycles_since_created(now) + interval_value
  previous_due_at = calculate_due_at(number_of_cycles)

  # The number of cycles since the billing cycle was created gets us to "now",
  # and if now matches a billing date exactly, we want the previous billing date.
  while previous_due_at >= now
    number_of_cycles -= interval_value
    previous_due_at = calculate_due_at(number_of_cycles)
  end

  previous_due_at
end
time_elapsed(interval = 1.second, now = Time.zone.now) click to toggle source

Returns the time elapsed since the previous due date. @param interval [ActiveSupport::Duration] The duration @param now [Time] The current time @return [Float] The number of intervals since the previous due date

# File lib/billing_cycle/billing_cycle.rb, line 76
def time_elapsed(interval = 1.second, now = Time.zone.now)
  (now - previous_due_at(now)) / interval
end
time_remaining(interval = 1.second, now = Time.zone.now) click to toggle source

Returns the time remaining until the next due date. @param now [Time] The current time @return [Float] The number of intervals until the next due date

# File lib/billing_cycle/billing_cycle.rb, line 83
def time_remaining(interval = 1.second, now = Time.zone.now)
  (next_due_at(now) - now) / interval
end

Private Instance Methods

calculate_due_at(number_of_cycles) click to toggle source

Calculate the due date based on number of billing cycles since or before the date/time the subscription was created. @param number_of_cycles [Integer] @return [Time]

# File lib/billing_cycle/billing_cycle.rb, line 93
def calculate_due_at(number_of_cycles)
  number_of_cycles.send(interval_units).from_now(created_at)
end
interval_units() click to toggle source

Returns the interval type from the interval's duration. @return [Symbol] `:month` for a duration of `1.month`

# File lib/billing_cycle/billing_cycle.rb, line 109
def interval_units
  if interval.parts.is_a? Array
    interval.parts[0][0]
  else
    interval.parts.keys[0]
  end
end
interval_value() click to toggle source

Returns the number from the interval's duration. @return [Integer] `6` for a duration of `6.months`

# File lib/billing_cycle/billing_cycle.rb, line 99
def interval_value
  if interval.parts.is_a? Array
    interval.parts[0][1]
  else
    interval.parts.values[0]
  end
end
number_of_cycles_since_created(now) click to toggle source

Returns the number billing cycles that have occurred between the created date and “now”. @param now [Time] The current time @return [Integer]

# File lib/billing_cycle/billing_cycle.rb, line 120
def number_of_cycles_since_created(now)
  (interval_value * ((now - created_at).to_i / interval)).to_i
end
seconds_in_cycle(now) click to toggle source

Returns the total number of seconds in the current billing cycle. @param now [Time] The current time @return [Float]

# File lib/billing_cycle/billing_cycle.rb, line 127
def seconds_in_cycle(now)
  next_due_at(now) - previous_due_at(now)
end