chronic 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY.md +9 -2
- data/Rakefile +0 -8
- data/chronic.gemspec +1 -1
- data/lib/chronic.rb +60 -47
- data/lib/chronic/chronic.rb +221 -90
- data/lib/chronic/grabber.rb +10 -1
- data/lib/chronic/handlers.rb +44 -186
- data/lib/chronic/mini_date.rb +6 -2
- data/lib/chronic/ordinal.rb +12 -1
- data/lib/chronic/pointer.rb +10 -1
- data/lib/chronic/repeater.rb +20 -1
- data/lib/chronic/repeaters/repeater_month.rb +13 -1
- data/lib/chronic/scalar.rb +29 -1
- data/lib/chronic/separator.rb +18 -1
- data/lib/chronic/time_zone.rb +10 -1
- data/lib/chronic/token.rb +14 -4
- data/test/test_Chronic.rb +52 -2
- data/test/test_RepeaterMonth.rb +4 -0
- metadata +2 -2
data/HISTORY.md
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
# 0.4.
|
1
|
+
# 0.4.2 / 2011-06-07
|
2
|
+
|
3
|
+
* Fix MonthRepeater for fetching past month offsets when current day is
|
4
|
+
later than the last day of a past month (ie on 29th of March when parsing
|
5
|
+
`last month` Chronic would return March instead of February. Now Chronic
|
6
|
+
returns the last day of the past month)
|
7
|
+
|
8
|
+
# 0.4.1 / 2011-06-05
|
2
9
|
|
3
10
|
* Fix MiniDate ranges for parsing seasons (Thomas Walpole)
|
4
11
|
|
@@ -27,7 +34,7 @@
|
|
27
34
|
* Fix undefined variable in RepeaterHour (Ryan Garver)
|
28
35
|
* Added support for parsing 'Fourty' as another mis-spelling (Lee Reilly)
|
29
36
|
* Added ordinal format support: ie 'February 14th, 2004' (Jeff Felchner)
|
30
|
-
* Fix dates when working with daylight saving times Mike Mangino
|
37
|
+
* Fix dates when working with daylight saving times (Mike Mangino)
|
31
38
|
|
32
39
|
# 0.3.0 / 2010-10-22
|
33
40
|
|
data/Rakefile
CHANGED
@@ -54,14 +54,6 @@ task :coverage do
|
|
54
54
|
sh "open coverage/index.html"
|
55
55
|
end
|
56
56
|
|
57
|
-
require 'rake/rdoctask'
|
58
|
-
Rake::RDocTask.new do |rdoc|
|
59
|
-
rdoc.rdoc_dir = 'rdoc'
|
60
|
-
rdoc.title = "#{name} #{version}"
|
61
|
-
rdoc.rdoc_files.include('README*')
|
62
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
63
|
-
end
|
64
|
-
|
65
57
|
desc "Open an irb session preloaded with this library"
|
66
58
|
task :console do
|
67
59
|
sh "irb -Ilib -rchronic"
|
data/chronic.gemspec
CHANGED
data/lib/chronic.rb
CHANGED
@@ -1,33 +1,82 @@
|
|
1
|
-
|
1
|
+
require 'time'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
# Parse natural language dates and times into Time or {Chronic::Span} objects
|
2
5
|
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# Purpose: Parse natural language dates and times into Time or
|
6
|
-
# Chronic::Span objects
|
6
|
+
# @example
|
7
|
+
# require 'chronic'
|
7
8
|
#
|
8
|
-
|
9
|
-
|
9
|
+
# Time.now #=> Sun Aug 27 23:18:25 PDT 2006
|
10
|
+
#
|
11
|
+
# Chronic.parse('tomorrow')
|
12
|
+
# #=> Mon Aug 28 12:00:00 PDT 2006
|
13
|
+
#
|
14
|
+
# Chronic.parse('monday', :context => :past)
|
15
|
+
# #=> Mon Aug 21 12:00:00 PDT 2006
|
16
|
+
#
|
17
|
+
# Chronic.parse('this tuesday 5:00')
|
18
|
+
# #=> Tue Aug 29 17:00:00 PDT 2006
|
19
|
+
#
|
20
|
+
# Chronic.parse('this tuesday 5:00', :ambiguous_time_range => :none)
|
21
|
+
# #=> Tue Aug 29 05:00:00 PDT 2006
|
22
|
+
#
|
23
|
+
# Chronic.parse('may 27th', :now => Time.local(2000, 1, 1))
|
24
|
+
# #=> Sat May 27 12:00:00 PDT 2000
|
25
|
+
#
|
26
|
+
# Chronic.parse('may 27th', :guess => false)
|
27
|
+
# #=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
|
28
|
+
#
|
29
|
+
# @author Tom Preston-Werner, Lee Jarvis
|
10
30
|
module Chronic
|
11
|
-
VERSION = "0.4.
|
31
|
+
VERSION = "0.4.2"
|
12
32
|
|
13
33
|
class << self
|
34
|
+
|
35
|
+
# @return [Boolean] true when debug mode is enabled
|
14
36
|
attr_accessor :debug
|
37
|
+
|
38
|
+
# @example
|
39
|
+
# require 'chronic'
|
40
|
+
# require 'active_support/time'
|
41
|
+
#
|
42
|
+
# Time.zone = 'UTC'
|
43
|
+
# Chronic.time_class = Time.zone
|
44
|
+
# Chronic.parse('June 15 2006 at 5:54 AM')
|
45
|
+
# # => Thu, 15 Jun 2006 05:45:00 UTC +00:00
|
46
|
+
#
|
47
|
+
# @return [Time] The time class Chronic uses internally
|
15
48
|
attr_accessor :time_class
|
49
|
+
|
50
|
+
# The current Time Chronic is using to base from
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# Time.now #=> 2011-06-06 14:13:43 +0100
|
54
|
+
# Chronic.parse('yesterday') #=> 2011-06-05 12:00:00 +0100
|
55
|
+
#
|
56
|
+
# now = Time.local(2025, 12, 24)
|
57
|
+
# Chronic.parse('tomorrow', :now => now) #=> 2025-12-25 12:00:00 +0000
|
58
|
+
#
|
59
|
+
# @return [Time, nil]
|
60
|
+
attr_accessor :now
|
16
61
|
end
|
17
62
|
|
18
63
|
self.debug = false
|
19
64
|
self.time_class = Time
|
20
65
|
end
|
21
66
|
|
22
|
-
require 'time'
|
23
|
-
require 'date'
|
24
|
-
|
25
67
|
require 'chronic/chronic'
|
26
68
|
require 'chronic/handlers'
|
27
69
|
require 'chronic/mini_date'
|
28
70
|
require 'chronic/tag'
|
29
71
|
require 'chronic/span'
|
30
72
|
require 'chronic/token'
|
73
|
+
require 'chronic/grabber'
|
74
|
+
require 'chronic/pointer'
|
75
|
+
require 'chronic/scalar'
|
76
|
+
require 'chronic/ordinal'
|
77
|
+
require 'chronic/separator'
|
78
|
+
require 'chronic/time_zone'
|
79
|
+
require 'chronic/numerizer'
|
31
80
|
|
32
81
|
require 'chronic/repeater'
|
33
82
|
require 'chronic/repeaters/repeater_year'
|
@@ -47,42 +96,6 @@ require 'chronic/repeaters/repeater_minute'
|
|
47
96
|
require 'chronic/repeaters/repeater_second'
|
48
97
|
require 'chronic/repeaters/repeater_time'
|
49
98
|
|
50
|
-
require 'chronic/grabber'
|
51
|
-
require 'chronic/pointer'
|
52
|
-
require 'chronic/scalar'
|
53
|
-
require 'chronic/ordinal'
|
54
|
-
require 'chronic/separator'
|
55
|
-
require 'chronic/time_zone'
|
56
|
-
|
57
|
-
require 'chronic/numerizer'
|
58
|
-
|
59
|
-
# class Time
|
60
|
-
# def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
61
|
-
# # extra_seconds = second > 60 ? second - 60 : 0
|
62
|
-
# # extra_minutes = minute > 59 ? minute - 59 : 0
|
63
|
-
# # extra_hours = hour > 23 ? hour - 23 : 0
|
64
|
-
# # extra_days = day >
|
65
|
-
#
|
66
|
-
# if month > 12
|
67
|
-
# if month % 12 == 0
|
68
|
-
# year += (month - 12) / 12
|
69
|
-
# month = 12
|
70
|
-
# else
|
71
|
-
# year += month / 12
|
72
|
-
# month = month % 12
|
73
|
-
# end
|
74
|
-
# end
|
75
|
-
#
|
76
|
-
# base = Time.local(year, month)
|
77
|
-
# puts base
|
78
|
-
# offset = ((day - 1) * 24 * 60 * 60) + (hour * 60 * 60) + (minute * 60) + second
|
79
|
-
# puts offset.to_s
|
80
|
-
# date = base + offset
|
81
|
-
# puts date
|
82
|
-
# date
|
83
|
-
# end
|
84
|
-
# end
|
85
|
-
|
86
99
|
class Time
|
87
100
|
def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
88
101
|
if second >= 60
|
data/lib/chronic/chronic.rb
CHANGED
@@ -11,76 +11,76 @@ module Chronic
|
|
11
11
|
|
12
12
|
class << self
|
13
13
|
|
14
|
-
# Parses a string containing a natural language date or time
|
15
|
-
# can find a date or time, either a Time or Chronic::Span will be returned
|
16
|
-
# (depending on the value of <tt>:guess</tt>). If no date or time can be found,
|
17
|
-
# +nil+ will be returned.
|
14
|
+
# Parses a string containing a natural language date or time
|
18
15
|
#
|
19
|
-
#
|
16
|
+
# If the parser can find a date or time, either a Time or Chronic::Span
|
17
|
+
# will be returned (depending on the value of `:guess`). If no
|
18
|
+
# date or time can be found, `nil` will be returned
|
20
19
|
#
|
21
|
-
# [
|
22
|
-
#
|
20
|
+
# @option opts [Symbol] :context (:future)
|
21
|
+
# * If your string represents a birthday, you can set `:context` to
|
22
|
+
# `:past` and if an ambiguous string is given, it will assume it is
|
23
|
+
# in the past. Specify `:future` or omit to set a future context.
|
23
24
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
25
|
+
# @option opts [Object] :now (Time.now)
|
26
|
+
# * By setting `:now` to a Time, all computations will be based off of
|
27
|
+
# that time instead of `Time.now`. If set to nil, Chronic will use
|
28
|
+
# `Time.now`
|
27
29
|
#
|
28
|
-
# [
|
29
|
-
#
|
30
|
+
# @option opts [Boolean] :guess (true)
|
31
|
+
# * By default, the parser will guess a single point in time for the
|
32
|
+
# given date or time. If you'd rather have the entire time span
|
33
|
+
# returned, set `:guess` to `false` and a {Chronic::Span} will
|
34
|
+
# be returned
|
30
35
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
# [<tt>:guess</tt>]
|
35
|
-
# +true+ or +false+ (defaults to +true+)
|
36
|
-
#
|
37
|
-
# By default, the parser will guess a single point in time for the
|
38
|
-
# given date or time. If you'd rather have the entire time span returned,
|
39
|
-
# set <tt>:guess</tt> to +false+ and a Chronic::Span will be returned.
|
40
|
-
#
|
41
|
-
# [<tt>:ambiguous_time_range</tt>]
|
42
|
-
# Integer or <tt>:none</tt> (defaults to <tt>6</tt> (6am-6pm))
|
43
|
-
#
|
44
|
-
# If an Integer is given, ambiguous times (like 5:00) will be
|
36
|
+
# @option opts [Integer] :ambiguous_time_range (6)
|
37
|
+
# * If an Integer is given, ambiguous times (like 5:00) will be
|
45
38
|
# assumed to be within the range of that time in the AM to that time
|
46
|
-
# in the PM. For example, if you set it to
|
47
|
-
# look for the time between 7am and 7pm. In the case of 5:00, it
|
48
|
-
# assume that means 5:00pm. If
|
49
|
-
# will be made, and the first matching instance of that
|
50
|
-
# be used
|
39
|
+
# in the PM. For example, if you set it to `7`, then the parser
|
40
|
+
# will look for the time between 7am and 7pm. In the case of 5:00, it
|
41
|
+
# would assume that means 5:00pm. If `:none` is given, no
|
42
|
+
# assumption will be made, and the first matching instance of that
|
43
|
+
# time will be used
|
51
44
|
#
|
52
|
-
# [
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# By default, Chronic will parse "03/04/2011" as the fourth day
|
45
|
+
# @option opts [Array] :endian_precedence ([:middle, :little])
|
46
|
+
# * By default, Chronic will parse "03/04/2011" as the fourth day
|
56
47
|
# of the third month. Alternatively you can tell Chronic to parse
|
57
48
|
# this as the third day of the fourth month by altering the
|
58
|
-
#
|
59
|
-
|
49
|
+
# `:endian_precedence` to `[:little, :middle]`
|
50
|
+
#
|
51
|
+
# @option opts [Integer] :ambiguous_year_future_bias (50)
|
52
|
+
# * When parsing two digit years (ie 79) unlike Rubys Time class,
|
53
|
+
# Chronic will attempt to assume the full year using this figure.
|
54
|
+
# Chronic will look x amount of years into the future and past. If
|
55
|
+
# the two digit year is `now + x years` it's assumed to be the
|
56
|
+
# future, `now - x years` is assumed to be the past
|
57
|
+
#
|
58
|
+
# @return [Time, Chronic::Span, nil]
|
59
|
+
def parse(text, opts={})
|
60
60
|
@text = text
|
61
|
-
options = DEFAULT_OPTIONS.merge
|
61
|
+
options = DEFAULT_OPTIONS.merge opts
|
62
62
|
|
63
63
|
# ensure the specified options are valid
|
64
|
-
(
|
64
|
+
(opts.keys - DEFAULT_OPTIONS.keys).each do |key|
|
65
|
+
raise InvalidArgumentException, "#{key} is not a valid option key."
|
66
|
+
end
|
65
67
|
|
66
|
-
[:past, :future, :none].include?(options[:context])
|
68
|
+
unless [:past, :future, :none].include?(options[:context])
|
69
|
+
raise InvalidArgumentException, "Invalid context, :past/:future only"
|
70
|
+
end
|
67
71
|
|
72
|
+
options[:text] = text
|
68
73
|
options[:now] ||= Chronic.time_class.now
|
69
|
-
|
70
|
-
|
71
|
-
# put the text into a normal format to ease scanning
|
72
|
-
text = pre_normalize(text)
|
74
|
+
Chronic.now = options[:now]
|
73
75
|
|
74
76
|
# tokenize words
|
75
|
-
|
77
|
+
tokens = tokenize(text, options)
|
76
78
|
|
77
79
|
if Chronic.debug
|
78
|
-
puts "
|
79
|
-
puts "| " + @tokens.to_s
|
80
|
-
puts "+---------------------------------------------------"
|
80
|
+
puts "+#{'-' * 51}\n| #{tokens}\n+#{'-' * 51}"
|
81
81
|
end
|
82
82
|
|
83
|
-
span = tokens_to_span(
|
83
|
+
span = tokens_to_span(tokens, options)
|
84
84
|
|
85
85
|
if options[:guess]
|
86
86
|
guess span
|
@@ -89,52 +89,66 @@ module Chronic
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
|
92
|
-
# Clean up the specified
|
93
|
-
#
|
94
|
-
#
|
92
|
+
# Clean up the specified text ready for parsing
|
93
|
+
#
|
94
|
+
# Clean up the string by stripping unwanted characters, converting
|
95
|
+
# idioms to their canonical form, converting number words to numbers
|
96
|
+
# (three => 3), and converting ordinal words to numeric
|
95
97
|
# ordinals (third => 3rd)
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# Chronic.pre_normalize('first day in May')
|
101
|
+
# #=> "1st day in may"
|
102
|
+
#
|
103
|
+
# Chronic.pre_normalize('tomorrow after noon')
|
104
|
+
# #=> "next day future 12:00"
|
105
|
+
#
|
106
|
+
# Chronic.pre_normalize('one hundred and thirty six days from now')
|
107
|
+
# #=> "136 days future this second"
|
108
|
+
#
|
109
|
+
# @param [String] text The string to normalize
|
110
|
+
# @return [String] A new string ready for Chronic to parse
|
96
111
|
def pre_normalize(text) #:nodoc:
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
112
|
+
text = text.to_s.downcase
|
113
|
+
text.gsub!(/['"\.,]/, '')
|
114
|
+
text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
|
115
|
+
text = numericize_numbers(text)
|
116
|
+
text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
|
117
|
+
text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
|
118
|
+
text.gsub!(/\b0(\d+:\d+\s*pm?\b)/, '\1')
|
119
|
+
text.gsub!(/\btoday\b/, 'this day')
|
120
|
+
text.gsub!(/\btomm?orr?ow\b/, 'next day')
|
121
|
+
text.gsub!(/\byesterday\b/, 'last day')
|
122
|
+
text.gsub!(/\bnoon\b/, '12:00')
|
123
|
+
text.gsub!(/\bmidnight\b/, '24:00')
|
124
|
+
text.gsub!(/\bbefore now\b/, 'past')
|
125
|
+
text.gsub!(/\bnow\b/, 'this second')
|
126
|
+
text.gsub!(/\b(ago|before)\b/, 'past')
|
127
|
+
text.gsub!(/\bthis past\b/, 'last')
|
128
|
+
text.gsub!(/\bthis last\b/, 'last')
|
129
|
+
text.gsub!(/\b(?:in|during) the (morning)\b/, '\1')
|
130
|
+
text.gsub!(/\b(?:in the|during the|at) (afternoon|evening|night)\b/, '\1')
|
131
|
+
text.gsub!(/\btonight\b/, 'this night')
|
132
|
+
text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
|
133
|
+
text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
|
134
|
+
text.gsub!(/\b(hence|after|from)\b/, 'future')
|
135
|
+
text
|
121
136
|
end
|
122
137
|
|
123
|
-
# Convert number words to numbers (three => 3)
|
124
|
-
|
138
|
+
# Convert number words to numbers (three => 3, fourth => 4th)
|
139
|
+
#
|
140
|
+
# @see Numerizer.numerize
|
141
|
+
# @param [String] text The string to convert
|
142
|
+
# @return [String] A new string with words converted to numbers
|
143
|
+
def numericize_numbers(text)
|
125
144
|
Numerizer.numerize(text)
|
126
145
|
end
|
127
146
|
|
128
|
-
def tokenize(text, options) #:nodoc:
|
129
|
-
tokens = text.split(' ').map { |word| Token.new(word) }
|
130
|
-
[Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
|
131
|
-
tokens = tok.scan(tokens, options)
|
132
|
-
end
|
133
|
-
tokens.delete_if { |token| !token.tagged? }
|
134
|
-
end
|
135
|
-
|
136
147
|
# Guess a specific time within the given span
|
137
|
-
|
148
|
+
#
|
149
|
+
# @param [Span] span
|
150
|
+
# @return [Time]
|
151
|
+
def guess(span)
|
138
152
|
return nil if span.nil?
|
139
153
|
if span.width > 1
|
140
154
|
span.begin + (span.width / 2)
|
@@ -142,16 +156,133 @@ module Chronic
|
|
142
156
|
span.begin
|
143
157
|
end
|
144
158
|
end
|
159
|
+
|
160
|
+
# List of {Handler} definitions. See {parse} for a list of options this
|
161
|
+
# method accepts
|
162
|
+
#
|
163
|
+
# @see parse
|
164
|
+
# @return [Hash] A Hash of Handler definitions
|
165
|
+
def definitions(options={})
|
166
|
+
options[:endian_precedence] ||= [:middle, :little]
|
167
|
+
|
168
|
+
@definitions ||= {
|
169
|
+
:time => [
|
170
|
+
Handler.new([:repeater_time, :repeater_day_portion?], nil)
|
171
|
+
],
|
172
|
+
|
173
|
+
:date => [
|
174
|
+
Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :repeater_time, :separator_slash_or_dash?, :time_zone, :scalar_year], :handle_rdn_rmn_sd_t_tz_sy),
|
175
|
+
Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
|
176
|
+
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
|
177
|
+
Handler.new([:repeater_month_name, :scalar_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_sd_sy),
|
178
|
+
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year, :separator_at?, 'time?'], :handle_rmn_od_sy),
|
179
|
+
Handler.new([:repeater_month_name, :scalar_day, :separator_at?, 'time?'], :handle_rmn_sd),
|
180
|
+
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :scalar_day], :handle_rmn_sd_on),
|
181
|
+
Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
|
182
|
+
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
|
183
|
+
Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
|
184
|
+
Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
|
185
|
+
Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sy_sm_sd),
|
186
|
+
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy)
|
187
|
+
],
|
188
|
+
|
189
|
+
# tonight at 7pm
|
190
|
+
:anchor => [
|
191
|
+
Handler.new([:grabber?, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
192
|
+
Handler.new([:grabber?, :repeater, :repeater, :separator_at?, :repeater?, :repeater?], :handle_r),
|
193
|
+
Handler.new([:repeater, :grabber, :repeater], :handle_r_g_r)
|
194
|
+
],
|
195
|
+
|
196
|
+
# 3 weeks from now, in 2 months
|
197
|
+
:arrow => [
|
198
|
+
Handler.new([:scalar, :repeater, :pointer], :handle_s_r_p),
|
199
|
+
Handler.new([:pointer, :scalar, :repeater], :handle_p_s_r),
|
200
|
+
Handler.new([:scalar, :repeater, :pointer, 'anchor'], :handle_s_r_p_a)
|
201
|
+
],
|
202
|
+
|
203
|
+
# 3rd week in march
|
204
|
+
:narrow => [
|
205
|
+
Handler.new([:ordinal, :repeater, :separator_in, :repeater], :handle_o_r_s_r),
|
206
|
+
Handler.new([:ordinal, :repeater, :grabber, :repeater], :handle_o_r_g_r)
|
207
|
+
]
|
208
|
+
}
|
209
|
+
|
210
|
+
endians = [
|
211
|
+
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
|
212
|
+
Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
|
213
|
+
]
|
214
|
+
|
215
|
+
case endian = Array(options[:endian_precedence]).first
|
216
|
+
when :little
|
217
|
+
@definitions[:endian] = endians.reverse
|
218
|
+
when :middle
|
219
|
+
@definitions[:endian] = endians
|
220
|
+
else
|
221
|
+
raise InvalidArgumentException, "Unknown endian option '#{endian}'"
|
222
|
+
end
|
223
|
+
|
224
|
+
@definitions
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def tokenize(text, options)
|
230
|
+
text = pre_normalize(text)
|
231
|
+
tokens = text.split(' ').map { |word| Token.new(word) }
|
232
|
+
[Repeater, Grabber, Pointer, Scalar, Ordinal, Separator, TimeZone].each do |tok|
|
233
|
+
tokens = tok.scan(tokens, options)
|
234
|
+
end
|
235
|
+
tokens.select { |token| token.tagged? }
|
236
|
+
end
|
237
|
+
|
238
|
+
def tokens_to_span(tokens, options) #:nodoc:
|
239
|
+
definitions = definitions(options)
|
240
|
+
|
241
|
+
(definitions[:date] + definitions[:endian]).each do |handler|
|
242
|
+
if handler.match(tokens, definitions)
|
243
|
+
puts "-date" if Chronic.debug
|
244
|
+
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
245
|
+
return Handlers.send(handler.handler_method, good_tokens, options)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
definitions[:anchor].each do |handler|
|
250
|
+
if handler.match(tokens, definitions)
|
251
|
+
puts "-anchor" if Chronic.debug
|
252
|
+
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
253
|
+
return Handlers.send(handler.handler_method, good_tokens, options)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
definitions[:arrow].each do |handler|
|
258
|
+
if handler.match(tokens, definitions)
|
259
|
+
puts "-arrow" if Chronic.debug
|
260
|
+
tags = [SeparatorAt, SeparatorSlashOrDash, SeparatorComma]
|
261
|
+
good_tokens = tokens.reject { |o| tags.any? { |t| o.get_tag(t) } }
|
262
|
+
return Handlers.send(handler.handler_method, good_tokens, options)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
definitions[:narrow].each do |handler|
|
267
|
+
if handler.match(tokens, definitions)
|
268
|
+
puts "-narrow" if Chronic.debug
|
269
|
+
good_tokens = tokens.select { |o| !o.get_tag Separator }
|
270
|
+
return Handlers.send(handler.handler_method, tokens, options)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
puts "-none" if Chronic.debug
|
275
|
+
return nil
|
276
|
+
end
|
277
|
+
|
145
278
|
end
|
146
279
|
|
147
280
|
# Internal exception
|
148
|
-
class ChronicPain < Exception
|
149
|
-
|
281
|
+
class ChronicPain < Exception
|
150
282
|
end
|
151
283
|
|
152
284
|
# This exception is raised if an invalid argument is provided to
|
153
285
|
# any of Chronic's methods
|
154
286
|
class InvalidArgumentException < Exception
|
155
|
-
|
156
287
|
end
|
157
288
|
end
|