chronic-davispuh 0.10.2.v0.1da32066b3f46f2506b3471e39557497e34afa27

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +3 -0
  5. data/HISTORY.md +243 -0
  6. data/LICENSE +21 -0
  7. data/README.md +185 -0
  8. data/Rakefile +68 -0
  9. data/chronic.gemspec +27 -0
  10. data/lib/chronic.rb +122 -0
  11. data/lib/chronic/arrow.rb +270 -0
  12. data/lib/chronic/date.rb +272 -0
  13. data/lib/chronic/definition.rb +208 -0
  14. data/lib/chronic/dictionary.rb +36 -0
  15. data/lib/chronic/handler.rb +44 -0
  16. data/lib/chronic/handlers/anchor.rb +65 -0
  17. data/lib/chronic/handlers/arrow.rb +84 -0
  18. data/lib/chronic/handlers/date.rb +270 -0
  19. data/lib/chronic/handlers/date_time.rb +72 -0
  20. data/lib/chronic/handlers/general.rb +130 -0
  21. data/lib/chronic/handlers/narrow.rb +54 -0
  22. data/lib/chronic/handlers/time.rb +167 -0
  23. data/lib/chronic/handlers/time_zone.rb +50 -0
  24. data/lib/chronic/objects/anchor_object.rb +263 -0
  25. data/lib/chronic/objects/arrow_object.rb +27 -0
  26. data/lib/chronic/objects/date_object.rb +164 -0
  27. data/lib/chronic/objects/date_time_object.rb +64 -0
  28. data/lib/chronic/objects/handler_object.rb +81 -0
  29. data/lib/chronic/objects/narrow_object.rb +85 -0
  30. data/lib/chronic/objects/time_object.rb +96 -0
  31. data/lib/chronic/objects/time_zone_object.rb +27 -0
  32. data/lib/chronic/parser.rb +154 -0
  33. data/lib/chronic/span.rb +32 -0
  34. data/lib/chronic/tag.rb +84 -0
  35. data/lib/chronic/tags/day_name.rb +34 -0
  36. data/lib/chronic/tags/day_portion.rb +33 -0
  37. data/lib/chronic/tags/day_special.rb +30 -0
  38. data/lib/chronic/tags/grabber.rb +29 -0
  39. data/lib/chronic/tags/keyword.rb +63 -0
  40. data/lib/chronic/tags/month_name.rb +39 -0
  41. data/lib/chronic/tags/ordinal.rb +52 -0
  42. data/lib/chronic/tags/pointer.rb +28 -0
  43. data/lib/chronic/tags/rational.rb +35 -0
  44. data/lib/chronic/tags/scalar.rb +101 -0
  45. data/lib/chronic/tags/season_name.rb +31 -0
  46. data/lib/chronic/tags/separator.rb +130 -0
  47. data/lib/chronic/tags/sign.rb +35 -0
  48. data/lib/chronic/tags/time_special.rb +34 -0
  49. data/lib/chronic/tags/time_zone.rb +56 -0
  50. data/lib/chronic/tags/unit.rb +174 -0
  51. data/lib/chronic/time.rb +141 -0
  52. data/lib/chronic/time_zone.rb +80 -0
  53. data/lib/chronic/token.rb +61 -0
  54. data/lib/chronic/token_group.rb +271 -0
  55. data/lib/chronic/tokenizer.rb +42 -0
  56. data/lib/chronic/version.rb +3 -0
  57. data/test/helper.rb +12 -0
  58. data/test/test_chronic.rb +190 -0
  59. data/test/test_daylight_savings.rb +98 -0
  60. data/test/test_handler.rb +113 -0
  61. data/test/test_parsing.rb +1520 -0
  62. data/test/test_span.rb +23 -0
  63. data/test/test_token.rb +31 -0
  64. metadata +218 -0
