chronic 0.2.2 → 0.10.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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +8 -0
  4. data/HISTORY.md +243 -0
  5. data/LICENSE +21 -0
  6. data/README.md +182 -0
  7. data/Rakefile +62 -15
  8. data/chronic.gemspec +23 -0
  9. data/lib/chronic/date.rb +82 -0
  10. data/lib/chronic/grabber.rb +24 -17
  11. data/lib/chronic/handler.rb +97 -0
  12. data/lib/chronic/handlers.rb +490 -312
  13. data/lib/chronic/mini_date.rb +38 -0
  14. data/lib/chronic/numerizer.rb +130 -0
  15. data/lib/chronic/ordinal.rb +33 -24
  16. data/lib/chronic/parser.rb +268 -0
  17. data/lib/chronic/pointer.rb +22 -17
  18. data/lib/chronic/repeater.rb +137 -107
  19. data/lib/chronic/repeaters/repeater_day.rb +51 -44
  20. data/lib/chronic/repeaters/repeater_day_name.rb +50 -43
  21. data/lib/chronic/repeaters/repeater_day_portion.rb +97 -81
  22. data/lib/chronic/repeaters/repeater_fortnight.rb +61 -54
  23. data/lib/chronic/repeaters/repeater_hour.rb +51 -44
  24. data/lib/chronic/repeaters/repeater_minute.rb +51 -44
  25. data/lib/chronic/repeaters/repeater_month.rb +79 -60
  26. data/lib/chronic/repeaters/repeater_month_name.rb +85 -83
  27. data/lib/chronic/repeaters/repeater_season.rb +110 -22
  28. data/lib/chronic/repeaters/repeater_season_name.rb +41 -22
  29. data/lib/chronic/repeaters/repeater_second.rb +41 -34
  30. data/lib/chronic/repeaters/repeater_time.rb +128 -104
  31. data/lib/chronic/repeaters/repeater_week.rb +65 -58
  32. data/lib/chronic/repeaters/repeater_weekday.rb +86 -0
  33. data/lib/chronic/repeaters/repeater_weekend.rb +59 -52
  34. data/lib/chronic/repeaters/repeater_year.rb +72 -52
  35. data/lib/chronic/scalar.rb +53 -46
  36. data/lib/chronic/season.rb +26 -0
  37. data/lib/chronic/separator.rb +174 -43
  38. data/lib/chronic/sign.rb +49 -0
  39. data/lib/chronic/span.rb +31 -0
  40. data/lib/chronic/tag.rb +37 -0
  41. data/lib/chronic/time.rb +40 -0
  42. data/lib/chronic/time_zone.rb +21 -11
  43. data/lib/chronic/token.rb +51 -0
  44. data/lib/chronic.rb +94 -69
  45. data/test/helper.rb +12 -0
  46. data/test/test_chronic.rb +183 -0
  47. data/test/test_daylight_savings.rb +118 -0
  48. data/test/{test_Handler.rb → test_handler.rb} +73 -55
  49. data/test/test_mini_date.rb +32 -0
  50. data/test/test_numerizer.rb +86 -0
  51. data/test/test_parsing.rb +891 -248
  52. data/test/{test_RepeaterDayName.rb → test_repeater_day_name.rb} +16 -17
  53. data/test/test_repeater_day_portion.rb +254 -0
  54. data/test/{test_RepeaterFortnight.rb → test_repeater_fortnight.rb} +17 -18
  55. data/test/{test_RepeaterHour.rb → test_repeater_hour.rb} +23 -20
  56. data/test/test_repeater_minute.rb +34 -0
  57. data/test/{test_RepeaterMonth.rb → test_repeater_month.rb} +21 -18
  58. data/test/{test_RepeaterMonthName.rb → test_repeater_month_name.rb} +18 -19
  59. data/test/test_repeater_season.rb +40 -0
  60. data/test/{test_RepeaterTime.rb → test_repeater_time.rb} +39 -23
  61. data/test/{test_RepeaterWeek.rb → test_repeater_week.rb} +17 -18
  62. data/test/test_repeater_weekday.rb +55 -0
  63. data/test/{test_RepeaterWeekend.rb → test_repeater_weekend.rb} +22 -23
  64. data/test/{test_RepeaterYear.rb → test_repeater_year.rb} +25 -19
  65. data/test/{test_Span.rb → test_span.rb} +7 -8
  66. data/test/{test_Token.rb → test_token.rb} +6 -7
  67. metadata +163 -85
  68. data/History.txt +0 -49
  69. data/Manifest.txt +0 -47
  70. data/README.txt +0 -149
  71. data/lib/chronic/chronic.rb +0 -239
  72. data/lib/numerizer/numerizer.rb +0 -103
  73. data/test/suite.rb +0 -9
  74. data/test/test_Chronic.rb +0 -50
  75. data/test/test_Numerizer.rb +0 -48
  76. data/test/test_Time.rb +0 -50
