Module: Ruby::Rego::Builtins::Times

Extended by:
RegistryHelpers
Defined in:
lib/ruby/rego/builtins/times.rb,
lib/ruby/rego/builtins/times/diff.rb,
lib/ruby/rego/builtins/times/parse.rb,
lib/ruby/rego/builtins/times/rfc3339.rb,
lib/ruby/rego/builtins/times/duration.rb,
lib/ruby/rego/builtins/times/go_layout.rb,
lib/ruby/rego/builtins/times/arithmetic.rb,
lib/ruby/rego/builtins/times/go_layout/parser.rb,
lib/ruby/rego/builtins/times/go_layout/scanner.rb,
lib/ruby/rego/builtins/times/go_layout/formatter.rb,
lib/ruby/rego/builtins/times/go_layout/parser/zones.rb,
lib/ruby/rego/builtins/times/go_layout/parser/fields.rb,
lib/ruby/rego/builtins/times/go_layout/parser/compose.rb,
lib/ruby/rego/builtins/times/go_layout/parser/consumers.rb

Overview

time.add_date — Go Time.AddDate calendar arithmetic with zone-aware re-anchoring. Lives apart from the time.* core so that file stays under RubyCritic’s complexity budget. Reopens Times; bare references to shared helpers/constants (operand_parts, localize, bounded, integer_value, raise_time_error, NANOS_PER_SECOND) resolve via the reopened module’s scope.

Defined Under Namespace

Modules: GoLayout

Constant Summary collapse

INT64_MAX =
(2**63) - 1
INT64_MIN =
-(2**63)
NANOS_PER_SECOND =
1_000_000_000
DAY_NAMES =

Go’s Weekday.String() names, indexed by Ruby’s Time#wday (Sunday == 0); kept as an explicit table that mirrors Go’s spelling rather than deriving the name another way.

%w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday].freeze
TIME_FUNCTIONS =
{
  "time.parse_rfc3339_ns" => { arity: 1, handler: :parse_rfc3339_ns },
  "time.parse_ns" => { arity: 2, handler: :parse_ns },
  "time.parse_duration_ns" => { arity: 1, handler: :parse_duration_ns },
  "time.now_ns" => { arity: 0, handler: :now_ns },
  "time.date" => { arity: 1, handler: :date },
  "time.clock" => { arity: 1, handler: :clock },
  "time.weekday" => { arity: 1, handler: :weekday },
  "time.diff" => { arity: 2, handler: :diff },
  "time.add_date" => { arity: 4, handler: :add_date },
  "time.format" => { arity: 1, handler: :format }
}.freeze
RFC3339 =
/\A(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d+)?(Z|[+-]\d{2}:\d{2})\z/
DURATION_UNITS =

Nanoseconds per standard Go duration unit.

{
  "ns" => 1, "us" => 1_000, "µs" => 1_000, "μs" => 1_000, "ms" => 1_000_000,
  "s" => NANOS_PER_SECOND, "m" => 60 * NANOS_PER_SECOND, "h" => 3600 * NANOS_PER_SECOND
}.freeze
EXTENDED_HOURS =

OPA’s extended units, expressed as a multiple of hours.

{ "d" => 24, "w" => 7 * 24, "y" => 365 * 24 }.freeze

Class Method Summary collapse

Methods included from RegistryHelpers

register_configured_functions

Class Method Details

.add_date(value, years_value, months_value, days_value) ⇒ Integer, Ruby::Rego::UndefinedValue

Adds years/months/days (Go’s Time.AddDate) to an instant, keeping the wall clock and zone, and returns the result as nanoseconds. The shifted calendar date is normalised the way Go’s time.Date does — overflow rolls forward (Jan 31 + 1mo -> Mar 2/3), not clamped — and the wall clock is re-anchored in the operand’s zone (a DST gap/overlap is resolved exactly as Go’s time.Date does). Out of the int64 range, or a non-integer count, is undefined. :reek:LongParameterList :reek:TooManyStatements

Parameters:

Returns:



25
26
27
28
29
30
31
32
33
34
# File 'lib/ruby/rego/builtins/times/arithmetic.rb', line 25

def self.add_date(value, years_value, months_value, days_value)
  nanos, zone = operand_parts(value, "time.add_date")
  years = int_arg(years_value, "time.add_date")
  months = int_arg(months_value, "time.add_date")
  days = int_arg(days_value, "time.add_date")
  fields = shift_date(localize(nanos, zone, "time.add_date"), years, months, days)
  bounded(reconstruct_ns(fields, zone, nanos % NANOS_PER_SECOND)) || UndefinedValue.new
rescue RangeError
  UndefinedValue.new
end

.clock(value) ⇒ Array(Integer, Integer, Integer)

Returns [hour, minute, second].

Returns:

  • (Array(Integer, Integer, Integer))

    [hour, minute, second]



80
81
82
83
# File 'lib/ruby/rego/builtins/times.rb', line 80

def self.clock(value)
  time = tz_instant(value, "time.clock")
  [time.hour, time.min, time.sec]
end

.date(value) ⇒ Array(Integer, Integer, Integer)

Returns [year, month, day].

