chronic 0.6.2 → 0.7.0
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/.gitignore +1 -0
- data/HISTORY.md +36 -0
- data/README.md +7 -3
- data/Rakefile +3 -5
- data/chronic.gemspec +3 -0
- data/lib/chronic/chronic.rb +78 -63
- data/lib/chronic/grabber.rb +9 -7
- data/lib/chronic/handler.rb +10 -12
- data/lib/chronic/handlers.rb +130 -3
- data/lib/chronic/ordinal.rb +12 -9
- data/lib/chronic/pointer.rb +9 -7
- data/lib/chronic/repeater.rb +29 -19
- data/lib/chronic/repeaters/repeater_day_portion.rb +29 -15
- data/lib/chronic/repeaters/repeater_time.rb +19 -19
- data/lib/chronic/repeaters/repeater_year.rb +1 -1
- data/lib/chronic/scalar.rb +30 -23
- data/lib/chronic/season.rb +1 -12
- data/lib/chronic/separator.rb +21 -15
- data/lib/chronic/tag.rb +5 -11
- data/lib/chronic/time_zone.rb +9 -7
- data/lib/chronic/token.rb +12 -10
- data/lib/chronic.rb +50 -44
- data/test/helper.rb +8 -2
- data/test/{test_Chronic.rb → test_chronic.rb} +6 -6
- data/test/{test_DaylightSavings.rb → test_daylight_savings.rb} +1 -1
- data/test/{test_Handler.rb → test_handler.rb} +1 -1
- data/test/{test_MiniDate.rb → test_mini_date.rb} +9 -9
- data/test/{test_Numerizer.rb → test_numerizer.rb} +1 -1
- data/test/test_parsing.rb +173 -14
- data/test/{test_RepeaterDayName.rb → test_repeater_day_name.rb} +1 -1
- data/test/test_repeater_day_portion.rb +254 -0
- data/test/{test_RepeaterFortnight.rb → test_repeater_fortnight.rb} +1 -1
- data/test/{test_RepeaterHour.rb → test_repeater_hour.rb} +1 -1
- data/test/{test_RepeaterMinute.rb → test_repeater_minute.rb} +1 -1
- data/test/{test_RepeaterMonth.rb → test_repeater_month.rb} +1 -1
- data/test/{test_RepeaterMonthName.rb → test_repeater_month_name.rb} +1 -1
- data/test/{test_RepeaterSeason.rb → test_repeater_season.rb} +1 -1
- data/test/{test_RepeaterTime.rb → test_repeater_time.rb} +1 -1
- data/test/{test_RepeaterWeek.rb → test_repeater_week.rb} +1 -1
- data/test/{test_RepeaterWeekday.rb → test_repeater_weekday.rb} +1 -1
- data/test/{test_RepeaterWeekend.rb → test_repeater_weekend.rb} +1 -1
- data/test/{test_RepeaterYear.rb → test_repeater_year.rb} +1 -1
- data/test/{test_Span.rb → test_span.rb} +1 -1
- data/test/{test_Token.rb → test_token.rb} +1 -1
- metadata +96 -83
- data/.gemtest +0 -0
- data/.yardopts +0 -3
data/.gitignore
CHANGED
data/HISTORY.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
# HEAD
|
|
2
|
+
|
|
3
|
+
* Support parsing EXIF date format (#112)
|
|
4
|
+
* Start using minitest for testing
|
|
5
|
+
* Ensure periods are interpreted as colons (#81).
|
|
6
|
+
* Support month/day and day/month parsing (#59).
|
|
7
|
+
* Support day(scalar)-month(name)-year(scalar) (#99).
|
|
8
|
+
* Handle text starting with 'a' or 'an' (#101, @steveburkett).
|
|
9
|
+
* Ensure post medium timestamps are correctly formatted (#89)
|
|
10
|
+
|
|
11
|
+
# 0.6.7 / 2012-01-31
|
|
12
|
+
|
|
13
|
+
* Handle day, month names with scalar day and year (Joe Fiorini)
|
|
14
|
+
* Ensure 31st parses correctly with day names (Joe Fiorini)
|
|
15
|
+
|
|
16
|
+
# 0.6.6 / 2011-11-23
|
|
17
|
+
|
|
18
|
+
* `Chronic.parse('thur')` no longer returns `nil` (@harold)
|
|
19
|
+
|
|
20
|
+
# 0.6.5 / 2011-11-04
|
|
21
|
+
|
|
22
|
+
* Fix bug when parsing ordinal repeaters (#73)
|
|
23
|
+
* Added handler support for day_name month_name (@imme5150)
|
|
24
|
+
* Fix bug when parsing strings prefixed with PM
|
|
25
|
+
|
|
26
|
+
# 0.6.4 / 2011-08-08
|
|
27
|
+
|
|
28
|
+
* Fixed bug where 'noon' was parsed as 00:00 rather than 12:00
|
|
29
|
+
with :ambiguous_time_range => :none (Vladimir Chernis)
|
|
30
|
+
* Add support for handling '2009 May 22nd'
|
|
31
|
+
* Add the ability to handle scalar-day/repeater-month-name as well as ordinals
|
|
32
|
+
|
|
33
|
+
# 0.6.3 / 2011-08-01
|
|
34
|
+
|
|
35
|
+
* Ensure 'thu' is parsed as Thursday for 1.8.7 generic timestamp
|
|
36
|
+
|
|
1
37
|
# 0.6.2 / 2011-07-28
|
|
2
38
|
|
|
3
39
|
* Ensure specific endian handlers are prioritised over normal date handlers
|
data/README.md
CHANGED
|
@@ -46,6 +46,10 @@ You can parse strings containing a natural language date using the
|
|
|
46
46
|
|
|
47
47
|
Chronic.parse('may 27th', :guess => false)
|
|
48
48
|
#=> Sun May 27 00:00:00 PDT 2007..Mon May 28 00:00:00 PDT 2007
|
|
49
|
+
|
|
50
|
+
Chronic.parse('6/4/2012', :endian_precedence => :little)
|
|
51
|
+
#=> Fri Apr 06 00:00:00 PDT 2012
|
|
52
|
+
|
|
49
53
|
|
|
50
54
|
See `Chronic.parse` for detailed usage instructions.
|
|
51
55
|
|
|
@@ -86,6 +90,7 @@ Simple
|
|
|
86
90
|
Complex
|
|
87
91
|
|
|
88
92
|
* 3 years ago
|
|
93
|
+
* a year ago
|
|
89
94
|
* 5 months before now
|
|
90
95
|
* 7 hours ago
|
|
91
96
|
* 7 days from now
|
|
@@ -162,14 +167,13 @@ If you'd like to hack on Chronic, start by forking the repo on GitHub:
|
|
|
162
167
|
|
|
163
168
|
https://github.com/mojombo/chronic
|
|
164
169
|
|
|
165
|
-
|
|
166
|
-
your changes merged back into core is as follows:
|
|
170
|
+
The best way to get your changes merged back into core is as follows:
|
|
167
171
|
|
|
168
172
|
1. Clone down your fork
|
|
169
173
|
1. Create a thoughtfully named topic branch to contain your change
|
|
170
174
|
1. Hack away
|
|
171
175
|
1. Add tests and make sure everything still passes by running `rake`
|
|
172
|
-
1. Ensure your tests pass in multiple timezones
|
|
176
|
+
1. Ensure your tests pass in multiple timezones. ie `TZ=utc rake` `TZ=BST rake`
|
|
173
177
|
1. If you are adding new functionality, document it in the README
|
|
174
178
|
1. Do not change the version number, we will do that on our end
|
|
175
179
|
1. If necessary, rebase your commits into logical chunks, without errors
|
data/Rakefile
CHANGED
|
@@ -5,11 +5,9 @@ def version
|
|
|
5
5
|
contents[/VERSION = "([^"]+)"/, 1]
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
test.
|
|
11
|
-
test.pattern = 'test/**/test_*.rb'
|
|
12
|
-
test.verbose = true
|
|
8
|
+
task :test do
|
|
9
|
+
$:.unshift './test'
|
|
10
|
+
Dir.glob('test/test_*.rb').each { |t| require File.basename(t) }
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
desc "Generate RCov test coverage and open in your browser"
|
data/chronic.gemspec
CHANGED
|
@@ -14,4 +14,7 @@ Gem::Specification.new do |s|
|
|
|
14
14
|
s.extra_rdoc_files = %w[README.md HISTORY.md LICENSE]
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
|
16
16
|
s.test_files = `git ls-files -- test`.split("\n")
|
|
17
|
+
|
|
18
|
+
s.add_development_dependency 'rake'
|
|
19
|
+
s.add_development_dependency 'minitest'
|
|
17
20
|
end
|
data/lib/chronic/chronic.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
|
|
3
|
+
# Returns a Hash of default configuration options.
|
|
3
4
|
DEFAULT_OPTIONS = {
|
|
4
5
|
:context => :future,
|
|
5
6
|
:now => nil,
|
|
@@ -11,53 +12,43 @@ module Chronic
|
|
|
11
12
|
|
|
12
13
|
class << self
|
|
13
14
|
|
|
14
|
-
# Parses a string containing a natural language date or time
|
|
15
|
+
# Parses a string containing a natural language date or time.
|
|
15
16
|
#
|
|
16
17
|
# If the parser can find a date or time, either a Time or Chronic::Span
|
|
17
18
|
# will be returned (depending on the value of `:guess`). If no
|
|
18
|
-
# date or time can be found, `nil` will be returned
|
|
19
|
+
# date or time can be found, `nil` will be returned.
|
|
19
20
|
#
|
|
20
|
-
#
|
|
21
|
+
# text - The String text to parse.
|
|
22
|
+
# opts - An optional Hash of configuration options:
|
|
23
|
+
# :context - If your string represents a birthday, you can set
|
|
24
|
+
# this value to :past and if an ambiguous string is
|
|
25
|
+
# given, it will assume it is in the past.
|
|
26
|
+
# :now - Time, all computations will be based off of time
|
|
27
|
+
# instead of Time.now.
|
|
28
|
+
# :guess - By default the parser will guess a single point in time
|
|
29
|
+
# for the given date or time. If you'd rather have the
|
|
30
|
+
# entire time span returned, set this to false
|
|
31
|
+
# and a Chronic::Span will be returned.
|
|
32
|
+
# :ambiguous_time_range - If an Integer is given, ambiguous times
|
|
33
|
+
# (like 5:00) will be assumed to be within the range of
|
|
34
|
+
# that time in the AM to that time in the PM. For
|
|
35
|
+
# example, if you set it to `7`, then the parser will
|
|
36
|
+
# look for the time between 7am and 7pm. In the case of
|
|
37
|
+
# 5:00, it would assume that means 5:00pm. If `:none`
|
|
38
|
+
# is given, no assumption will be made, and the first
|
|
39
|
+
# matching instance of that time will be used.
|
|
40
|
+
# :endian_precedence - By default, Chronic will parse "03/04/2011"
|
|
41
|
+
# as the fourth day of the third month. Alternatively you
|
|
42
|
+
# can tell Chronic to parse this as the third day of the
|
|
43
|
+
# fourth month by setting this to [:little, :middle].
|
|
44
|
+
# :ambiguous_year_future_bias - When parsing two digit years
|
|
45
|
+
# (ie 79) unlike Rubys Time class, Chronic will attempt
|
|
46
|
+
# to assume the full year using this figure. Chronic will
|
|
47
|
+
# look x amount of years into the future and past. If the
|
|
48
|
+
# two digit year is `now + x years` it's assumed to be the
|
|
49
|
+
# future, `now - x years` is assumed to be the past.
|
|
21
50
|
#
|
|
22
|
-
#
|
|
23
|
-
# * If your string represents a birthday, you can set `:context` to
|
|
24
|
-
# `:past` and if an ambiguous string is given, it will assume it is
|
|
25
|
-
# in the past. Specify `:future` or omit to set a future context.
|
|
26
|
-
#
|
|
27
|
-
# @option opts [Object] :now (Time.now)
|
|
28
|
-
# * By setting `:now` to a Time, all computations will be based off of
|
|
29
|
-
# that time instead of `Time.now`. If set to nil, Chronic will use
|
|
30
|
-
# `Time.now`
|
|
31
|
-
#
|
|
32
|
-
# @option opts [Boolean] :guess (true)
|
|
33
|
-
# * By default, the parser will guess a single point in time for the
|
|
34
|
-
# given date or time. If you'd rather have the entire time span
|
|
35
|
-
# returned, set `:guess` to `false` and a {Chronic::Span} will
|
|
36
|
-
# be returned
|
|
37
|
-
#
|
|
38
|
-
# @option opts [Integer] :ambiguous_time_range (6)
|
|
39
|
-
# * If an Integer is given, ambiguous times (like 5:00) will be
|
|
40
|
-
# assumed to be within the range of that time in the AM to that time
|
|
41
|
-
# in the PM. For example, if you set it to `7`, then the parser
|
|
42
|
-
# will look for the time between 7am and 7pm. In the case of 5:00, it
|
|
43
|
-
# would assume that means 5:00pm. If `:none` is given, no
|
|
44
|
-
# assumption will be made, and the first matching instance of that
|
|
45
|
-
# time will be used
|
|
46
|
-
#
|
|
47
|
-
# @option opts [Array] :endian_precedence ([:middle, :little])
|
|
48
|
-
# * By default, Chronic will parse "03/04/2011" as the fourth day
|
|
49
|
-
# of the third month. Alternatively you can tell Chronic to parse
|
|
50
|
-
# this as the third day of the fourth month by altering the
|
|
51
|
-
# `:endian_precedence` to `[:little, :middle]`
|
|
52
|
-
#
|
|
53
|
-
# @option opts [Integer] :ambiguous_year_future_bias (50)
|
|
54
|
-
# * When parsing two digit years (ie 79) unlike Rubys Time class,
|
|
55
|
-
# Chronic will attempt to assume the full year using this figure.
|
|
56
|
-
# Chronic will look x amount of years into the future and past. If
|
|
57
|
-
# the two digit year is `now + x years` it's assumed to be the
|
|
58
|
-
# future, `now - x years` is assumed to be the past
|
|
59
|
-
#
|
|
60
|
-
# @return [Time, Chronic::Span, nil]
|
|
51
|
+
# Returns a new Time object, or Chronic::Span if :guess option is false.
|
|
61
52
|
def parse(text, opts={})
|
|
62
53
|
options = DEFAULT_OPTIONS.merge opts
|
|
63
54
|
|
|
@@ -87,14 +78,17 @@ module Chronic
|
|
|
87
78
|
end
|
|
88
79
|
end
|
|
89
80
|
|
|
90
|
-
# Clean up the specified text ready for parsing
|
|
81
|
+
# Clean up the specified text ready for parsing.
|
|
91
82
|
#
|
|
92
83
|
# Clean up the string by stripping unwanted characters, converting
|
|
93
84
|
# idioms to their canonical form, converting number words to numbers
|
|
94
85
|
# (three => 3), and converting ordinal words to numeric
|
|
95
86
|
# ordinals (third => 3rd)
|
|
96
87
|
#
|
|
97
|
-
#
|
|
88
|
+
# text - The String text to normalize.
|
|
89
|
+
#
|
|
90
|
+
# Examples:
|
|
91
|
+
#
|
|
98
92
|
# Chronic.pre_normalize('first day in May')
|
|
99
93
|
# #=> "1st day in may"
|
|
100
94
|
#
|
|
@@ -104,21 +98,22 @@ module Chronic
|
|
|
104
98
|
# Chronic.pre_normalize('one hundred and thirty six days from now')
|
|
105
99
|
# #=> "136 days future this second"
|
|
106
100
|
#
|
|
107
|
-
#
|
|
108
|
-
# @return [String] A new string ready for Chronic to parse
|
|
101
|
+
# Returns a new String ready for Chronic to parse.
|
|
109
102
|
def pre_normalize(text)
|
|
110
103
|
text = text.to_s.downcase
|
|
111
|
-
text.gsub!(
|
|
104
|
+
text.gsub!(/\./, ':')
|
|
105
|
+
text.gsub!(/['"]/, '')
|
|
112
106
|
text.gsub!(/,/, ' ')
|
|
107
|
+
text.gsub!(/^second /, '2nd ')
|
|
113
108
|
text.gsub!(/\bsecond (of|day|month|hour|minute|second)\b/, '2nd \1')
|
|
114
109
|
text = Numerizer.numerize(text)
|
|
115
110
|
text.gsub!(/ \-(\d{4})\b/, ' tzminus\1')
|
|
116
111
|
text.gsub!(/([\/\-\,\@])/) { ' ' + $1 + ' ' }
|
|
117
|
-
text.gsub!(
|
|
112
|
+
text.gsub!(/(?:^|\s)0(\d+:\d+\s*pm?\b)/, ' \1')
|
|
118
113
|
text.gsub!(/\btoday\b/, 'this day')
|
|
119
114
|
text.gsub!(/\btomm?orr?ow\b/, 'next day')
|
|
120
115
|
text.gsub!(/\byesterday\b/, 'last day')
|
|
121
|
-
text.gsub!(/\bnoon\b/, '12:
|
|
116
|
+
text.gsub!(/\bnoon\b/, '12:00pm')
|
|
122
117
|
text.gsub!(/\bmidnight\b/, '24:00')
|
|
123
118
|
text.gsub!(/\bnow\b/, 'this second')
|
|
124
119
|
text.gsub!(/\b(?:ago|before(?: now)?)\b/, 'past')
|
|
@@ -129,23 +124,26 @@ module Chronic
|
|
|
129
124
|
text.gsub!(/\b\d+:?\d*[ap]\b/,'\0m')
|
|
130
125
|
text.gsub!(/(\d)([ap]m|oclock)\b/, '\1 \2')
|
|
131
126
|
text.gsub!(/\b(hence|after|from)\b/, 'future')
|
|
127
|
+
text.gsub!(/^\s?an? /i, '1 ')
|
|
128
|
+
text.gsub!(/\b(\d{4}):(\d{2}):(\d{2})\b/, '\1 / \2 / \3') # DTOriginal
|
|
132
129
|
text
|
|
133
130
|
end
|
|
134
131
|
|
|
135
|
-
# Convert number words to numbers (three => 3, fourth => 4th)
|
|
132
|
+
# Convert number words to numbers (three => 3, fourth => 4th).
|
|
133
|
+
#
|
|
134
|
+
# text - The String to convert.
|
|
136
135
|
#
|
|
137
|
-
#
|
|
138
|
-
# @param [String] text The string to convert
|
|
139
|
-
# @return [String] A new string with words converted to numbers
|
|
136
|
+
# Returns a new String with words converted to numbers.
|
|
140
137
|
def numericize_numbers(text)
|
|
141
138
|
warn "Chronic.numericize_numbers will be deprecated in version 0.7.0. Please use Chronic::Numerizer.numerize instead"
|
|
142
139
|
Numerizer.numerize(text)
|
|
143
140
|
end
|
|
144
141
|
|
|
145
|
-
# Guess a specific time within the given span
|
|
142
|
+
# Guess a specific time within the given span.
|
|
146
143
|
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
144
|
+
# span - The Chronic::Span object to calcuate a guess from.
|
|
145
|
+
#
|
|
146
|
+
# Returns a new Time object.
|
|
149
147
|
def guess(span)
|
|
150
148
|
if span.width > 1
|
|
151
149
|
span.begin + (span.width / 2)
|
|
@@ -154,11 +152,13 @@ module Chronic
|
|
|
154
152
|
end
|
|
155
153
|
end
|
|
156
154
|
|
|
157
|
-
# List of
|
|
158
|
-
# method accepts
|
|
155
|
+
# List of Handler definitions. See #parse for a list of options this
|
|
156
|
+
# method accepts.
|
|
157
|
+
#
|
|
158
|
+
# options - An optional Hash of configuration options:
|
|
159
|
+
# :endian_precedence -
|
|
159
160
|
#
|
|
160
|
-
#
|
|
161
|
-
# @return [Hash] A Hash of Handler definitions
|
|
161
|
+
# Returns A Hash of Handler definitions.
|
|
162
162
|
def definitions(options={})
|
|
163
163
|
options[:endian_precedence] ||= [:middle, :little]
|
|
164
164
|
|
|
@@ -169,6 +169,9 @@ module Chronic
|
|
|
169
169
|
|
|
170
170
|
:date => [
|
|
171
171
|
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),
|
|
172
|
+
Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day], :handle_rdn_rmn_sd),
|
|
173
|
+
Handler.new([:repeater_day_name, :repeater_month_name, :scalar_day, :scalar_year], :handle_rdn_rmn_sd_sy),
|
|
174
|
+
Handler.new([:repeater_day_name, :repeater_month_name, :ordinal_day], :handle_rdn_rmn_od),
|
|
172
175
|
Handler.new([:scalar_year, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_day, :repeater_time, :time_zone], :handle_sy_sm_sd_t_tz),
|
|
173
176
|
Handler.new([:repeater_month_name, :scalar_day, :scalar_year], :handle_rmn_sd_sy),
|
|
174
177
|
Handler.new([:repeater_month_name, :ordinal_day, :scalar_year], :handle_rmn_od_sy),
|
|
@@ -179,11 +182,15 @@ module Chronic
|
|
|
179
182
|
Handler.new([:repeater_month_name, :ordinal_day, :separator_at?, 'time?'], :handle_rmn_od),
|
|
180
183
|
Handler.new([:ordinal_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_od_rmn_sy),
|
|
181
184
|
Handler.new([:ordinal_day, :repeater_month_name, :separator_at?, 'time?'], :handle_od_rmn),
|
|
185
|
+
Handler.new([:scalar_year, :repeater_month_name, :ordinal_day], :handle_sy_rmn_od),
|
|
182
186
|
Handler.new([:repeater_time, :repeater_day_portion?, :separator_on?, :repeater_month_name, :ordinal_day], :handle_rmn_od_on),
|
|
183
187
|
Handler.new([:repeater_month_name, :scalar_year], :handle_rmn_sy),
|
|
184
188
|
Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy),
|
|
189
|
+
Handler.new([:scalar_day, :repeater_month_name, :separator_at?, 'time?'], :handle_sd_rmn),
|
|
185
190
|
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, :
|
|
191
|
+
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day], :handle_sm_sd),
|
|
192
|
+
Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_year], :handle_sm_sy),
|
|
193
|
+
Handler.new([:scalar_day, :separator_slash_or_dash, :repeater_month_name, :separator_slash_or_dash, :scalar_year], :handle_sm_rmn_sy)
|
|
187
194
|
],
|
|
188
195
|
|
|
189
196
|
# tonight at 7pm
|
|
@@ -224,9 +231,17 @@ module Chronic
|
|
|
224
231
|
@definitions
|
|
225
232
|
end
|
|
226
233
|
|
|
227
|
-
# Construct a time
|
|
234
|
+
# Construct a new time object determining possible month overflows
|
|
235
|
+
# and leap years.
|
|
236
|
+
#
|
|
237
|
+
# year - Integer year.
|
|
238
|
+
# month - Integer month.
|
|
239
|
+
# day - Integer day.
|
|
240
|
+
# hour - Integer hour.
|
|
241
|
+
# minute - Integer minute.
|
|
242
|
+
# second - Integer second.
|
|
228
243
|
#
|
|
229
|
-
#
|
|
244
|
+
# Returns a new Time object constructed from these params.
|
|
230
245
|
def construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0)
|
|
231
246
|
if second >= 60
|
|
232
247
|
minute += second / 60
|
data/lib/chronic/grabber.rb
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
class Grabber < Tag
|
|
3
3
|
|
|
4
|
-
# Scan an Array of
|
|
5
|
-
# each token
|
|
4
|
+
# Scan an Array of Tokens and apply any necessary Grabber tags to
|
|
5
|
+
# each token.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# tokens - An Array of Token objects to scan.
|
|
8
|
+
# options - The Hash of options specified in Chronic::parse.
|
|
9
|
+
#
|
|
10
|
+
# Returns an Array of Token objects.
|
|
10
11
|
def self.scan(tokens, options)
|
|
11
12
|
tokens.each do |token|
|
|
12
13
|
if t = scan_for_all(token) then token.tag(t); next end
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
17
|
+
# token - The Token object to scan.
|
|
18
|
+
#
|
|
19
|
+
# Returns a new Grabber object.
|
|
18
20
|
def self.scan_for_all(token)
|
|
19
21
|
scan_for token, self,
|
|
20
22
|
{
|
data/lib/chronic/handler.rb
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
class Handler
|
|
3
3
|
|
|
4
|
-
# @return [Array] A list of patterns
|
|
5
4
|
attr_reader :pattern
|
|
6
5
|
|
|
7
|
-
# @return [Symbol] The method which handles this list of patterns.
|
|
8
|
-
# This method should exist inside the {Handlers} module
|
|
9
6
|
attr_reader :handler_method
|
|
10
7
|
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# are matched.
|
|
8
|
+
# pattern - An Array of patterns to match tokens against.
|
|
9
|
+
# handler_method - A Symbole representing the method to be invoked
|
|
10
|
+
# when patterns are matched.
|
|
14
11
|
def initialize(pattern, handler_method)
|
|
15
12
|
@pattern = pattern
|
|
16
13
|
@handler_method = handler_method
|
|
17
14
|
end
|
|
18
15
|
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
16
|
+
# tokens - An Array of tokens to process.
|
|
17
|
+
# definitions - A Hash of definitions to check against.
|
|
18
|
+
#
|
|
19
|
+
# Returns true if a match is found.
|
|
23
20
|
def match(tokens, definitions)
|
|
24
21
|
token_index = 0
|
|
25
22
|
|
|
@@ -70,8 +67,9 @@ module Chronic
|
|
|
70
67
|
Handlers.send(@handler_method, tokens, options)
|
|
71
68
|
end
|
|
72
69
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
70
|
+
# other - The other Handler object to compare.
|
|
71
|
+
#
|
|
72
|
+
# Returns true if these Handlers match.
|
|
75
73
|
def ==(other)
|
|
76
74
|
@pattern == other.pattern
|
|
77
75
|
end
|
data/lib/chronic/handlers.rb
CHANGED
|
@@ -59,6 +59,32 @@ module Chronic
|
|
|
59
59
|
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
def handle_sy_rmn_od(tokens, options)
|
|
63
|
+
year = tokens[0].get_tag(ScalarYear).type
|
|
64
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
|
65
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
|
66
|
+
time_tokens = tokens.last(tokens.size - 3)
|
|
67
|
+
|
|
68
|
+
return if month_overflow?(year, month, day)
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
day_start = Chronic.time_class.local(year, month, day)
|
|
72
|
+
day_or_time(day_start, time_tokens, options)
|
|
73
|
+
rescue ArgumentError
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Handle scalar-day/repeater-month-name
|
|
79
|
+
def handle_sd_rmn(tokens, options)
|
|
80
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
81
|
+
day = tokens[0].get_tag(ScalarDay).type
|
|
82
|
+
|
|
83
|
+
return if month_overflow?(Chronic.now.year, month.index, day)
|
|
84
|
+
|
|
85
|
+
handle_m_d(month, day, tokens[2..tokens.size], options)
|
|
86
|
+
end
|
|
87
|
+
|
|
62
88
|
# Handle repeater-month-name/ordinal-day with separator-on
|
|
63
89
|
def handle_rmn_od_on(tokens, options)
|
|
64
90
|
if tokens.size > 3
|
|
@@ -198,6 +224,27 @@ module Chronic
|
|
|
198
224
|
handle_sm_sd_sy(new_tokens + time_tokens, options)
|
|
199
225
|
end
|
|
200
226
|
|
|
227
|
+
# Handle scalar-day/scalar-month AND scalar-month/scalar-day
|
|
228
|
+
def handle_sm_sd(tokens, options)
|
|
229
|
+
month = tokens[0].get_tag(ScalarMonth).type
|
|
230
|
+
day = tokens[1].get_tag(ScalarDay).type
|
|
231
|
+
year = Chronic.now.year
|
|
232
|
+
|
|
233
|
+
if Array(options[:endian_precedence]).first == :little
|
|
234
|
+
day, month = month, day
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
return if month_overflow?(year, month, day)
|
|
238
|
+
|
|
239
|
+
begin
|
|
240
|
+
start_time = Chronic.time_class.local(year, month, day)
|
|
241
|
+
end_time = Chronic.time_class.local(year, month, day + 1)
|
|
242
|
+
Span.new(start_time, end_time)
|
|
243
|
+
rescue ArgumentError
|
|
244
|
+
nil
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
201
248
|
# Handle scalar-month/scalar-year
|
|
202
249
|
def handle_sm_sy(tokens, options)
|
|
203
250
|
month = tokens[0].get_tag(ScalarMonth).type
|
|
@@ -219,6 +266,66 @@ module Chronic
|
|
|
219
266
|
end
|
|
220
267
|
end
|
|
221
268
|
|
|
269
|
+
# Handle RepeaterDayName RepeaterMonthName OrdinalDay
|
|
270
|
+
def handle_rdn_rmn_od(tokens, options)
|
|
271
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
272
|
+
day = tokens[2].get_tag(OrdinalDay).type
|
|
273
|
+
year = Chronic.now.year
|
|
274
|
+
|
|
275
|
+
return if month_overflow?(year, month.index, day)
|
|
276
|
+
|
|
277
|
+
begin
|
|
278
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
279
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
|
280
|
+
Span.new(start_time, end_time)
|
|
281
|
+
rescue ArgumentError
|
|
282
|
+
nil
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Handle RepeaterDayName RepeaterMonthName ScalarDay
|
|
287
|
+
def handle_rdn_rmn_sd(tokens, options)
|
|
288
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
289
|
+
day = tokens[2].get_tag(ScalarDay).type
|
|
290
|
+
year = Chronic.now.year
|
|
291
|
+
|
|
292
|
+
return if month_overflow?(year, month.index, day)
|
|
293
|
+
|
|
294
|
+
begin
|
|
295
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
296
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
|
297
|
+
Span.new(start_time, end_time)
|
|
298
|
+
rescue ArgumentError
|
|
299
|
+
nil
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Handle RepeaterDayName RepeaterMonthName ScalarDay ScalarYear
|
|
304
|
+
def handle_rdn_rmn_sd_sy(tokens, options)
|
|
305
|
+
month = tokens[1].get_tag(RepeaterMonthName)
|
|
306
|
+
day = tokens[2].get_tag(ScalarDay).type
|
|
307
|
+
year = tokens[3].get_tag(ScalarYear).type
|
|
308
|
+
|
|
309
|
+
return if month_overflow?(year, month.index, day)
|
|
310
|
+
|
|
311
|
+
begin
|
|
312
|
+
start_time = Chronic.time_class.local(year, month.index, day)
|
|
313
|
+
end_time = time_with_rollover(year, month.index, day + 1)
|
|
314
|
+
Span.new(start_time, end_time)
|
|
315
|
+
rescue ArgumentError
|
|
316
|
+
nil
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def handle_sm_rmn_sy(tokens, options)
|
|
321
|
+
day = tokens[0].get_tag(ScalarDay).type
|
|
322
|
+
month = tokens[1].get_tag(RepeaterMonthName).index
|
|
323
|
+
year = tokens[2].get_tag(ScalarYear).type
|
|
324
|
+
time = Chronic.time_class.local(year, month, day)
|
|
325
|
+
end_time = Chronic.time_class.local(year, month, day + 1)
|
|
326
|
+
Span.new(time, end_time)
|
|
327
|
+
end
|
|
328
|
+
|
|
222
329
|
# anchors
|
|
223
330
|
|
|
224
331
|
# Handle repeaters
|
|
@@ -241,7 +348,7 @@ module Chronic
|
|
|
241
348
|
repeater = tokens[1].get_tag(Repeater)
|
|
242
349
|
pointer = tokens[2].get_tag(Pointer).type
|
|
243
350
|
|
|
244
|
-
repeater.offset(span, distance, pointer)
|
|
351
|
+
repeater.offset(span, distance, pointer) if repeater.respond_to?(:offset)
|
|
245
352
|
end
|
|
246
353
|
|
|
247
354
|
# Handle scalar/repeater/pointer
|
|
@@ -276,7 +383,7 @@ module Chronic
|
|
|
276
383
|
ordinal.times do
|
|
277
384
|
span = repeater.next(:future)
|
|
278
385
|
|
|
279
|
-
if span.begin
|
|
386
|
+
if span.begin >= outer_span.end
|
|
280
387
|
span = nil
|
|
281
388
|
break
|
|
282
389
|
end
|
|
@@ -339,7 +446,11 @@ module Chronic
|
|
|
339
446
|
raise ChronicPain, "Invalid grabber"
|
|
340
447
|
end
|
|
341
448
|
|
|
342
|
-
|
|
449
|
+
if Chronic.debug
|
|
450
|
+
puts "Handler-class: #{head.class}"
|
|
451
|
+
puts "--#{outer_span}"
|
|
452
|
+
end
|
|
453
|
+
|
|
343
454
|
find_within(repeaters, outer_span, pointer)
|
|
344
455
|
end
|
|
345
456
|
|
|
@@ -353,6 +464,8 @@ module Chronic
|
|
|
353
464
|
else
|
|
354
465
|
day > RepeaterMonth::MONTH_DAYS[month - 1]
|
|
355
466
|
end
|
|
467
|
+
rescue ArgumentError
|
|
468
|
+
false
|
|
356
469
|
end
|
|
357
470
|
|
|
358
471
|
# Recursively finds repeaters within other repeaters.
|
|
@@ -371,6 +484,20 @@ module Chronic
|
|
|
371
484
|
end
|
|
372
485
|
end
|
|
373
486
|
|
|
487
|
+
def time_with_rollover(year, month, day)
|
|
488
|
+
date_parts =
|
|
489
|
+
if month_overflow?(year, month, day)
|
|
490
|
+
if month == 12
|
|
491
|
+
[year + 1, 1, 1]
|
|
492
|
+
else
|
|
493
|
+
[year, month + 1, 1]
|
|
494
|
+
end
|
|
495
|
+
else
|
|
496
|
+
[year, month, day]
|
|
497
|
+
end
|
|
498
|
+
Chronic.time_class.local(*date_parts)
|
|
499
|
+
end
|
|
500
|
+
|
|
374
501
|
def dealias_and_disambiguate_times(tokens, options)
|
|
375
502
|
# handle aliases of am/pm
|
|
376
503
|
# 5:00 in the morning -> 5:00 am
|
data/lib/chronic/ordinal.rb
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
class Ordinal < Tag
|
|
3
3
|
|
|
4
|
-
# Scan an Array of
|
|
5
|
-
# each token
|
|
4
|
+
# Scan an Array of Token objects and apply any necessary Ordinal
|
|
5
|
+
# tags to each token.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# tokens - An Array of tokens to scan.
|
|
8
|
+
# options - The Hash of options specified in Chronic::parse.
|
|
9
|
+
#
|
|
10
|
+
# Returns an Array of tokens.
|
|
10
11
|
def self.scan(tokens, options)
|
|
11
12
|
tokens.each do |token|
|
|
12
13
|
if t = scan_for_ordinals(token) then token.tag(t) end
|
|
@@ -14,14 +15,16 @@ module Chronic
|
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
#
|
|
18
|
-
#
|
|
18
|
+
# token - The Token object we want to scan.
|
|
19
|
+
#
|
|
20
|
+
# Returns a new Ordinal object.
|
|
19
21
|
def self.scan_for_ordinals(token)
|
|
20
22
|
Ordinal.new($1.to_i) if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
|
21
23
|
end
|
|
22
24
|
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
+
# token - The Token object we want to scan.
|
|
26
|
+
#
|
|
27
|
+
# Returns a new Ordinal object.
|
|
25
28
|
def self.scan_for_days(token)
|
|
26
29
|
if token.word =~ /^(\d*)(st|nd|rd|th)$/
|
|
27
30
|
unless $1.to_i > 31 || $1.to_i < 1
|
data/lib/chronic/pointer.rb
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
module Chronic
|
|
2
2
|
class Pointer < Tag
|
|
3
3
|
|
|
4
|
-
# Scan an Array of
|
|
5
|
-
# each token
|
|
4
|
+
# Scan an Array of Token objects and apply any necessary Pointer
|
|
5
|
+
# tags to each token.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# tokens - An Array of tokens to scan.
|
|
8
|
+
# options - The Hash of options specified in Chronic::parse.
|
|
9
|
+
#
|
|
10
|
+
# Returns an Array of tokens.
|
|
10
11
|
def self.scan(tokens, options)
|
|
11
12
|
tokens.each do |token|
|
|
12
13
|
if t = scan_for_all(token) then token.tag(t) end
|
|
13
14
|
end
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
#
|
|
17
|
-
#
|
|
17
|
+
# token - The Token object we want to scan.
|
|
18
|
+
#
|
|
19
|
+
# Returns a new Pointer object.
|
|
18
20
|
def self.scan_for_all(token)
|
|
19
21
|
scan_for token, self,
|
|
20
22
|
{
|