@@ -1,76 +1,207 @@
1
1
  module Chronic
2
+ class Separator < Tag
2
3
 
3
- class Separator < Tag #:nodoc:
4
- def self.scan(tokens)
5
- tokens.each_index do |i|
6
- if t = self.scan_for_commas(tokens[i]) then tokens[i].tag(t); next end
7
- if t = self.scan_for_slash_or_dash(tokens[i]) then tokens[i].tag(t); next end
8
- if t = self.scan_for_at(tokens[i]) then tokens[i].tag(t); next end
9
- if t = self.scan_for_in(tokens[i]) then tokens[i].tag(t); next end
4
+ # Scan an Array of Token objects and apply any necessary Separator
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_commas(token) then token.tag(t); next end
14
+ if t = scan_for_dots(token) then token.tag(t); next end
15
+ if t = scan_for_colon(token) then token.tag(t); next end
16
+ if t = scan_for_space(token) then token.tag(t); next end
17
+ if t = scan_for_slash(token) then token.tag(t); next end
18
+ if t = scan_for_dash(token) then token.tag(t); next end
19
+ if t = scan_for_quote(token) then token.tag(t); next end
20
+ if t = scan_for_at(token) then token.tag(t); next end
21
+ if t = scan_for_in(token) then token.tag(t); next end
22
+ if t = scan_for_on(token) then token.tag(t); next end
23
+ if t = scan_for_and(token) then token.tag(t); next end
24
+ if t = scan_for_t(token) then token.tag(t); next end
25
+ if t = scan_for_w(token) then token.tag(t); next end
10
26
  end
11
- tokens
12
27
  end
13
-
28
+
29
+ # token - The Token object we want to scan.
30
+ #
31
+ # Returns a new SeparatorComma object.
14
32
  def self.scan_for_commas(token)
15
- scanner = {/^,$/ => :comma}
16
- scanner.keys.each do |scanner_item|
17
- return SeparatorComma.new(scanner[scanner_item]) if scanner_item =~ token.word
18
- end
19
- return nil
20
- end
21
-
22
- def self.scan_for_slash_or_dash(token)
23
- scanner = {/^-$/ => :dash,
24
- /^\/$/ => :slash}
25
- scanner.keys.each do |scanner_item|
26
- return SeparatorSlashOrDash.new(scanner[scanner_item]) if scanner_item =~ token.word
27
- end
28
- return nil
33
+ scan_for token, SeparatorComma, { /^,$/ => :comma }
34
+ end
35
+
36
+ # token - The Token object we want to scan.
37
+ #
38
+ # Returns a new SeparatorDot object.
39
+ def self.scan_for_dots(token)
40
+ scan_for token, SeparatorDot, { /^\.$/ => :dot }
41
+ end
42
+
43
+ # token - The Token object we want to scan.
44
+ #
45
+ # Returns a new SeparatorColon object.
46
+ def self.scan_for_colon(token)
47
+ scan_for token, SeparatorColon, { /^:$/ => :colon }
48
+ end
49
+
50
+ # token - The Token object we want to scan.
51
+ #
52
+ # Returns a new SeparatorSpace object.
53
+ def self.scan_for_space(token)
54
+ scan_for token, SeparatorSpace, { /^ $/ => :space }
29
55
  end
30
-
56
+
57
+ # token - The Token object we want to scan.
58
+ #
59
+ # Returns a new SeparatorSlash object.
60
+ def self.scan_for_slash(token)
61
+ scan_for token, SeparatorSlash, { /^\/$/ => :slash }
62
+ end
63
+
64
+ # token - The Token object we want to scan.
65
+ #
66
+ # Returns a new SeparatorDash object.
67
+ def self.scan_for_dash(token)
68
+ scan_for token, SeparatorDash, { /^-$/ => :dash }
69
+ end
70
+
71
+ # token - The Token object we want to scan.
72
+ #
73
+ # Returns a new SeparatorQuote object.
74
+ def self.scan_for_quote(token)
75
+ scan_for token, SeparatorQuote,
76
+ {
77
+ /^'$/ => :single_quote,
78
+ /^"$/ => :double_quote
79
+ }
80
+ end
81
+
82
+ # token - The Token object we want to scan.
83
+ #
84
+ # Returns a new SeparatorAt object.
31
85
  def self.scan_for_at(token)
