diff --git a/lib/sumologic/cli.rb b/lib/sumologic/cli.rb index 4fb358b..2c35591 100644 --- a/lib/sumologic/cli.rb +++ b/lib/sumologic/cli.rb @@ -41,7 +41,7 @@ def initialize(*args) Time Formats: --from and --to support multiple formats: • 'now' - current time - • Relative: '-30s', '-5m', '-2h', '-7d', '-1w', '-1M' (sec/min/hour/day/week/month) + • Relative: '-30s', '-5m', '-2h', '-1h30m', '-7d', '-1w', '-1M' (compound supported) • Unix timestamp: '1700000000' (seconds since epoch) • ISO 8601: '2025-11-13T14:00:00' @@ -131,7 +131,7 @@ def list_sources Time Formats: --from and --to support multiple formats: • 'now' - current time - • Relative: '-30s', '-5m', '-2h', '-7d', '-1w', '-1M' (sec/min/hour/day/week/month) + • Relative: '-30s', '-5m', '-2h', '-1h30m', '-7d', '-1w', '-1M' (compound supported) • Unix timestamp: '1700000000' (seconds since epoch) • ISO 8601: '2025-11-13T14:00:00' diff --git a/lib/sumologic/utils/time_parser.rb b/lib/sumologic/utils/time_parser.rb index afe5318..4300c7f 100644 --- a/lib/sumologic/utils/time_parser.rb +++ b/lib/sumologic/utils/time_parser.rb @@ -7,7 +7,7 @@ module Utils # Parses various time formats into ISO 8601 strings for the Sumo Logic API # Supports: # - 'now' - current time - # - Relative times: '-30s', '-5m', '-2h', '-7d', '-1w', '-1M' + # - Relative times: '-30s', '-5m', '-2h', '-7d', '-1w', '-1M', '-1h30m' # - Unix timestamps: '1700000000' or 1700000000 # - ISO 8601: '2025-11-13T14:00:00' class TimeParser @@ -21,7 +21,9 @@ class TimeParser 'M' => 2_592_000 # months (30 days approximation) }.freeze - RELATIVE_TIME_REGEX = /^([+-])(\d+)([smhdwM])$/.freeze + # Matches single or compound relative times: -30m, -1h30m, -2d3h15m + RELATIVE_TIME_REGEX = /^([+-])(\d+[smhdwM])+$/.freeze + RELATIVE_COMPONENT_REGEX = /(\d+)([smhdwM])/.freeze class ParseError < StandardError; end @@ -32,10 +34,8 @@ class ParseError < StandardError; end def self.parse(time_str, _timezone: 'UTC') return parse_now if time_str.to_s.downcase == 'now' - # Try relative time format (e.g., '-30m', '+1h') - if time_str.is_a?(String) && (match = time_str.match(RELATIVE_TIME_REGEX)) - return parse_relative_time(match) - end + # Try relative time format (e.g., '-30m', '+1h', '-1h30m') + return parse_relative_time(time_str) if time_str.is_a?(String) && time_str.match?(RELATIVE_TIME_REGEX) # Try Unix timestamp (integer or numeric string) return parse_unix_timestamp(time_str) if unix_timestamp?(time_str) @@ -95,14 +95,14 @@ def self.parse_timezone(timezone_str) format_time(Time.now) end - private_class_method def self.parse_relative_time(match) - sign, amount, unit = match.captures - amount = amount.to_i - amount = -amount if sign == '-' + private_class_method def self.parse_relative_time(time_str) + sign = time_str[0] + components = time_str.scan(RELATIVE_COMPONENT_REGEX) - seconds_delta = amount * UNITS[unit] - target_time = Time.now + seconds_delta + seconds_delta = components.sum { |amount, unit| amount.to_i * UNITS[unit] } + seconds_delta = -seconds_delta if sign == '-' + target_time = Time.now + seconds_delta format_time(target_time) end diff --git a/spec/sumologic/utils/time_parser_spec.rb b/spec/sumologic/utils/time_parser_spec.rb index 5f286cd..52dc93a 100644 --- a/spec/sumologic/utils/time_parser_spec.rb +++ b/spec/sumologic/utils/time_parser_spec.rb @@ -63,6 +63,26 @@ result = described_class.parse('-100m') expect(result).to eq('2025-11-19T10:20:00') end + + it 'parses compound hours and minutes' do + result = described_class.parse('-1h30m') + expect(result).to eq('2025-11-19T10:30:00') + end + + it 'parses compound days and hours' do + result = described_class.parse('-2d3h') + expect(result).to eq('2025-11-17T09:00:00') + end + + it 'parses compound days, hours, and minutes' do + result = described_class.parse('-1d2h30m') + expect(result).to eq('2025-11-18T09:30:00') + end + + it 'parses compound with positive offset' do + result = described_class.parse('+1h30m') + expect(result).to eq('2025-11-19T13:30:00') + end end context 'with Unix timestamps' do