chronic 0.9.1 → 0.10.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -4
  3. data/HISTORY.md +12 -0
  4. data/README.md +8 -0
  5. data/Rakefile +28 -8
  6. data/chronic.gemspec +7 -5
  7. data/lib/chronic.rb +10 -10
  8. data/lib/chronic/date.rb +82 -0
  9. data/lib/chronic/handler.rb +34 -25
  10. data/lib/chronic/handlers.rb +22 -3
  11. data/lib/chronic/ordinal.rb +22 -20
  12. data/lib/chronic/parser.rb +31 -26
  13. data/lib/chronic/repeater.rb +18 -18
  14. data/lib/chronic/repeaters/repeater_day.rb +4 -3
  15. data/lib/chronic/repeaters/repeater_day_name.rb +5 -4
  16. data/lib/chronic/repeaters/repeater_day_portion.rb +4 -3
  17. data/lib/chronic/repeaters/repeater_fortnight.rb +4 -3
  18. data/lib/chronic/repeaters/repeater_hour.rb +4 -3
  19. data/lib/chronic/repeaters/repeater_minute.rb +4 -3
  20. data/lib/chronic/repeaters/repeater_month.rb +5 -4
  21. data/lib/chronic/repeaters/repeater_month_name.rb +4 -3
  22. data/lib/chronic/repeaters/repeater_season.rb +5 -3
  23. data/lib/chronic/repeaters/repeater_second.rb +4 -3
  24. data/lib/chronic/repeaters/repeater_time.rb +35 -25
  25. data/lib/chronic/repeaters/repeater_week.rb +4 -3
  26. data/lib/chronic/repeaters/repeater_weekday.rb +4 -3
  27. data/lib/chronic/repeaters/repeater_weekend.rb +4 -3
  28. data/lib/chronic/repeaters/repeater_year.rb +5 -4
  29. data/lib/chronic/scalar.rb +34 -69
  30. data/lib/chronic/separator.rb +107 -8
  31. data/lib/chronic/sign.rb +49 -0
  32. data/lib/chronic/tag.rb +5 -4
  33. data/lib/chronic/time.rb +40 -0
  34. data/test/helper.rb +2 -2
  35. data/test/test_chronic.rb +4 -4
  36. data/test/test_handler.rb +20 -0
  37. data/test/test_parsing.rb +72 -3
  38. data/test/test_repeater_time.rb +18 -0
  39. metadata +24 -6
@@ -0,0 +1,49 @@
1
+ module Chronic
2
+ class Sign < Tag
3
+
4
+ # Scan an Array of Token objects and apply any necessary Sign
5
+ # tags to each token.
6
+ #
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.
11
+ def self.scan(tokens, options)
12
+ tokens.each do |token|
13
+ if t = scan_for_plus(token) then token.tag(t); next end
14
+ if t = scan_for_minus(token) then token.tag(t); next end
15
+ end
16
+ end
17
+
18
+ # token - The Token object we want to scan.
19
+ #
20
+ # Returns a new SignPlus object.
21
+ def self.scan_for_plus(token)
22
+ scan_for token, SignPlus, { /^\+$/ => :plus }
23
+ end
24
+
25
+ # token - The Token object we want to scan.
26
+ #
27
+ # Returns a new SignMinus object.
28
+ def self.scan_for_minus(token)
29
+ scan_for token, SignMinus, { /^-$/ => :minus }
30
+ end
31
+
32
+ def to_s
33
+ 'sign'
34
+ end
35
+ end
36
+
37
+ class SignPlus < Sign #:nodoc:
38
+ def to_s
39
+ super << '-plus'
40
+ end
41
+ end
42
+
43
+ class SignMinus < Sign #:nodoc:
44
+ def to_s
45
+ super << '-minus'
46
+ end
47
+ end
48
+
49
+ end
@@ -6,8 +6,9 @@ module Chronic
6
6
  attr_accessor :type
7
7
 
8
8
  # type - The Symbol type of this tag.
9
- def initialize(type)
9
+ def initialize(type, options = {})
10
10
  @type = type
11
+ @options = options
11
12
  end
12
13
 