32
- scanner = {/^(at|@)$/ => :at}
33
- scanner.keys.each do |scanner_item|
34
- return SeparatorAt.new(scanner[scanner_item]) if scanner_item =~ token.word
35
- end
36
- return nil
86
+ scan_for token, SeparatorAt, { /^(at|@)$/ => :at }
37
87
  end
38
-
88
+
89
+ # token - The Token object we want to scan.
90
+ #
91
+ # Returns a new SeparatorIn object.
39
92
  def self.scan_for_in(token)
40
- scanner = {/^in$/ => :in}
41
- scanner.keys.each do |scanner_item|
42
- return SeparatorIn.new(scanner[scanner_item]) if scanner_item =~ token.word
43
- end
44
- return nil
93
+ scan_for token, SeparatorIn, { /^in$/ => :in }
45
94
  end
46
-
95
+
96
+ # token - The Token object we want to scan.
97
+ #
98
+ # Returns a new SeparatorOn object.
99
+ def self.scan_for_on(token)
100
+ scan_for token, SeparatorOn, { /^on$/ => :on }
101
+ end
102
+
103
+ # token - The Token object we want to scan.
104
+ #
105
+ # Returns a new SeperatorAnd Object object.
106
+ def self.scan_for_and(token)
107
+ scan_for token, SeparatorAnd, { /^and$/ => :and }
108
+ end
109
+
110
+ # token - The Token object we want to scan.
111
+ #
112
+ # Returns a new SeperatorT Object object.
113
+ def self.scan_for_t(token)
114
+ scan_for token, SeparatorT, { /^t$/ => :T }
115
+ end
116
+
117
+ # token - The Token object we want to scan.
118
+ #
119
+ # Returns a new SeperatorW Object object.
120
+ def self.scan_for_w(token)
121
+ scan_for token, SeparatorW, { /^w$/ => :W }
122
+ end
123
+
47
124
  def to_s
48
125
  'separator'
49
126
  end
50
127
  end
51
-
128
+
52
129
  class SeparatorComma < Separator #:nodoc:
53
130
  def to_s
54
131
  super << '-comma'
55
132
  end
56
133
  end
57
-
58
- class SeparatorSlashOrDash < Separator #:nodoc:
134
+
135
+ class SeparatorDot < Separator #:nodoc:
136
+ def to_s
137
+ super << '-dot'
138
+ end
139
+ end
140
+
141
+ class SeparatorColon < Separator #:nodoc:
142
+ def to_s
143
+ super << '-colon'
144
+ end
145
+ end
146
+
147
+ class SeparatorSpace < Separator #:nodoc:
59
148
  def to_s
60
- super << '-slashordash-' << @type.to_s
149
+ super << '-space'
61
150
  end
62
151
  end
63
-
152
+
153
+ class SeparatorSlash < Separator #:nodoc:
154
+ def to_s
155
+ super << '-slash'
156
+ end
157
+ end
158
+
159
+ class SeparatorDash < Separator #:nodoc:
160
+ def to_s
161
+ super << '-dash'
162
+ end
163
+ end
164
+
165
+ class SeparatorQuote < Separator #:nodoc:
166
+ def to_s
167
+ super << '-quote-' << @type.to_s
168
+ end
169
+ end
170
+
64
171
  class SeparatorAt < Separator #:nodoc:
65
172
  def to_s
66
173
  super << '-at'
67
174
  end
68
175
  end
69
-
176
+
70
177
  class SeparatorIn < Separator #:nodoc:
71
178
  def to_s
72
179
  super << '-in'
73
180
  end
74
181
  end
75
182
 
