Skip to content

Commit

Permalink
improve datetime support
Browse files Browse the repository at this point in the history
 - reduce date(time) false positive parsing
 - add option to disable raw datetime literals

Fixes #276
  • Loading branch information
rubysolo committed Mar 17, 2023
1 parent 8aa37bc commit 6322b13
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 7 deletions.
7 changes: 5 additions & 2 deletions lib/dentaku/calculator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module Dentaku
class Calculator
include StringCasing
attr_reader :result, :memory, :tokenizer, :case_sensitive, :aliases,
:nested_data_support, :ast_cache
:nested_data_support, :ast_cache, :raw_date_literals

def initialize(options = {})
clear
Expand All @@ -19,6 +19,8 @@ def initialize(options = {})
@aliases = options.delete(:aliases) || Dentaku.aliases
@nested_data_support = options.fetch(:nested_data_support, true)
options.delete(:nested_data_support)
@raw_date_literals = options.fetch(:raw_date_literals, true)
options.delete(:raw_date_literals)
@ast_cache = options
@disable_ast_cache = false
@function_registry = Dentaku::AST::FunctionRegistry.new
Expand Down Expand Up @@ -94,9 +96,10 @@ def ast(expression)

@ast_cache.fetch(expression) {
options = {
aliases: aliases,
case_sensitive: case_sensitive,
function_registry: @function_registry,
aliases: aliases
raw_date_literals: raw_date_literals
}

tokens = tokenizer.tokenize(expression, options)
Expand Down
6 changes: 4 additions & 2 deletions lib/dentaku/token_scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Dentaku
class TokenScanner
extend StringCasing

DATE_TIME_REGEXP = /\d{2}\d{2}?-\d{1,2}-\d{1,2}( \d{1,2}:\d{1,2}:\d{1,2})? ?(Z|((\+|\-)\d{2}\:?\d{2}))?/.freeze
DATE_TIME_REGEXP = /\d{2}\d{2}?-\d{1,2}-\d{1,2}( \d{1,2}:\d{1,2}:\d{1,2})? ?(Z|((\+|\-)\d{2}\:?\d{2}))?(?!\d)/.freeze

def initialize(category, regexp, converter = nil, condition = nil)
@category = category
Expand Down Expand Up @@ -75,7 +75,9 @@ def scanners=(scanner_ids)

def scanners(options = {})
@case_sensitive = options.fetch(:case_sensitive, false)
@scanners.values
raw_date_literals = options.fetch(:raw_date_literals, true)

@scanners.select { |k, _| raw_date_literals || k != :datetime }.values
end

def whitespace
Expand Down
10 changes: 7 additions & 3 deletions lib/dentaku/tokenizer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

module Dentaku
class Tokenizer
attr_reader :case_sensitive, :aliases
attr_reader :aliases

LPAREN = TokenMatcher.new(:grouping, :open)
RPAREN = TokenMatcher.new(:grouping, :close)
Expand All @@ -15,10 +15,14 @@ def tokenize(string, options = {})
@aliases = options.fetch(:aliases, global_aliases)
input = strip_comments(string.to_s.dup)
input = replace_aliases(input)
@case_sensitive = options.fetch(:case_sensitive, false)

scanner_options = {
case_sensitive: options.fetch(:case_sensitive, false),
raw_date_literals: options.fetch(:raw_date_literals, true)
}

until input.empty?
scanned = TokenScanner.scanners(case_sensitive: case_sensitive).any? do |scanner|
scanned = TokenScanner.scanners(scanner_options).any? do |scanner|
scanned, input = scan(input, scanner)
scanned
end
Expand Down
8 changes: 8 additions & 0 deletions spec/calculator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
expect(calculator.evaluate('(2 + 3) - 1')).to eq(4)
expect(calculator.evaluate('(-2 + 3) - 1')).to eq(0)
expect(calculator.evaluate('(-2 - 3) - 1')).to eq(-6)
expect(calculator.evaluate('1353+91-1-3322-22')).to eq(-1901)
expect(calculator.evaluate('1 + -(2 ^ 2)')).to eq(-3)
expect(calculator.evaluate('3 + -num', num: 2)).to eq(1)
expect(calculator.evaluate('-num + 3', num: 2)).to eq(1)
Expand Down Expand Up @@ -445,6 +446,13 @@
expect(calculator.evaluate('t1 > 2017-01-02', t1: Time.local(2017, 1, 3).to_datetime)).to be_truthy
end

describe 'disabling date literals' do
it 'does not parse formulas with minus signs as dates' do
calculator = described_class.new(raw_date_literals: false)
expect(calculator.evaluate!('2020-01-01')).to eq(2018)
end
end

describe 'supports date arithmetic' do
it 'from hardcoded string' do
expect(calculator.evaluate!('2020-01-01 + 30').to_date).to eq(Time.local(2020, 1, 31).to_date)
Expand Down

0 comments on commit 6322b13

Please sign in to comment.