@@ -0,0 +1,68 @@
1
+ $:.unshift File.expand_path('../lib', __FILE__)
2
+ require 'chronic/version'
3
+
4
+ def version
5
+ Chronic::VERSION
6
+ end
7
+
8
+ def do_test
9
+ require 'chronic'
10
+ $:.unshift './test'
11
+ Dir.glob('test/test_*.rb').each { |t| require File.basename(t) }
12
+ end
13
+
14
+ def open_command
15
+ case RUBY_PLATFORM
16
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
17
+ 'start'
18
+ when /darwin|mac os/
19
+ 'open'
20
+ else
21
+ 'xdg-open'
22
+ end
23
+ end
24
+
25
+ desc 'Run tests'
26
+ task :test do
27
+ do_test
28
+ end
29
+
30
+ desc 'Generate SimpleCov test coverage and open in your browser'
31
+ task :coverage do
32
+ require 'simplecov'
33
+ FileUtils.rm_rf('./coverage')
34
+ SimpleCov.command_name 'Unit Tests'
35
+ SimpleCov.at_exit do
36
+ SimpleCov.result.format!
37
+ sh "#{open_command} #{SimpleCov.coverage_path}/index.html"
38
+ end
39
+ SimpleCov.start
40
+ do_test
41
+ end
42
+
43
+ desc 'Open an irb session preloaded with this library'
44
+ task :console do
45
+ sh 'irb -Ilib -rchronic'
46
+ end
47
+
48
+ desc "Release Chronic version #{version}"
49
+ task :release => :build do
50
+ unless `git branch` =~ /^\* master$/
51
+ puts "You must be on the master branch to release!"
52
+ exit!
53
+ end
54
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
55
+ sh "git tag v#{version}"
56
+ sh "git push origin master"
57
+ sh "git push origin v#{version}"
58
+ sh "gem push pkg/chronic-#{version}.gem"
59
+ end
60
+
61
+ desc 'Build a gem from the gemspec'
62
+ task :build do
63
+ FileUtils.mkdir_p 'pkg'
64
+ sh 'gem build chronic.gemspec'
65
+ FileUtils.mv("./chronic-#{version}.gem", "pkg")
66
+ end
67
+
68
+ task :default => :test
@@ -0,0 +1,27 @@
1
+ $:.unshift File.expand_path('../lib', __FILE__)
2
+ require 'chronic/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'chronic-davispuh'
6
+ s.version = Chronic::VERSION
7
+ s.rubyforge_project = 'chronic'
8
+ s.summary = 'Natural language date/time parsing.'
9
+ s.description = 'Chronic is a natural language date/time parser written in pure Ruby.'
10
+ s.authors = ['Tom Preston-Werner', 'Lee Jarvis']
11
+ s.email = ['tom@mojombo.com', 'ljjarvis@gmail.com']
12
+ s.homepage = 'http://github.com/mojombo/chronic'
13
+ s.license = 'MIT'
14
+ s.rdoc_options = ['--charset=UTF-8']
15
+ s.extra_rdoc_files = %w[README.md HISTORY.md LICENSE]
16
+ s.files = `git ls-files`.split($/)
17
+ s.test_files = `git ls-files -- test`.split($/)
18
+
19
+ s.add_runtime_dependency 'numerizer', '~> 0.2'
20
+ s.add_runtime_dependency 'tzinfo'
21
+ s.add_runtime_dependency 'TimezoneParser'
22
+
23
+ s.add_development_dependency 'rake', '~> 10'
24
+ s.add_development_dependency 'simplecov', '~> 0'
25
+ s.add_development_dependency 'minitest', '~> 5.0'
26
+ s.add_development_dependency 'activesupport', '~> 4'
27
+ end
@@ -0,0 +1,122 @@
1
+ require 'time'
2
+ require 'date'
3
+ require 'numerizer'
4
+
5
+ require 'chronic/version'
6
+
7
+ require 'tzinfo'
8
+ require 'timezone_parser'
9
+
10
+ require 'chronic/parser'
11
+ require 'chronic/date'
12
+ require 'chronic/time'
13
+ require 'chronic/time_zone'
14
+ require 'chronic/arrow'
15
+
16
+ require 'chronic/handler'
17
+ require 'chronic/span'
18
+ require 'chronic/token'
19
+ require 'chronic/token_group'
20
+ require 'chronic/tokenizer'
21
+
22
+ require 'chronic/tag'
23
+ require 'chronic/tags/day_name'
24
+ require 'chronic/tags/day_portion'
25
+ require 'chronic/tags/day_special'
26
+ require 'chronic/tags/grabber'
27
+ require 'chronic/tags/keyword'
28
+ require 'chronic/tags/month_name'
29
+ require 'chronic/tags/ordinal'
30
+ require 'chronic/tags/pointer'
31
+ require 'chronic/tags/rational'
32
+ require 'chronic/tags/scalar'
33
+ require 'chronic/tags/season_name'
34
+ require 'chronic/tags/separator'
35
+ require 'chronic/tags/sign'
36
+ require 'chronic/tags/time_special'
37
+ require 'chronic/tags/time_zone'
38
+ require 'chronic/tags/unit'
39
+
40
+ # Parse natural language dates and times into Time or Chronic::Span objects.
41
+ #
42
+ # Examples:
43
+ #
44
+ # require 'chronic'
45
+ #
46
+ # Time.now #=> Sun Aug 27 23:18:25 PDT 2006
47
+ #
48
+ # Chronic.parse('tomorrow')
49
+ # #=> Mon Aug 28 12:00:00 PDT 2006
50
+ #
51
+ # Chronic.parse('monday', :context => :past)
52
+ # #=> Mon Aug 21 12:00:00 PDT 2006
53
+ module Chronic
54
+
55
+ class << self
56
+
57
+ # Returns true when debug mode is enabled.
58
+ attr_accessor :debug
59
+
60
+ # Examples:
61
+ #
62
+ # require 'chronic'
63
+ # require 'active_support/time'
64
+ #
65
+ # Time.zone = 'UTC'
66
+ # Chronic.time_class = Time.zone
67
+ # Chronic.parse('June 15 2006 at 5:54 AM')
68
+ # # => Thu, 15 Jun 2006 05:45:00 UTC +00:00
69
+ #
70
+ # Returns The Time class Chronic uses internally.
71
+ attr_accessor :time_class
72
+ end
73
+
74
+ self.debug = false
75
+ self.time_class = ::Time
76
+
77
+
78
+ # Parses a string containing a natural language date or time.
79
+ #
80
+ # If the parser can find a date or time, either a Time or Chronic::Span
81
+ # will be returned (depending on the value of `:guess`). If no
82
+ # date or time can be found, `nil` will be returned.
83
+ #
84
+ # text - The String text to parse.
85
+ # opts - An optional Hash of configuration options passed to Parser::new.
86
+ def self.parse(text, options = {})
87
+ Parser.new(options).parse(text)
88
+ end
89
+
90
+ # Construct a new time object determining possible month overflows
91
+ # and leap years.
92
+ #
93
+ # year - Integer year.
94
+ # month - Integer month.
95
+ # day - Integer day.
96
+ # hour - Integer hour.
97
+ # minute - Integer minute.
98
+ # second - Integer second.
99
+ #
100
+ # Returns a new Time object constructed from these params.
101
+ def self.construct(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, timezone = nil)
102
+ day, hour, minute, second = Time::normalize(day, hour, minute, second)
103
+
104
+ year, month, day = Date::add_day(year, month, day, 0) if day > 28
105
+ year, month = Date::add_month(year, month, 0) if month > 12
106
+
107
+ if Chronic.time_class.name == 'Date'
108
+ Chronic.time_class.new(year, month, day)
109
+ elsif not Chronic.time_class.respond_to?(:new) or (RUBY_VERSION.to_f < 1.9 and Chronic.time_class.name == 'Time')
110
+ Chronic.time_class.local(year, month, day, hour, minute, second)
111
+ else
112
+ if timezone and timezone.respond_to?(:to_offset)
113
+ offset = timezone.to_offset(year, month, day, hour, minute, second)
114
+ else
115
+ offset = timezone
116
+ end
117
+ offset = TimeZone::normalize_offset(offset) if Chronic.time_class.name == 'DateTime'
118
+ Chronic.time_class.new(year, month, day, hour, minute, second, offset)
119
+ end
120
+ end
121
+
122
+ end
@@ -0,0 +1,270 @@
1
+ module Chronic
2
+ class Arrow
3
+ BASE_UNITS = [
4
+ :years,
5
+ :months,
6
+ :days,
7
+ :hours,
8
+ :minutes,
9
+ :seconds
10
+ ]
11
+
12
+ UNITS = BASE_UNITS + [
13
+ :miliseconds,
14
+ :mornings,
15
+ :noons,
16
+ :afternoons,
17
+ :evenings,
18
+ :nights,
19
+ :midnights,
20
+ :weeks,
21
+ :weekdays,
22
+ :wdays,
23
+ :weekends,
24
+ :fortnights,
25
+ :quarters,
26
+ :seasons
27
+ ]
28
+
29
+ PRECISION = [
30
+ :miliseconds,
31
+ :seconds,
32
+ :minutes,
33
+ :noons,
34
+ :midnights,
35
+ :hours,
36
+ :mornings,
37
+ :afternoons,
38
+ :evenings,
39
+ :nights,
40
+ :days,
41
+ :weekdays,
42
+ :weekends,
43
+ :wdays,
44
+ :weeks,
45
+ :fortnights,
46
+ :months,
47
+ :quarters,
48
+ :seasons,
49
+ :years
50
+ ]
51
+
52
+ attr_reader :begin
53
+ attr_reader :end
54
+
55
+ UNITS.each { |u| attr_reader u }
56
+
57
+ attr_reader :pointer
58
+ attr_reader :order
59
+
60
+ def initialize(arrows, options)
61
+ @options = options
62
+ @ordered = []
63
+ build(arrows)
64
+ end
65
+
66
+ def range
67
+ @begin..@end
68
+ end
69
+
70
+ def width
71
+ @end - @begin
72
+ end
73
+
74
+ def overlap?(r2)
75
+ r1 = range
76
+ (r1.begin <= r2.end) and (r2.begin <= r1.end)
77
+ end
78
+
79
+ def translate_unit(unit)
80
+ unit.to_s + 's'
81
+ end
82
+
83
+ def to_span(span, timezone = nil)
84
+ precision = PRECISION.length - 1
85
+ ds = Date::split(span.begin)
86
+ ds += Time::split(span.begin)
87
+ case span.precision
88
+ when :year
89
+ precision = PRECISION.index(:years)
90
+ ds[3] = ds[4] = ds[5] = 0
91
+ when :month
92
+ precision = PRECISION.index(:months)
93
+ ds[3] = ds[4] = ds[5] = 0
94
+ when :day, :day_special
95
+ precision = PRECISION.index(:days)
96
+ ds[3] = ds[4] = ds[5] = 0
97
+ when :hour
98
+ precision = PRECISION.index(:hours)
99
+ when :minute
100
+ precision = PRECISION.index(:minutes)
101
+ when :second
102
+ precision = PRECISION.index(:seconds)
103
+ when :time_special
104
+ precision = 0
105
+ end
106
+
107
+ sign = (@pointer == :past) ? -1 : 1
108
+ @ordered.each do |data|
109
+ unit = data.first
110
+ count = data[1]
111
+ special = data.last
112
+ case unit
113
+ when *BASE_UNITS
114
+ ds[BASE_UNITS.index(unit)] += sign * count
115
+ ds[2], ds[3], ds[4], ds[5] = Time::normalize(ds[2], ds[3], ds[4], ds[5])
116
+ ds[0], ds[1], ds[2] = Date::normalize(ds[0], ds[1], ds[2])
117
+ precision = update_precision(precision, unit)
118
+ when :seasons
119
+ # TODO
120
+ raise "Not Implemented Arrow #{unit}"
121
+ when :quarters
122
+ ds[0], quarter = Date::add_quarter(ds[0], Date::get_quarter_index(ds[1]), sign * count)
123
+ ds[1] = Date::QUARTERS[quarter]
124
+ ds[2] = 1
125
+ ds[3], ds[4], ds[5] = 0, 0, 0
126
+ precision = PRECISION.index(unit)
127
+ when :fortnights
128
+ ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], sign * count * Date::FORTNIGHT_DAYS)
129
+ precision = update_precision(precision, unit)
130
+ when :weeks
131
+ ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], sign * count * Date::WEEK_DAYS)
132
+ precision = update_precision(precision, unit)
133
+ when :wdays
134
+ date = Chronic.time_class.new(ds[0], ds[1], ds[2])
135
+ diff = Date::wday_diff(date, special, sign)
136
+ ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], diff + sign * (count - 1) * Date::WEEK_DAYS)
137
+ precision = update_precision(precision, unit)
138
+ when :weekends
139
+ date = Chronic.time_class.new(ds[0], ds[1], ds[2])
140
+ diff = Date::wday_diff(date, Date::DAYS[:saturday], sign)
141
+ ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], diff + sign * (count - 1) * Date::WEEK_DAYS)
142
+ ds[3], ds[4], ds[5] = 0, 0, 0
143
+ precision = update_precision(precision, unit)
144
+ when :weekdays
145
+ # TODO
146
+ raise "Not Implemented Arrow #{unit}"
147
+ when :days
148
+ # TODO
149
+ raise "Not Implemented Arrow #{unit}"
150
+ when :mornings, :noons, :afternoons, :evenings, :nights, :midnights
151
+ name = n(unit)
152
+ count -= 1 if @pointer == :past and ds[3] > Time::SPECIAL[name].end
153
+ count += 1 if @pointer == :future and ds[3] < Time::SPECIAL[name].begin
154
+ ds[0], ds[1], ds[2] = Date::add_day(ds[0], ds[1], ds[2], sign * count)
155
+ ds[3], ds[4], ds[5] = Time::SPECIAL[name].begin, 0, 0
156
+ precision = PRECISION.index(unit)
157
+ when :miliseconds
158
+ # TODO
159
+ raise "Not Implemented Arrow #{unit}"
160
+ end
161
+ end
162
+
163
+ de = ds.dup
164
+ case PRECISION[precision]
165
+ when :miliseconds
166
+ de[4], de[5] = Time::add_second(ds[4], ds[5], 0.001)
167
+ when :seconds
168
+ de[4], de[5] = Time::add_second(ds[4], ds[5])
169
+ when :minutes
170
+ de[3], de[4] = Time::add_minute(ds[3], ds[4])
171
+ de[5] = 0
172
+ when :mornings, :noons, :afternoons, :evenings, :nights, :midnights
173
+ name = n(PRECISION[precision])
174
+ de[3], de[4], de[5] = Time::SPECIAL[name].end, 0, 0
175
+ when :hours
176
+ de[2], de[3] = Time::add_hour(ds[2], ds[3])
177
+ de[4] = de[5] = 0
178
+ when :days, :weekdays, :wdays
179
+ de[0], de[1], de[2] = Date::add_day(ds[0], ds[1], ds[2])
180
+ de[3] = de[4] = de[5] = 0
181
+ when :weekends
182
+ end_date = Chronic.time_class.new(ds[0], ds[1], ds[2])
183
+ diff = Date::wday_diff(end_date, Date::DAYS[:monday])
184
+ de[0], de[1], de[2] = Date::add_day(ds[0], ds[1], ds[2], diff)
185
+ de[3] = de[4] = de[5] = 0
186
+ when :weeks
187
+ end_date = Chronic.time_class.new(ds[0], ds[1], ds[2])
188
+ diff = Date::wday_diff(end_date, Date::DAYS[@options[:week_start]])
189
+ de[0], de[1], de[2] = Date::add_day(ds[0], ds[1], ds[2], diff)
190
+ de[3] = de[4] = de[5] = 0
191
+ when :fortnights
192
+ end_date = Chronic.time_class.new(ds[0], ds[1], ds[2])
193
+ diff = Date::wday_diff(end_date, Date::DAYS[@options[:week_start]])
194
+ de[0], de[1], de[2] = Date::add_day(ds[0], ds[1], ds[2], diff + Date::WEEK_DAYS)
195
+ de[3] = de[4] = de[5] = 0
196
+ when :months
197
+ de[0], de[1] = Date::add_month(ds[0], ds[1])
198
+ de[2] = 1
199
+ de[3] = de[4] = de[5] = 0
200
+ when :quarters
201
+ de[0], quarter = Date::add_quarter(ds[0], Date::get_quarter_index(ds[1]))
202
+ de[1] = Date::QUARTERS[quarter]
203
+ de[2] = 1
204
+ de[3] = de[4] = de[5] = 0
205
+ when :seasons
206
+ # TODO
207
+ raise "Not Implemented Arrow #{PRECISION[precision]}"
208
+ when :years
209
+ de[0] += 1
210
+ de[1] = de[2] = 1
211
+ de[3] = de[4] = de[5] = 0
212
+ end
213
+ utc_offset = nil
214
+ end_utc_offset = nil
215
+ if timezone
216
+ utc_offset = timezone.to_offset(ds[0], ds[1], ds[2], ds[3], ds[4], ds[5])
217
+ end_utc_offset = timezone.to_offset(de[0], de[1], de[2], de[3], de[4], de[5])
218
+ end
219
+ span_start = Chronic.construct(ds[0], ds[1], ds[2], ds[3], ds[4], ds[5], utc_offset)
220
+ span_end = Chronic.construct(de[0], de[1], de[2], de[3], de[4], de[5], utc_offset)
221
+ Span.new(span_start, span_end)
222
+ end
223
+
224
+ def to_s
225
+ full = []
226
+ UNITS.each do |unit|
227
+ count = instance_variable_get('@'+unit.to_s)
228
+ full << [unit, count].join(' ') unless count.nil?
229
+ end
230
+ unless full.empty?
231
+ 'Arrow => ' + @pointer.inspect + ' ' + full.join(', ')
232
+ else
233
+ 'Arrow => empty'
234
+ end
235
+ end
236
+
237
+ protected
238
+
239
+ def n(unit)
240
+ s = unit.to_s
241
+ s.to_s[0, s.length - 1].to_sym
242
+ end
243
+
244
+ def v(v)
245
+ '@'+v.to_s
246
+ end
247
+
248
+ def update_precision(precision, unit)
249
+ new_precision = PRECISION.index(unit)
250
+ precision = new_precision if new_precision < precision
251
+ precision
252
+ end
253
+
254
+ def build(arrows)
255
+ arrows.each do |arrow|
256
+ @begin = arrow.begin if @begin.nil? or @begin > arrow.begin
257
+ @end = arrow.end if @end.nil? or @end < arrow.end
258
+ @pointer = arrow.pointer if @pointer.nil? and not arrow.pointer.nil?
259
+ next if arrow.unit.nil?
260
+ unit = translate_unit(arrow.unit)
261
+ raise "Uknown unit #{unit.to_sym.inspect}" unless UNITS.include?(unit.to_sym)
262
+ count = instance_variable_get(v(unit))
263
+ count ||= 0
264
+ @ordered << [unit.to_sym, arrow.count, arrow.special]
265
+ instance_variable_set(v(unit), count+arrow.count)
266
+ end
267
+ end
268
+
269
+ end
270
+ end