Parameters:

  • value (Ruby::Rego::Value)

    ns, or [ns, tz] (an optional ignored layout may follow)

Returns:

  • (Array(Integer, Integer, Integer))

    [year, month, day]



74
75
76
77
# File 'lib/ruby/rego/builtins/times.rb', line 74

def self.date(value)
  time = tz_instant(value, "time.date")
  [time.year, time.month, time.day]
end

.diff(left, right) ⇒ Array(Integer, Integer, Integer, Integer, Integer, Integer)

The calendar difference between two instants as [years, months, days, hours, minutes, seconds], all non-negative — matching OPA (which uses icza/gox’s algorithm). Both instants are decomposed in the FIRST operand’s timezone (Go realigns the second to the first’s location), then a borrow-normalised component subtraction is taken from the earlier to the later. The second operand’s zone is still resolved (and so validated) even though the decomposition uses the first’s. :reek:TooManyStatements

Parameters:

Returns:

  • (Array(Integer, Integer, Integer, Integer, Integer, Integer))


23
24
25
26
27
28
29
30
31
# File 'lib/ruby/rego/builtins/times/diff.rb', line 23

def self.diff(left, right)
  left_nanos, zone = operand_parts(left, "time.diff")
  right_nanos, right_zone = operand_parts(right, "time.diff")
  in_zone(utc_instant(0), right_zone, "time.diff") # validate the 2nd zone too
  left_nanos, right_nanos = right_nanos, left_nanos if left_nanos > right_nanos # order earlier->later
  diff_components(localize(left_nanos, zone, "time.diff"), localize(right_nanos, zone, "time.diff"))
rescue RangeError
  raise_time_error("time.diff")
end

.format(value) ⇒ String, Ruby::Rego::UndefinedValue

Formats an instant (ns, [ns, tz], or [ns, tz, layout]) using a Go reference-time layout (default RFC3339Nano; a named constant or a literal layout otherwise), matching OPA.

Parameters:

Returns:



105
106
107
108
109
110
# File 'lib/ruby/rego/builtins/times.rb', line 105

def self.format(value)
  nanos, zone, layout = operand_parts(value, "time.format")
  GoLayout.format(localize(nanos, zone, "time.format"), layout)
rescue RangeError
  UndefinedValue.new
end

.now_nsInteger

The current wall-clock time in nanoseconds since the Unix epoch (Go’s time.Now().UnixNano()). Impure: within one evaluation OPA fixes the clock once so every call returns the same value. That per-evaluation consistency is supplied by an evaluator overlay that calls this method once at evaluation start (see Evaluator#evaluate); this base handler reads the clock fresh, serving as the single source of the timestamp computation and the value outside an evaluation.

Returns:

  • (Integer)


96
97
98
99
# File 'lib/ruby/rego/builtins/times.rb', line 96

def self.now_ns
  now = ::Time.now
  (now.to_i * NANOS_PER_SECOND) + now.nsec
end

.parse_duration_ns(value) ⇒ Integer, Ruby::Rego::UndefinedValue

Parameters:

Returns:



23
24
25
# File 'lib/ruby/rego/builtins/times/duration.rb', line 23

def self.parse_duration_ns(value)
  parse_extended_duration(string_arg(value, "time.parse_duration_ns")) || UndefinedValue.new
end

.parse_ns(layout_value, value_value) ⇒ Integer, Ruby::Rego::UndefinedValue

Returns epoch nanoseconds, or undefined when the value does not match the layout, a field is out of range, input is left over, or the instant falls outside the int64-nanosecond range.

Parameters:

Returns:

  • (Integer, Ruby::Rego::UndefinedValue)

    epoch nanoseconds, or undefined when the value does not match the layout, a field is out of range, input is left over, or the instant falls outside the int64-nanosecond range



18
19
20
21
22
23
# File 'lib/ruby/rego/builtins/times/parse.rb', line 18

def self.parse_ns(layout_value, value_value)
  layout = string_arg(layout_value, "time.parse_ns")
  value = string_arg(value_value, "time.parse_ns")
  # parse returns the ns Integer (0 is valid and truthy) or nil → undefined.
  GoLayout.parse(layout, value) || UndefinedValue.new
end

.parse_rfc3339_ns(value) ⇒ Integer, Ruby::Rego::UndefinedValue

Parameters:

Returns:



18
19
20
21
22
# File 'lib/ruby/rego/builtins/times/rfc3339.rb', line 18

def self.parse_rfc3339_ns(value)
  match = RFC3339.match(string_arg(value, "time.parse_rfc3339_ns"))
  nanos = match && rfc3339_nanos(match)
  nanos || UndefinedValue.new
end

.register!Ruby::Rego::Builtins::BuiltinRegistry



64
65
66
67
68
# File 'lib/ruby/rego/builtins/times.rb', line 64

def self.register!
  registry = BuiltinRegistry.instance
  register_configured_functions(registry, TIME_FUNCTIONS)
  registry
end

.weekday(value) ⇒ String

Returns the English weekday name.

Returns:

  • (String)

    the English weekday name



86
87
88
# File 'lib/ruby/rego/builtins/times.rb', line 86

def self.weekday(value)
  DAY_NAMES.fetch(tz_instant(value, "time.weekday").wday)
end