13
14
  # time - Set the start Time for this Tag.
@@ -18,13 +19,13 @@ module Chronic
18
19
  class << self
19
20
  private
20
21
 
21
- def scan_for(token, klass, items={})
22
+ def scan_for(token, klass, items={}, options = {})
22
23
  case items
23
24
  when Regexp
24
- return klass.new(token.word) if items =~ token.word
25
+ return klass.new(token.word, options) if items =~ token.word
25
26
  when Hash
26
27
  items.each do |item, symbol|
27
- return klass.new(symbol) if item =~ token.word
28
+ return klass.new(symbol, options) if item =~ token.word
28
29
  end
29
30
  end
30
31
  nil
@@ -0,0 +1,40 @@
1
+ module Chronic
2
+ class Time
3
+ HOUR_SECONDS = 3600 # 60 * 60
4
+ MINUTE_SECONDS = 60
5
+ SECOND_SECONDS = 1 # haha, awesome
6
+ SUBSECOND_SECONDS = 0.001
7
+
8
+ # Checks if given number could be hour
9
+ def self.could_be_hour?(hour)
10
+ hour >= 0 && hour <= 24
11
+ end
12
+
13
+ # Checks if given number could be minute
14
+ def self.could_be_minute?(minute)
15
+ minute >= 0 && minute <= 60
16
+ end
17
+
18
+ # Checks if given number could be second
19
+ def self.could_be_second?(second)
20
+ second >= 0 && second <= 60
21
+ end
22
+
23
+ # Checks if given number could be subsecond
24
+ def self.could_be_subsecond?(subsecond)
25
+ subsecond >= 0 && subsecond <= 999999
26
+ end
27
+
28
+ # normalize offset in seconds to offset as string +mm:ss or -mm:ss
29
+ def self.normalize_offset(offset)
30
+ return offset if offset.is_a?(String)
31
+ offset = Chronic.time_class.now.to_time.utc_offset unless offset # get current system's UTC offset if offset is nil
32
+ sign = '+'
33
+ sign = '-' if offset < 0
34
+ hours = (offset.abs / 3600).to_i.to_s.rjust(2,'0')
35
+ minutes = (offset.abs % 3600).to_s.rjust(2,'0')
36
+ sign + hours + minutes
37
+ end
38
+
39
+ end
40
+ end
@@ -5,8 +5,8 @@ end
5
5
 
6
6
  require 'minitest/autorun'
7
7
 
8
- class TestCase < MiniTest::Unit::TestCase
8
+ class TestCase < MiniTest::Test
9
9
  def self.test(name, &block)
10
10
  define_method("test_#{name.gsub(/\W/, '_')}", &block) if block
11
11
  end
12
- end
12
+ end
@@ -58,10 +58,10 @@ class TestChronic < TestCase
58
58
  def test_endian_definitions
59
59
  # middle, little
60
60
  endians = [
61
- Chronic::Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
62
- Chronic::Handler.new([:scalar_month, :separator_slash_or_dash, :scalar_day, :separator_at?, 'time?'], :handle_sm_sd),
63
- Chronic::Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_at?, 'time?'], :handle_sd_sm),
64
- Chronic::Handler.new([:scalar_day, :separator_slash_or_dash, :scalar_month, :separator_slash_or_dash, :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
61
+ Chronic::Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_day, [:separator_slash, :separator_dash], :scalar_year, :separator_at?, 'time?'], :handle_sm_sd_sy),
62
+ Chronic::Handler.new([:scalar_month, [:separator_slash, :separator_dash], :scalar_day, :separator_at?, 'time?'], :handle_sm_sd),
63
+ Chronic::Handler.new([:scalar_day, [:separator_slash, :separator_dash], :scalar_month, :separator_at?, 'time?'], :handle_sd_sm),
64
+ Chronic::Handler.new([:scalar_day, [:separator_slash, :separator_dash], :scalar_month, [:separator_slash, :separator_dash], :scalar_year, :separator_at?, 'time?'], :handle_sd_sm_sy)
65
65
  ]
66
66
 
67
67
  assert_equal endians, Chronic::Parser.new.definitions[:endian]