76
- end
183
+ class SeparatorOn < Separator #:nodoc:
184
+ def to_s
185
+ super << '-on'
186
+ end
187
+ end
188
+
189
+ class SeparatorAnd < Separator #:nodoc:
190
+ def to_s
191
+ super << '-and'
192
+ end
193
+ end
194
+
195
+ class SeparatorT < Separator #:nodoc:
196
+ def to_s
197
+ super << '-T'
198
+ end
199
+ end
200
+
201
+ class SeparatorW < Separator #:nodoc:
202
+ def to_s
203
+ super << '-W'
204
+ end
205
+ end
206
+
207
+ end
@@ -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
@@ -0,0 +1,31 @@
1
+ module Chronic
2
+ # A Span represents a range of time. Since this class extends
3
+ # Range, you can use #begin and #end to get the beginning and
4
+ # ending times of the span (they will be of class Time)
5
+ class Span < Range
6
+ # Returns the width of this span in seconds
7
+ def width
8
+ (self.end - self.begin).to_i
9
+ end
10
+
11
+ # Add a number of seconds to this span, returning the
12
+ # resulting Span
13
+ def +(seconds)
14
+ Span.new(self.begin + seconds, self.end + seconds)
15
+ end
16
+
17
+ # Subtract a number of seconds to this span, returning the
18
+ # resulting Span
19
+ def -(seconds)
20
+ self + -seconds
21
+ end
22
+
23
+ # Prints this span in a nice fashion
24
+ def to_s
25
+ '(' << self.begin.to_s << '..' << self.end.to_s << ')'
26
+ end
27
+
28
+ alias :cover? :include? if RUBY_VERSION =~ /^1.8/
29
+
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ module Chronic
2
+ # Tokens are tagged with subclassed instances of this class when
3
+ # they match specific criteria.
4
+ class Tag
5
+
6
+ attr_accessor :type
7
+
8
+ # type - The Symbol type of this tag.
9
+ def initialize(type, options = {})
10
+ @type = type
11
+ @options = options
12
+ end
13
+
14
+ # time - Set the start Time for this Tag.
15
+ def start=(time)
16
+ @now = time
17
+ end
18
+
19
+ class << self
20
+ private
21
+
22
+ def scan_for(token, klass, items={}, options = {})
23
+ case items
24
+ when Regexp
25
+ return klass.new(token.word, options) if items =~ token.word
26
+ when Hash
27
+ items.each do |item, symbol|
28
+ return klass.new(symbol, options) if item =~ token.word
29
+ end
30
+ end
31
+ nil
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -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
@@ -1,22 +1,32 @@
1
1
  module Chronic
2
- class TimeZone < Tag #:nodoc:
3
- def self.scan(tokens)
4
- tokens.each_index do |i|
5
- if t = self.scan_for_all(tokens[i]) then tokens[i].tag(t); next end
2
+ class TimeZone < Tag
3
+
4
+ # Scan an Array of Token objects and apply any necessary TimeZone
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_all(token) then token.tag(t); next end
6
14
  end
7
- tokens
8
15
  end
9
16
 
17
+ # token - The Token object we want to scan.
18
+ #
19
+ # Returns a new Pointer object.
10
20
  def self.scan_for_all(token)
11
- scanner = {/[PMCE][DS]T/i => :tz}
12
- scanner.keys.each do |scanner_item|
13
- return self.new(scanner[scanner_item]) if scanner_item =~ token.word
14
- end
15
- return nil
21
+ scan_for token, self,
22
+ {
23
+ /[PMCE][DS]T|UTC/i => :tz,
24
+ /(tzminus)?\d{2}:?\d{2}/ => :tz
25
+ }
16
26
  end
17
27
 
18
28
  def to_s
19
29
  'timezone'
20
30
  end
21
31
  end
22
- end
32
+ end
@@ -0,0 +1,51 @@
1
+ module Chronic
2
+ class Token
3
+
4
+ attr_accessor :word
5
+ attr_accessor :tags
6
+
7
+ def initialize(word)
8
+ @word = word
9
+ @tags = []
10
+ end
11
+
12
+ # Tag this token with the specified tag.
13
+ #
14
+ # new_tag - The new Tag object.
15
+ #
16
+ # Returns nothing.
17
+ def tag(new_tag)
18
+ @tags << new_tag
19
+ end
20
+
21
+ # Remove all tags of the given class.
22
+ #
23
+ # tag_class - The tag Class to remove.
24
+ #
25
+ # Returns nothing.
26
+ def untag(tag_class)
27
+ @tags.delete_if { |m| m.kind_of? tag_class }
28
+ end
29
+
30
+ # Returns true if this token has any tags.
31
+ def tagged?
32
+ @tags.size > 0
33
+ end
34
+
35
+ # tag_class - The tag Class to search for.
36
+ #
37
+ # Returns The first Tag that matches the given class.
38
+ def get_tag(tag_class)
39
+ @tags.find { |m| m.kind_of? tag_class }
40
+ end
41
+
42
+ # Print this Token in a pretty way
43
+ def to_s
44
+ @word << '(' << @tags.join(', ') << ') '
45
+ end
46
+
47
+ def inspect
48
+ to_s
49
+ end
50
+ end
51
+ end