class RuboCop::Cop::Rails::TimeZone

Time.at(timestamp).in_time_zone
Time.current
# good
Time.zone.parse(‘2015-03-02 19:05:37’)
Time.zone.now
# good
Time.parse(‘2015-03-02 19:05:37’)
Time.now
# bad
# ‘flexible` allows usage of `in_time_zone` instead of `zone`.
@example EnforcedStyle: flexible (default)
Time.zone.parse(’2015-03-02 19:05:37’)
Time.zone.now
# good
Time.at(timestamp).in_time_zone
Time.current
# bad
Time.parse(‘2015-03-02 19:05:37’)
Time.now
# bad
# ‘strict` means that `Time` should be used with `zone`.
@example EnforcedStyle: strict
to use Time.in_time_zone.
When EnforcedStyle is ’flexible’ then it’s also allowed
then only use of Time.zone is allowed.
Two styles are supported for this cop. When EnforcedStyle is ‘strict’
and the article danilenko.org/2012/7/6/rails_timezones/ .
Built on top of Ruby on Rails style guide (github.com/rubocop-hq/rails-style-guide#time)
This cop checks for the use of Time methods without zone.

def acceptable?

def acceptable?
  style == :flexible
end

def acceptable_methods(klass, method_name, node)

def acceptable_methods(klass, method_name, node)
  acceptable = [
    "`Time.zone.#{safe_method(method_name, node)}`",
    "`#{klass}.current`"
  ]
  ACCEPTED_METHODS.each do |am|
    acceptable << "`#{klass}.#{method_name}.#{am}`"
  end
  acceptable
end

def autocorrect(node)

def autocorrect(node)
  lambda do |corrector|
    if acceptable?
      corrector.insert_after(node.source_range, '.in_time_zone')
    else
      corrector.insert_after(node.children[0].source_range, '.zone')
    end
  end
end

def build_message(klass, method_name, node)

rubocop:disable Metrics/MethodLength
def build_message(klass, method_name, node)
  if acceptable?
    format(
      MSG_ACCEPTABLE,
      current: "#{klass}.#{method_name}",
      prefer: acceptable_methods(klass, method_name, node).join(', ')
    )
  elsif method_name == 'current'
    format(MSG_CURRENT,
           current: "#{klass}.#{method_name}")
  else
    safe_method_name = safe_method(method_name, node)
    format(MSG,
           current: "#{klass}.#{method_name}",
           prefer: "Time.zone.#{safe_method_name}")
  end
end

def check_localtime(node)

def check_localtime(node)
  selector_node = node
  while node && node.send_type?
    break if extract_method(node) == :localtime
    node = node.parent
  end
  return if node.arguments?
  add_offense(selector_node,
              location: :selector, message: MSG_LOCALTIME)
end

def check_time_node(klass, node)

def check_time_node(klass, node)
  chain = extract_method_chain(node)
  return if danger_chain?(chain)
  return check_localtime(node) if need_check_localtime?(chain)
  method_name = (chain & DANGEROUS_METHODS).join('.')
  return if offset_provided?(node)
  message = build_message(klass, method_name, node)
  add_offense(node, location: :selector, message: message)
end

def danger_chain?(chain)

def danger_chain?(chain)
  (chain & DANGEROUS_METHODS).empty? || !(chain & good_methods).empty?
end

def extract_method(node)

def extract_method(node)
  _receiver, method_name, *_args = *node
  method_name
end

def extract_method_chain(node)

def extract_method_chain(node)
  chain = []
  while !node.nil? && node.send_type?
    chain << extract_method(node) if method_from_time_class?(node)
    node = node.parent
  end
  chain
end

def good_methods

def good_methods
  if style == :strict
    GOOD_METHODS
  else
    GOOD_METHODS + [:current] + ACCEPTED_METHODS
  end
end

def method_from_time_class?(node)

called is part of the time class.
Only add the method to the chain if the method being
def method_from_time_class?(node)
  receiver, method_name, *_args = *node
  if (receiver.is_a? RuboCop::AST::Node) && !receiver.cbase_type?
    method_from_time_class?(receiver)
  else
    TIMECLASS.include? method_name
  end
end

def method_send?(node)

and receiver is the given node
checks that parent node of send_type
def method_send?(node)
  return false unless node.parent && node.parent.send_type?
  receiver, _method_name, *_args = *node.parent
  receiver == node
end

def need_check_localtime?(chain)

def need_check_localtime?(chain)
  acceptable? && chain.include?(:localtime)
end

def offset_provided?(node)

Time.new(1988, 3, 15, 3, 0, 0, "-05:00")
Example:
When it is, that should be considered safe
Time.new can be called with a time zone offset
def offset_provided?(node)
  node.arguments.size >= 7
end

def on_const(node)

def on_const(node)
  mod, klass = *node
  # we should only check core class
  # (`DateTime`/`Time` or `::Date`/`::DateTime`)
  return unless (mod.nil? || mod.cbase_type?) && method_send?(node)
  check_time_node(klass, node.parent) if TIMECLASS.include?(klass)
end

def safe_method(method_name, node)

def safe_method(method_name, node)
  return method_name unless method_name == 'new'
  if node.arguments?
    'local'
  else
    'now'
  end
end