@@ -105,4 +105,24 @@ class TestHandler < TestCase
105
105
  assert handler.match(tokens, definitions)
106
106
  end
107
107
 
108
+ def test_handler_class_7
109
+ handler = Chronic::Handler.new([[:separator_on, :separator_at], :scalar], :handler)
110
+
111
+ tokens = [Chronic::Token.new('at'),
112
+ Chronic::Token.new('14')]
113
+
114
+ tokens[0].tag(Chronic::SeparatorAt.new('at'))
115
+ tokens[1].tag(Chronic::Scalar.new(14))
116
+
117
+ assert handler.match(tokens, definitions)
118
+
119
+ tokens = [Chronic::Token.new('on'),
120
+ Chronic::Token.new('15')]
121
+
122
+ tokens[0].tag(Chronic::SeparatorOn.new('on'))
123
+ tokens[1].tag(Chronic::Scalar.new(15))
124
+
125
+ assert handler.match(tokens, definitions)
126
+ end
127
+
108
128
  end
@@ -18,12 +18,25 @@ class TestParsing < TestCase
18
18
  time = Chronic.parse("2012-08-02T08:00:00-04:00")
19
19
  assert_equal Time.utc(2012, 8, 2, 12), time
20
20
 
21
+ time = Chronic.parse("2013-08-01T19:30:00.345-07:00")
22
+ time2 = Time.parse("2013-08-01 019:30:00.345-07:00")
23
+ assert_in_delta time, time2, 0.001
24
+
21
25
  time = Chronic.parse("2012-08-02T12:00:00Z")
22
26
  assert_equal Time.utc(2012, 8, 2, 12), time
23
27
 
24
28
  time = Chronic.parse("2012-01-03 01:00:00.100")
25
29
  time2 = Time.parse("2012-01-03 01:00:00.100")
26
- assert_equal time, time2
30
+ assert_in_delta time, time2, 0.001
31
+
32
+ time = Chronic.parse("2012-01-03 01:00:00.234567")
33
+ time2 = Time.parse("2012-01-03 01:00:00.234567")
34
+ assert_in_delta time, time2, 0.000001
35
+
36
+ assert_nil Chronic.parse("1/1/32.1")
37
+
38
+ time = Chronic.parse("28th", {:guess => :begin})
39
+ assert_equal Time.new(Time.now.year, Time.now.month, 28), time
27
40
  end
28
41
 
29
42
  def test_handle_rmn_sd
@@ -59,6 +72,9 @@ class TestParsing < TestCase
59
72
 
60
73
  time = parse_now("may 28 at 5:32.19pm", :context => :past)
61
74
  assert_equal Time.local(2006, 5, 28, 17, 32, 19), time
75
+
76
+ time = parse_now("may 28 at 5:32:19.764")
77
+ assert_in_delta Time.local(2007, 5, 28, 17, 32, 19, 764000), time, 0.001
62
78
  end
63
79
 
64
80
  def test_handle_rmn_sd_on
@@ -166,6 +182,9 @@ class TestParsing < TestCase
166
182
 
167
183
  time = parse_now("2011-07-03 21:11:35 UTC")
168
184
  assert_equal 1309727495, time.to_i
185
+
186
+ time = parse_now("2011-07-03 21:11:35.362 UTC")
187
+ assert_in_delta 1309727495.362, time.to_f, 0.001
169
188
  end
170
189
 
171
190
  def test_handle_rmn_sd_sy
@@ -282,6 +301,9 @@ class TestParsing < TestCase
282
301
  # month day overflows
283
302
  time = parse_now("30/2/2000")
284
303
  assert_nil time
304
+
305
+ time = parse_now("2013-03-12 17:00", :context => :past)
306
+ assert_equal Time.local(2013, 3, 12, 17, 0, 0), time
285
307
  end
286
308
 
287
309
  def test_handle_sd_sm_sy
@@ -293,6 +315,15 @@ class TestParsing < TestCase
293
315
 
294
316
  time = parse_now("03/18/2012 09:26 pm")
295
317
  assert_equal Time.local(2012, 3, 18, 21, 26), time
