Module: ElasticGraph::Support::TimeUtil

Defined in:
lib/elastic_graph/support/time_util.rb

Constant Summary collapse

NANOS_PER_SECOND =
1_000_000_000
NANOS_PER_MINUTE =
NANOS_PER_SECOND * 60
NANOS_PER_HOUR =
NANOS_PER_MINUTE * 60

Class Method Summary collapse

Class Method Details

.advance_one_unit(time, unit) ⇒ Object

Helper method for advancing time. Unfortunately, Ruby’s core ‘Time` type does not directly support this. ActiveSupport (from rails) provides this functionality, but we don’t depend on rails at all and don’t want to add such a heavyweight dependency for such a small thing.

Luckily, our needs are quite limited, which makes this a much simpler problem then a general purpose ‘time.advance(…)` API:

  • We only need to support year, month, day, and hour advances.

  • We only ever need to advance a single unit.

This provides a simple, correct implementation for that constrained problem space.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/elastic_graph/support/time_util.rb', line 53

def self.advance_one_unit(time, unit)
  case unit
  when :year
    with_updated(time, year: time.year + 1)
  when :month
    maybe_next_month =
      if time.month == 12
        with_updated(time, year: time.year + 1, month: 1)
      else
        with_updated(time, month: time.month + 1)
      end

    # If the next month has fewer days than the month of `time`, then it can "spill over" to a day
    # from the first week of the month following that. For example, if the date of `time` was 2021-01-31
    # and we add a month, it attempts to go to `2021-02-31` but such a date doesn't exist--instead
    # `maybe_next_month` will be on `2021-03-03` because of the overflow. Here we correct for that.
    #
    # Our assumption (which we believe to be correct) is that every time this happens, both of these are true:
    # - `time.day` is near the end of its month
    # - `maybe_next_month.day` is near the start of its month
    #
    # ...and furthermore, we do not believe there is any other case where `time.day` and `maybe_next_month.day` can differ.
    if time.day > maybe_next_month.day
      corrected_date = maybe_next_month.to_date - maybe_next_month.day
      with_updated(time, year: corrected_date.year, month: corrected_date.month, day: corrected_date.day)
    else
      maybe_next_month
    end
  when :day
    next_day = time.to_date + 1
    with_updated(time, year: next_day.year, month: next_day.month, day: next_day.day)
  when :hour
    time + 3600
  end
end

.nano_of_day_from_local_time(local_time_string) ⇒ Object

Simple helper function to convert a local time string (such as ‘03:45:12` or `12:30:43.756`) to an integer value between 0 and 24 * 60 * 60 * 1,000,000,000 - 1 representing the nano of day for the local time value.

This is meant to match the behavior of Java’s ‘LocalTime#toNanoOfDay()` API: docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/LocalTime.html#toNanoOfDay()

This is specifically useful when we need to work with local time values in a script: by converting a local time parameter to nano-of-day, our script can more efficiently compare values, avoiding the need to parse the same local time parameters over and over again as it applies the script to each document.

Note: this method assumes the given ‘local_time_string` is well-formed. You’ll get an exception if you provide a malformed value, but no effort has been put into giving a clear error message. The caller is expected to have already validated that the ‘local_time_string` is formatted correctly.



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/elastic_graph/support/time_util.rb', line 31

def self.nano_of_day_from_local_time(local_time_string)
  hours_str, minutes_str, full_seconds_str = local_time_string.split(":")
  seconds_str, subseconds_str = (_ = full_seconds_str).split(".")

  hours = Integer(_ = hours_str, 10)
  minutes = Integer(_ = minutes_str, 10)
  seconds = Integer(seconds_str, 10)
  nanos = Integer(subseconds_str.to_s.ljust(9, "0"), 10)

  (hours * NANOS_PER_HOUR) + (minutes * NANOS_PER_MINUTE) + (seconds * NANOS_PER_SECOND) + nanos
end