chronic-davispuh 0.10.2.v0.1da32066b3f46f2506b3471e39557497e34afa27
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +10 -0
- data/Gemfile +3 -0
- data/HISTORY.md +243 -0
- data/LICENSE +21 -0
- data/README.md +185 -0
- data/Rakefile +68 -0
- data/chronic.gemspec +27 -0
- data/lib/chronic.rb +122 -0
- data/lib/chronic/arrow.rb +270 -0
- data/lib/chronic/date.rb +272 -0
- data/lib/chronic/definition.rb +208 -0
- data/lib/chronic/dictionary.rb +36 -0
- data/lib/chronic/handler.rb +44 -0
- data/lib/chronic/handlers/anchor.rb +65 -0
- data/lib/chronic/handlers/arrow.rb +84 -0
- data/lib/chronic/handlers/date.rb +270 -0
- data/lib/chronic/handlers/date_time.rb +72 -0
- data/lib/chronic/handlers/general.rb +130 -0
- data/lib/chronic/handlers/narrow.rb +54 -0
- data/lib/chronic/handlers/time.rb +167 -0
- data/lib/chronic/handlers/time_zone.rb +50 -0
- data/lib/chronic/objects/anchor_object.rb +263 -0
- data/lib/chronic/objects/arrow_object.rb +27 -0
- data/lib/chronic/objects/date_object.rb +164 -0
- data/lib/chronic/objects/date_time_object.rb +64 -0
- data/lib/chronic/objects/handler_object.rb +81 -0
- data/lib/chronic/objects/narrow_object.rb +85 -0
- data/lib/chronic/objects/time_object.rb +96 -0
- data/lib/chronic/objects/time_zone_object.rb +27 -0
- data/lib/chronic/parser.rb +154 -0
- data/lib/chronic/span.rb +32 -0
- data/lib/chronic/tag.rb +84 -0
- data/lib/chronic/tags/day_name.rb +34 -0
- data/lib/chronic/tags/day_portion.rb +33 -0
- data/lib/chronic/tags/day_special.rb +30 -0
- data/lib/chronic/tags/grabber.rb +29 -0
- data/lib/chronic/tags/keyword.rb +63 -0
- data/lib/chronic/tags/month_name.rb +39 -0
- data/lib/chronic/tags/ordinal.rb +52 -0
- data/lib/chronic/tags/pointer.rb +28 -0
- data/lib/chronic/tags/rational.rb +35 -0
- data/lib/chronic/tags/scalar.rb +101 -0
- data/lib/chronic/tags/season_name.rb +31 -0
- data/lib/chronic/tags/separator.rb +130 -0
- data/lib/chronic/tags/sign.rb +35 -0
- data/lib/chronic/tags/time_special.rb +34 -0
- data/lib/chronic/tags/time_zone.rb +56 -0
- data/lib/chronic/tags/unit.rb +174 -0
- data/lib/chronic/time.rb +141 -0
- data/lib/chronic/time_zone.rb +80 -0
- data/lib/chronic/token.rb +61 -0
- data/lib/chronic/token_group.rb +271 -0
- data/lib/chronic/tokenizer.rb +42 -0
- data/lib/chronic/version.rb +3 -0
- data/test/helper.rb +12 -0
- data/test/test_chronic.rb +190 -0
- data/test/test_daylight_savings.rb +98 -0
- data/test/test_handler.rb +113 -0
- data/test/test_parsing.rb +1520 -0
- data/test/test_span.rb +23 -0
- data/test/test_token.rb +31 -0
- metadata +218 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'chronic/handlers/date_time'
|
2
|
+
|
3
|
+
module Chronic
|
4
|
+
class DateTimeObject < HandlerObject
|
5
|
+
include DateStructure
|
6
|
+
include TimeStructure
|
7
|
+
include TimeZoneStructure
|
8
|
+
|
9
|
+
def initialize(tokens, token_index, definitions, local_date, options)
|
10
|
+
super
|
11
|
+
@normalized = false
|
12
|
+
match(tokens, @index, definitions)
|
13
|
+
end
|
14
|
+
|
15
|
+
def normalize!
|
16
|
+
return if @normalized
|
17
|
+
adjust!
|
18
|
+
@normalized = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def is_valid?
|
22
|
+
normalize!
|
23
|
+
return false if @year.nil? or @month.nil? or @day.nil? or @hour.nil? or @minute.nil? or @second.nil?
|
24
|
+
::Date.valid_date?(@year, @month, @day)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_end
|
28
|
+
year = @year
|
29
|
+
month = @month
|
30
|
+
day = @day
|
31
|
+
hour = @hour
|
32
|
+
minute = @minute
|
33
|
+
second = @second
|
34
|
+
if @precision == :subsecond
|
35
|
+
minute, second = Time::add_second(minute, second, 1.0 / @subsecond_size)
|
36
|
+
else
|
37
|
+
minute, second = Time::add_second(minute, second)
|
38
|
+
end
|
39
|
+
[year, month, day, hour, minute, second]
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_span
|
43
|
+
span_start = Chronic.construct(@year, @month, @day, @hour, @minute, @second, self)
|
44
|
+
end_year, end_month, end_day, end_hour, end_minute, end_second = get_end
|
45
|
+
span_end = Chronic.construct(end_year, end_month, end_day, end_hour, end_minute, end_second, self)
|
46
|
+
Span.new(span_start, span_end, true)
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_s
|
50
|
+
"year #{@year.inspect}, month #{@month.inspect}, day #{@day.inspect}, hour #{@hour.inspect}, minute #{@minute.inspect}, second #{@second.inspect}, subsecond #{@subsecond.inspect}"
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def adjust!
|
56
|
+
@second ||= 0
|
57
|
+
@second += @subsecond if @subsecond
|
58
|
+
@subsecond = 0
|
59
|
+
end
|
60
|
+
|
61
|
+
include DateTimeHandlers
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'chronic/handlers/general'
|
2
|
+
|
3
|
+
module Chronic
|
4
|
+
class HandlerObject
|
5
|
+
|
6
|
+
attr_accessor :local_date
|
7
|
+
attr_reader :begin
|
8
|
+
attr_reader :width
|
9
|
+
attr_reader :precision
|
10
|
+
def initialize(tokens, token_index, definitions, local_date, options)
|
11
|
+
@tokens = tokens
|
12
|
+
@begin = token_index
|
13
|
+
@local_date = local_date
|
14
|
+
@options = options
|
15
|
+
@context = @options[:context]
|
16
|
+
@width = 0
|
17
|
+
@index = token_index
|
18
|
+
end
|
19
|
+
|
20
|
+
def end
|
21
|
+
@begin + @width - 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def range
|
25
|
+
@begin..(@begin + @width - 1)
|
26
|
+
end
|
27
|
+
|
28
|
+
def overlap?(r2)
|
29
|
+
r1 = range
|
30
|
+
(r1.begin <= r2.end) and (r2.begin <= r1.end)
|
31
|
+
end
|
32
|
+
|
33
|
+
def is_valid?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def local_day
|
38
|
+
[@local_date.year, @local_date.month, @local_date.day]
|
39
|
+
end
|
40
|
+
|
41
|
+
def local_time
|
42
|
+
[@local_date.hour, @local_date.min, @local_date.sec + @local_date.usec.to_f / (10 ** 6)]
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def match(tokens, token_index, definitions)
|
48
|
+
definitions.each do |definition|
|
49
|
+
if Handler.match(tokens, token_index, definition.first)
|
50
|
+
self.method(definition.last).call
|
51
|
+
@width = @index - @begin
|
52
|
+
puts "\nHandler: #{self.class.name}.#{definition.last.to_s} @ #{@begin}-#{self.end} =>\n=======> #{self}" if Chronic.debug and @width > 0
|
53
|
+
return
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@width = 0
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_sign
|
60
|
+
(@context == :past) ? -1 : ((@context == :future) ? 1 : 0)
|
61
|
+
end
|
62
|
+
|
63
|
+
def get_modifier
|
64
|
+
(@grabber == :last) ? -1 : ((@grabber == :next) ? 1 : 0)
|
65
|
+
end
|
66
|
+
|
67
|
+
def next_tag
|
68
|
+
@index += 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_possible(tag)
|
72
|
+
if @tokens[@index].get_tag(tag)
|
73
|
+
next_tag
|
74
|
+
return true
|
75
|
+
end
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'chronic/handlers/narrow'
|
2
|
+
|
3
|
+
module Chronic
|
4
|
+
class NarrowObject < HandlerObject
|
5
|
+
attr_reader :number
|
6
|
+
attr_reader :wday
|
7
|
+
attr_reader :unit
|
8
|
+
def initialize(tokens, token_index, definitions, local_date, options)
|
9
|
+
super
|
10
|
+
handle_possible(SeparatorSpace) if handle_possible(KeywordOn)
|
11
|
+
match(tokens, @index, definitions)
|
12
|
+
end
|
13
|
+
|
14
|
+
def is_valid?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"number #{@number.inspect}, wday #{@wday.inspect}, unit #{@unit.inspect}, grabber #{@grabber.inspect}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_span(span = nil, timezone = nil)
|
23
|
+
start = @local_date
|
24
|
+
start = span.begin if span
|
25
|
+
hour, minute, second, utc_offset = Time::split(start)
|
26
|
+
end_hour = end_minute = end_second = 0
|
27
|
+
modifier = get_modifier
|
28
|
+
sign = get_sign
|
29
|
+
if @wday
|
30
|
+
diff = Date::wday_diff(start, @wday, 0, 0)
|
31
|
+
diff += Date::WEEK_DAYS if diff < 0
|
32
|
+
diff += Date::WEEK_DAYS * (@number - 1) if @number > 1
|
33
|
+
year, month, day = Date::add_day(start.year, start.month, start.day, diff)
|
34
|
+
end_year, end_month, end_day = year, month, day
|
35
|
+
end_hour += Date::DAY_HOURS
|
36
|
+
else
|
37
|
+
case @unit
|
38
|
+
when :year
|
39
|
+
# TODO
|
40
|
+
raise "Not Implemented NarrowObject #{@unit.inspect}"
|
41
|
+
when :season
|
42
|
+
# TODO
|
43
|
+
raise "Not Implemented NarrowObject #{@unit.inspect}"
|
44
|
+
when :quarter
|
45
|
+
this_quarter = Date::get_quarter_index(start.month)
|
46
|
+
diff = Date::quarter_diff(this_quarter, @number, modifier, sign)
|
47
|
+
year, quarter = Date::add_quarter(start.year, this_quarter, diff)
|
48
|
+
year = start.year if span
|
49
|
+
month = Date::QUARTERS[quarter]
|
50
|
+
end_year, next_quarter = Date::add_quarter(year, quarter)
|
51
|
+
end_month = Date::QUARTERS[next_quarter]
|
52
|
+
day = end_day = 1
|
53
|
+
hour = minute = second = 0
|
54
|
+
when :month
|
55
|
+
day = start.day
|
56
|
+
year, month = Date::add_month(start.year, start.month, @number - 1)
|
57
|
+
end_year, end_month, end_day = year, month, day
|
58
|
+
end_year, end_month = Date::add_month(year, month, 1)
|
59
|
+
when :fortnight, :week, :weekend, :weekday
|
60
|
+
# TODO
|
61
|
+
raise "Not Implemented NarrowObject #{@unit.inspect}"
|
62
|
+
when :day
|
63
|
+
year, month, day = Date::add_day(start.year, start.month, start.day, @number - 1)
|
64
|
+
end_year, end_month, end_day = year, month, day
|
65
|
+
end_hour += Date::DAY_HOURS
|
66
|
+
when :morning, :noon, :afternoon, :evening, :night, :midnight, :hour, :minute, :second, :milisecond
|
67
|
+
# TODO
|
68
|
+
raise "Not Implemented NarrowObject #{@unit.inspect}"
|
69
|
+
else
|
70
|
+
raise "Uknown unit #{@unit.inspect}!"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
span_start = Chronic.construct(year, month, day, hour, minute, second, timezone)
|
74
|
+
return nil if span and span_start >= span.end
|
75
|
+
span_end = Chronic.construct(end_year, end_month, end_day, end_hour, end_minute, end_second, timezone)
|
76
|
+
span_end = span.end if span and span_end > span.end
|
77
|
+
Span.new(span_start, span_end, true)
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
include NarrowHandlers
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'chronic/handlers/time'
|
2
|
+
|
3
|
+
module Chronic
|
4
|
+
class TimeObject < HandlerObject
|
5
|
+
include TimeStructure
|
6
|
+
attr_reader :time_special
|
7
|
+
attr_reader :day_portion
|
8
|
+
attr_reader :ambiguous
|
9
|
+
def initialize(tokens, token_index, definitions, local_date, options)
|
10
|
+
super
|
11
|
+
@ambiguous = true
|
12
|
+
@ambiguous = false if @options[:hours24] == true or @options[:ambiguous_time_range] == :none
|
13
|
+
@normalized = false
|
14
|
+
match(tokens, @index, definitions)
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalize!
|
18
|
+
return if @normalized
|
19
|
+
if @hour
|
20
|
+
if @day_portion == :am # 0am to 12pm
|
21
|
+
@hour = 0 if @hour == 12
|
22
|
+
@ambiguous = false
|
23
|
+
elsif @time_special == :morning
|
24
|
+
@ambiguous = false
|
25
|
+
elsif @day_portion == :pm or # 12pm to 0am
|
26
|
+
@time_special == :afternoon or
|
27
|
+
@time_special == :evening
|
28
|
+
@hour += 12 if @hour < 12
|
29
|
+
@ambiguous = false
|
30
|
+
elsif @time_special == :night
|
31
|
+
@hour = 0 if @hour == 12
|
32
|
+
@hour += 12 if @hour < 12
|
33
|
+
@ambiguous = false
|
34
|
+
end
|
35
|
+
@hour += 12 if @ambiguous and @options[:context] != :none and @hour != 12 and @hour <= @options[:ambiguous_time_range]
|
36
|
+
elsif @time_special
|
37
|
+
if @time_special == :now
|
38
|
+
set_time
|
39
|
+
else
|
40
|
+
@hour = Time::SPECIAL[@time_special].begin
|
41
|
+
end
|
42
|
+
else
|
43
|
+
@hour = @local_date.hour
|
44
|
+
end
|
45
|
+
@ambiguous = false
|
46
|
+
@minute ||= @second ? @local_date.minute : 0
|
47
|
+
@second ||= 0
|
48
|
+
@second += @subsecond if @subsecond
|
49
|
+
@subsecond = 0
|
50
|
+
@normalized = true
|
51
|
+
end
|
52
|
+
|
53
|
+
def is_valid?
|
54
|
+
normalize!
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_end
|
59
|
+
hour = @hour
|
60
|
+
minute = @minute
|
61
|
+
second = @second
|
62
|
+
case @precision
|
63
|
+
when :hour
|
64
|
+
hour += 1
|
65
|
+
when :minute
|
66
|
+
hour, minute = Time::add_minute(hour, minute)
|
67
|
+
when :second
|
68
|
+
minute, second = Time::add_second(minute, second)
|
69
|
+
when :subsecond
|
70
|
+
minute, second = Time::add_second(minute, second, 1.0 / @subsecond_size)
|
71
|
+
when :time_special
|
72
|
+
if @time_special != :now
|
73
|
+
hour = Time::SPECIAL[@time_special].end
|
74
|
+
minute = second = 0
|
75
|
+
end
|
76
|
+
else
|
77
|
+
# BUG! Should never happen
|
78
|
+
raise "Uknown precision #{@precision.inspect}"
|
79
|
+
end
|
80
|
+
[hour, minute, second]
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
"hour #{@hour.inspect}, minute #{@minute.inspect}, second #{@second.inspect}, subsecond #{@subsecond.inspect}, time special #{time_special.inspect}, day portion #{@day_portion.inspect}, precision #{@precision.inspect}, ambiguous #{ambiguous.inspect}"
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
def set_time
|
90
|
+
@hour, @minute, @second = local_time
|
91
|
+
end
|
92
|
+
|
93
|
+
include TimeHandlers
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'chronic/handlers/time_zone'
|
2
|
+
|
3
|
+
module Chronic
|
4
|
+
class TimeZoneObject < HandlerObject
|
5
|
+
include TimeZoneStructure
|
6
|
+
|
7
|
+
def initialize(tokens, token_index, definitions, local_date, options)
|
8
|
+
super
|
9
|
+
match(tokens, @index, definitions)
|
10
|
+
end
|
11
|
+
|
12
|
+
def normalize!
|
13
|
+
return if @normalized
|
14
|
+
@offset = to_offset
|
15
|
+
@normalized = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_valid?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
include TimeZoneHandlers
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'chronic/dictionary'
|
2
|
+
|
3
|
+
module Chronic
|
4
|
+
class Parser
|
5
|
+
|
6
|
+
# Hash of default configuration options.
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
:context => :future,
|
9
|
+
:now => nil,
|
10
|
+
:hours24 => nil,
|
11
|
+
:week_start => :sunday,
|
12
|
+
:guess => true,
|
13
|
+
:ambiguous_time_range => 6,
|
14
|
+
:endian_precedence => [:middle, :little],
|
15
|
+
:ambiguous_year_future_bias => 50
|
16
|
+
}
|
17
|
+
|
18
|
+
attr_accessor :now
|
19
|
+
attr_reader :options
|
20
|
+
|
21
|
+
# options - An optional Hash of configuration options:
|
22
|
+
# :context - If your string represents a birthday, you can set
|
23
|
+
# this value to :past and if an ambiguous string is
|
24
|
+
# given, it will assume it is in the past.
|
25
|
+
# :now - Time, all computations will be based off of time
|
26
|
+
# instead of Time.now.
|
27
|
+
# :hours24 - Time will be parsed as it would be 24 hour clock.
|
28
|
+
# :week_start - By default, the parser assesses weeks start on
|
29
|
+
# sunday but you can change this value to :monday if
|
30
|
+
# needed.
|
31
|
+
# :guess - By default the parser will guess a single point in time
|
32
|
+
# for the given date or time. If you'd rather have the
|
33
|
+
# entire time span returned, set this to false
|
34
|
+
# and a Chronic::Span will be returned. Setting :guess to :end
|
35
|
+
# will return last time from Span, to :middle for middle (same as just true)
|
36
|
+
# and :begin for first time from span.
|
37
|
+
# :ambiguous_time_range - If an Integer is given, ambiguous times
|
38
|
+
# (like 5:00) will be assumed to be within the range of
|
39
|
+
# that time in the AM to that time in the PM. For
|
40
|
+
# example, if you set it to `7`, then the parser will
|
41
|
+
# look for the time between 7am and 7pm. In the case of
|
42
|
+
# 5:00, it would assume that means 5:00pm. If `:none`
|
43
|
+
# is given, no assumption will be made, and the first
|
44
|
+
# matching instance of that time will be used.
|
45
|
+
# :endian_precedence - By default, Chronic will parse "03/04/2011"
|
46
|
+
# as the fourth day of the third month. Alternatively you
|
47
|
+
# can tell Chronic to parse this as the third day of the
|
48
|
+
# fourth month by setting this to [:little, :middle].
|
49
|
+
# :ambiguous_year_future_bias - When parsing two digit years
|
50
|
+
# (ie 79) unlike Rubys Time class, Chronic will attempt
|
51
|
+
# to assume the full year using this figure. Chronic will
|
52
|
+
# look x amount of years into the future and past. If the
|
53
|
+
# two digit year is `now + x years` it's assumed to be the
|
54
|
+
# future, `now - x years` is assumed to be the past.
|
55
|
+
def initialize(options = {})
|
56
|
+
validate_options!(options)
|
57
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
58
|
+
@now = options[:now] || Chronic.time_class.now
|
59
|
+
end
|
60
|
+
|
61
|
+
# Parse "text" with the given options
|
62
|
+
# Returns either a Time or Chronic::Span, depending on the value of options[:guess]
|
63
|
+
def parse(text)
|
64
|
+
text = pre_proccess(text)
|
65
|
+
text = pre_normalize(text)
|
66
|
+
puts text.inspect if Chronic.debug
|
67
|
+
|
68
|
+
tokens = Tokenizer::tokenize(' ' + text + ' ')
|
69
|
+
tag(tokens, options)
|
70
|
+
|
71
|
+
puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}" if Chronic.debug
|
72
|
+
|
73
|
+
token_group = TokenGroup.new(tokens, definitions(options), @now, options)
|
74
|
+
span = token_group.to_span
|
75
|
+
|
76
|
+
guess(span, options[:guess]) if span
|
77
|
+
end
|
78
|
+
|
79
|
+
# Replace any whitespace characters to single space
|
80
|
+
def pre_proccess(text)
|
81
|
+
text.to_s.strip.gsub(/[[:space:]]+/, ' ').gsub(/\s{2,}/, ' ')
|
82
|
+
end
|
83
|
+
|
84
|
+
# Clean up the specified text ready for parsing.
|
85
|
+
#
|
86
|
+
# Clean up the string, convert number words to numbers
|
87
|
+
# (three => 3), and convert ordinal words to numeric
|
88
|
+
# ordinals (third => 3rd)
|
89
|
+
#
|
90
|
+
# text - The String text to normalize.
|
91
|
+
#
|
92
|
+
# Returns a new String ready for Chronic to parse.
|
93
|
+
def pre_normalize(text)
|
94
|
+
text.gsub!(/\b(quarters?)\b/, '<=\1=>') # workaround for Numerizer
|
95
|
+
text.gsub!(/\b\s+third\b/, ' 3rd')
|
96
|
+
text.gsub!(/\b\s+fourth\b/, ' 4th')
|
97
|
+
text = Numerizer.numerize(text)
|
98
|
+
text.gsub!(/<=(quarters?)=>/, '\1')
|
99
|
+
text
|
100
|
+
end
|
101
|
+
|
102
|
+
# Guess a specific time within the given span.
|
103
|
+
#
|
104
|
+
# span - The Chronic::Span object to calcuate a guess from.
|
105
|
+
#
|
106
|
+
# Returns a new Time object.
|
107
|
+
def guess(span, mode = :middle)
|
108
|
+
return span if not mode
|
109
|
+
return span.begin + span.width / 2 if span.width > 1 and (mode == true or mode == :middle)
|
110
|
+
return span.end if mode == :end
|
111
|
+
span.begin
|
112
|
+
end
|
113
|
+
|
114
|
+
# List of Handler definitions. See Chronic.parse for a list of options this
|
115
|
+
# method accepts.
|
116
|
+
#
|
117
|
+
# options - An optional Hash of configuration options.
|
118
|
+
#
|
119
|
+
# Returns a Hash of Handler definitions.
|
120
|
+
def definitions(options = {})
|
121
|
+
SpanDictionary.new(options).definitions
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
|
127
|
+
def validate_options!(options)
|
128
|
+
given = options.keys.map(&:to_s).sort
|
129
|
+
allowed = DEFAULT_OPTIONS.keys.map(&:to_s).sort
|
130
|
+
non_permitted = given - allowed
|
131
|
+
raise ArgumentError, "Unsupported option(s): #{non_permitted.join(', ')}" if non_permitted.any?
|
132
|
+
end
|
133
|
+
|
134
|
+
def tag(tokens, options)
|
135
|
+
[DayName, MonthName, SeasonName, DaySpecial, TimeSpecial, DayPortion, Grabber, Pointer, Rational, Keyword, Separator, Scalar, Ordinal, Sign, Unit, TimeZoneTag].each do |tok|
|
136
|
+
tok.scan(tokens, options)
|
137
|
+
end
|
138
|
+
previous = nil
|
139
|
+
tokens.select! do |token|
|
140
|
+
if token.tagged?
|
141
|
+
if !previous or !token.tags.first.kind_of?(Separator) or token.tags.first.class != previous.class
|
142
|
+
previous = token.tags.first
|
143
|
+
true
|
144
|
+
else
|
145
|
+
false
|
146
|
+
end
|
147
|
+
else
|
148
|
+
false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|