318
+
319
+ time = parse_now("30.07.2013 16:34:22")
320
+ assert_equal Time.local(2013, 7, 30, 16, 34, 22), time
321
+
322
+ time = parse_now("09.08.2013")
323
+ assert_equal Time.local(2013, 8, 9, 12), time
324
+
325
+ time = parse_now("30-07-2013 21:53:49")
326
+ assert_equal Time.local(2013, 7, 30, 21, 53, 49), time
296
327
  end
297
328
 
298
329
  def test_handle_sy_sm_sd
@@ -317,9 +348,18 @@ class TestParsing < TestCase
317
348
  time = parse_now("2006-08-20 15:30.30")
318
349
  assert_equal Time.local(2006, 8, 20, 15, 30, 30), time
319
350
 
351
+ time = parse_now("2006-08-20 15:30:30:000536")
352
+ assert_in_delta Time.local(2006, 8, 20, 15, 30, 30, 536), time, 0.000001
353
+
320
354
  time = parse_now("1902-08-20")
321
355
  assert_equal Time.local(1902, 8, 20, 12, 0, 0), time
322
356
 
357
+ time = parse_now("2013.07.30 11:45:23")
358
+ assert_equal Time.local(2013, 7, 30, 11, 45, 23), time
359
+
360
+ time = parse_now("2013.08.09")
361
+ assert_equal Time.local(2013, 8, 9, 12, 0, 0), time
362
+
323
363
  # exif date time original
324
364
  time = parse_now("2012:05:25 22:06:50")
325
365
  assert_equal Time.local(2012, 5, 25, 22, 6, 50), time
@@ -367,8 +407,8 @@ class TestParsing < TestCase
367
407
  time = parse_now("2012-06")
368
408
  assert_equal Time.local(2012, 06, 16), time
369
409
 
370
- time = parse_now("2013/11")
371
- assert_equal Time.local(2013, 11, 16), time
410
+ time = parse_now("2013/12")
411
+ assert_equal Time.local(2013, 12, 16, 12, 0), time
372
412
  end
373
413
 
374
414
  def test_handle_r
@@ -383,6 +423,21 @@ class TestParsing < TestCase
383
423
 
384
424
  time = parse_now("01:00:00 PM")
385
425
  assert_equal Time.local(2006, 8, 16, 13), time
426
+
427
+ time = parse_now("today at 02:00:00", :hours24 => false)
428
+ assert_equal Time.local(2006, 8, 16, 14), time
429
+
430
+ time = parse_now("today at 02:00:00 AM", :hours24 => false)
431
+ assert_equal Time.local(2006, 8, 16, 2), time
432
+
433
+ time = parse_now("today at 3:00:00", :hours24 => true)
434
+ assert_equal Time.local(2006, 8, 16, 3), time
435
+
436
+ time = parse_now("today at 03:00:00", :hours24 => true)
437
+ assert_equal Time.local(2006, 8, 16, 3), time
438
+
439
+ time = parse_now("tomorrow at 4a.m.")
440
+ assert_equal Time.local(2006, 8, 17, 4), time
386
441
  end
387
442
 
388
443
  def test_handle_r_g_r
@@ -1149,6 +1204,20 @@ class TestParsing < TestCase
1149
1204
  assert_equal Time.local(2006, 12, 31, 12), time
1150
1205
  end
1151
1206
 
1207
+ def test_handle_rdn_rmn_od_sy
1208
+ time = parse_now("Thu Aug 10th 2005")
1209
+ assert_equal Time.local(2005, 8, 10, 12), time
1210
+
1211
+ time = parse_now("Thursday July 31st 2005")
1212
+ assert_equal Time.local(2005, 7, 31, 12), time
1213
+
1214
+ time = parse_now("Thursday December 31st 2005")
1215
+ assert_equal Time.local(2005, 12, 31, 12), time
1216
+
1217
+ time = parse_now("Thursday December 30th 2005")
1218
+ assert_equal Time.local(2005, 12, 30, 12), time
1219
+ end
1220
+
1152
1221
  def test_normalizing_day_portions
1153
1222
  assert_equal pre_normalize("8:00 pm February 11"), pre_normalize("8:00 p.m. February 11")
1154
1223
  end
@@ -7,6 +7,12 @@ class TestRepeaterTime < TestCase
7
7
  @now = Time.local(2006, 8, 16, 14, 0, 0, 0)
8
8
  end
9
9
 
10
+ def test_generic
11
+ assert_raises(ArgumentError) do
12
+ Chronic::RepeaterTime.new('00:01:02:03:004')
13
+ end
14
+ end
15
+
10
16
  def test_next_future
11
17
  t = Chronic::RepeaterTime.new('4:00')
12
18
  t.start = @now
@@ -25,6 +31,12 @@ class TestRepeaterTime < TestCase
25
31
 
26
32
  assert_equal Time.local(2006, 8, 17, 4), t.next(:future).begin
27
33
  assert_equal Time.local(2006, 8, 18, 4), t.next(:future).begin
34
+
35
+ t = Chronic::RepeaterTime.new('0000')
36
+ t.start = @now
37
+
38
+ assert_equal Time.local(2006, 8, 17, 0), t.next(:future).begin
39
+ assert_equal Time.local(2006, 8, 18, 0), t.next(:future).begin
28
40
  end
29
41
 
30
42
  def test_next_past
@@ -39,6 +51,12 @@ class TestRepeaterTime < TestCase
39
51
 
40
52
  assert_equal Time.local(2006, 8, 16, 13), t.next(:past).begin
41
53
  assert_equal Time.local(2006, 8, 15, 13), t.next(:past).begin
54
+
55
+ t = Chronic::RepeaterTime.new('0:00.000')
56
+ t.start = @now
57
+
58
+ assert_equal Time.local(2006, 8, 16, 0), t.next(:past).begin
59
+ assert_equal Time.local(2006, 8, 15, 0), t.next(:past).begin
42
60
  end
43
61
 
44
62
  def test_type
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chronic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Preston-Werner
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-25 00:00:00.000000000 Z
12
+ date: 2013-08-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -26,7 +26,7 @@ dependencies:
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
28
  - !ruby/object:Gem::Dependency
29
- name: minitest
29
+ name: simplecov
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - '>='
@@ -39,10 +39,24 @@ dependencies:
39
39
  - - '>='
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: minitest
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: '5.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: '5.0'
42
56
  description: Chronic is a natural language date/time parser written in pure Ruby.
43
57
  email:
44
58
  - tom@mojombo.com
45
- - lee@jarvis.co
59
+ - ljjarvis@gmail.com
46
60
  executables: []
47
61
  extensions: []
48
62
  extra_rdoc_files:
@@ -58,6 +72,7 @@ files:
58
72
  - Rakefile
59
73
  - chronic.gemspec
60
74
  - lib/chronic.rb
75
+ - lib/chronic/date.rb
61
76
  - lib/chronic/grabber.rb
62
77
  - lib/chronic/handler.rb
63
78
  - lib/chronic/handlers.rb
@@ -86,8 +101,10 @@ files:
86
101
  - lib/chronic/scalar.rb
87
102
  - lib/chronic/season.rb
88
103
  - lib/chronic/separator.rb
104
+ - lib/chronic/sign.rb
89
105
  - lib/chronic/span.rb
90
106
  - lib/chronic/tag.rb
107
+ - lib/chronic/time.rb
91
108
  - lib/chronic/time_zone.rb
92
109
  - lib/chronic/token.rb
93
110
  - test/helper.rb
@@ -113,7 +130,8 @@ files:
113
130
  - test/test_span.rb
114
131
  - test/test_token.rb
115
132
  homepage: http://github.com/mojombo/chronic
116
- licenses: []
133
+ licenses:
134
+ - MIT
117
135
  metadata: {}
118
136
  post_install_message:
119
137
  rdoc_options:
@@ -132,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
150
  version: '0'
133
151
  requirements: []
134
152
  rubyforge_project: chronic
135
- rubygems_version: 2.0.0
153
+ rubygems_version: 2.0.2
136
154
  signing_key:
137
155
  specification_version: 4
138
156
  summary: Natural language date/time parsing.