chronic-davispuh 0.10.2.v0.1da32066b3f46f2506b3471e39557497e34afa27
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +10 -0
- data/Gemfile +3 -0
- data/HISTORY.md +243 -0
- data/LICENSE +21 -0
- data/README.md +185 -0
- data/Rakefile +68 -0
- data/chronic.gemspec +27 -0
- data/lib/chronic.rb +122 -0
- data/lib/chronic/arrow.rb +270 -0
- data/lib/chronic/date.rb +272 -0
- data/lib/chronic/definition.rb +208 -0
- data/lib/chronic/dictionary.rb +36 -0
- data/lib/chronic/handler.rb +44 -0
- data/lib/chronic/handlers/anchor.rb +65 -0
- data/lib/chronic/handlers/arrow.rb +84 -0
- data/lib/chronic/handlers/date.rb +270 -0
- data/lib/chronic/handlers/date_time.rb +72 -0
- data/lib/chronic/handlers/general.rb +130 -0
- data/lib/chronic/handlers/narrow.rb +54 -0
- data/lib/chronic/handlers/time.rb +167 -0
- data/lib/chronic/handlers/time_zone.rb +50 -0
- data/lib/chronic/objects/anchor_object.rb +263 -0
- data/lib/chronic/objects/arrow_object.rb +27 -0
- data/lib/chronic/objects/date_object.rb +164 -0
- data/lib/chronic/objects/date_time_object.rb +64 -0
- data/lib/chronic/objects/handler_object.rb +81 -0
- data/lib/chronic/objects/narrow_object.rb +85 -0
- data/lib/chronic/objects/time_object.rb +96 -0
- data/lib/chronic/objects/time_zone_object.rb +27 -0
- data/lib/chronic/parser.rb +154 -0
- data/lib/chronic/span.rb +32 -0
- data/lib/chronic/tag.rb +84 -0
- data/lib/chronic/tags/day_name.rb +34 -0
- data/lib/chronic/tags/day_portion.rb +33 -0
- data/lib/chronic/tags/day_special.rb +30 -0
- data/lib/chronic/tags/grabber.rb +29 -0
- data/lib/chronic/tags/keyword.rb +63 -0
- data/lib/chronic/tags/month_name.rb +39 -0
- data/lib/chronic/tags/ordinal.rb +52 -0
- data/lib/chronic/tags/pointer.rb +28 -0
- data/lib/chronic/tags/rational.rb +35 -0
- data/lib/chronic/tags/scalar.rb +101 -0
- data/lib/chronic/tags/season_name.rb +31 -0
- data/lib/chronic/tags/separator.rb +130 -0
- data/lib/chronic/tags/sign.rb +35 -0
- data/lib/chronic/tags/time_special.rb +34 -0
- data/lib/chronic/tags/time_zone.rb +56 -0
- data/lib/chronic/tags/unit.rb +174 -0
- data/lib/chronic/time.rb +141 -0
- data/lib/chronic/time_zone.rb +80 -0
- data/lib/chronic/token.rb +61 -0
- data/lib/chronic/token_group.rb +271 -0
- data/lib/chronic/tokenizer.rb +42 -0
- data/lib/chronic/version.rb +3 -0
- data/test/helper.rb +12 -0
- data/test/test_chronic.rb +190 -0
- data/test/test_daylight_savings.rb +98 -0
- data/test/test_handler.rb +113 -0
- data/test/test_parsing.rb +1520 -0
- data/test/test_span.rb +23 -0
- data/test/test_token.rb +31 -0
- metadata +218 -0
data/lib/chronic/span.rb
ADDED
@@ -0,0 +1,32 @@
|
|
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
|
+
attr_accessor :precision
|
7
|
+
# Returns the width of this span in seconds
|
8
|
+
def width
|
9
|
+
(self.end - self.begin).to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
# Add a number of seconds to this span, returning the
|
13
|
+
# resulting Span
|
14
|
+
def +(seconds)
|
15
|
+
Span.new(self.begin + seconds, self.end + seconds)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Subtract a number of seconds to this span, returning the
|
19
|
+
# resulting Span
|
20
|
+
def -(seconds)
|
21
|
+
self + -seconds
|
22
|
+
end
|
23
|
+
|
24
|
+
# Prints this span in a nice fashion
|
25
|
+
def to_s
|
26
|
+
'(' << self.begin.to_s << '...' << self.end.to_s << ')'
|
27
|
+
end
|
28
|
+
|
29
|
+
alias :cover? :include? if RUBY_VERSION =~ /^1.8/
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/lib/chronic/tag.rb
ADDED
@@ -0,0 +1,84 @@
|
|
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
|
+
attr_accessor :width
|
8
|
+
|
9
|
+
# type - The Symbol type of this tag.
|
10
|
+
def initialize(type, width = nil, options = {})
|
11
|
+
@type = type
|
12
|
+
@width = width
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# Public: Scan an Array of Token objects.
|
18
|
+
#
|
19
|
+
# tokens - An Array of tokens to scan.
|
20
|
+
# options - The Hash of options specified in Chronic::parse.
|
21
|
+
#
|
22
|
+
# Returns an Array of tokens.
|
23
|
+
def scan(tokens, options)
|
24
|
+
raise NotImplementedError, 'Subclasses must override scan!'
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Internal: Match item and create respective Tag class.
|
30
|
+
# When item is a Symbol it will match only when it's identical to Token.
|
31
|
+
# When it's a String it will case-insesitively match partial token,
|
32
|
+
# but only if item's last char have different type than token text's next char.
|
33
|
+
# When item is a Regexp it will match by it.
|
34
|
+
#
|
35
|
+
# item - Item to match. It can be String, Symbol or Regexp.
|
36
|
+
# klass - Tag class to create.
|
37
|
+
# symbol - Tag type as symbol or string to pass to Tag class.
|
38
|
+
# token - Token to match against.
|
39
|
+
# options - Options as hash to pass to Tag class.
|
40
|
+
#
|
41
|
+
# Returns an instance of specified Tag klass or nil if item didn't match.
|
42
|
+
def match_item(item, klass, symbol, token, options)
|
43
|
+
match = false
|
44
|
+
case item
|
45
|
+
when String
|
46
|
+
item_type = Tokenizer.char_type(item.to_s[-1])
|
47
|
+
text_type = token.text[token.position+item.length]
|
48
|
+
text_type = Tokenizer.char_type(text_type) if text_type
|
49
|
+
compatible = true
|
50
|
+
compatible = item_type != text_type if text_type && (item_type == :letter || item_type == :digit)
|
51
|
+
match = compatible && token.text[token.position, item.length].casecmp(item).zero?
|
52
|
+
when Symbol
|
53
|
+
match = token.word == item.to_s
|
54
|
+
when Regexp
|
55
|
+
match = token.word =~ item
|
56
|
+
end
|
57
|
+
return klass.new(symbol, nil, options) if match
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
# Internal: Scan for specified items and create respective Tag class.
|
62
|
+
#
|
63
|
+
# token - Token to match against.
|
64
|
+
# klass - Tag class to create.
|
65
|
+
# items - Item(s) to match. It can be Hash, String, Symbol or Regexp.
|
66
|
+
# Hash keys can be String, Symbol or Regexp, but values much be Symbol.
|
67
|
+
# options - Options as hash to pass to Tag class.
|
68
|
+
#
|
69
|
+
# Returns an instance of specified Tag klass or nil if item(s) didn't match.
|
70
|
+
def scan_for(token, klass, items, options = {})
|
71
|
+
if items.kind_of?(Hash)
|
72
|
+
items.each do |item, symbol|
|
73
|
+
scanned = match_item(item, klass, symbol, token, options)
|
74
|
+
return scanned if scanned
|
75
|
+
end
|
76
|
+
else
|
77
|
+
return match_item(items, klass, token.word, token, options)
|
78
|
+
end
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Chronic
|
2
|
+
class DayName < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary DayName
|
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
|
+
token.tag scan_for(token, self, patterns, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.patterns
|
18
|
+
@@patterns ||= {
|
19
|
+
/^m[ou]n(day)?$/i => :monday,
|
20
|
+
/^t(ue|eu|oo|u)s?(day)?$/i => :tuesday,
|
21
|
+
/^we(d|dnes|nds|nns)(day)?$/i => :wednesday,
|
22
|
+
/^th(u|ur|urs|ers)(day)?$/i => :thursday,
|
23
|
+
/^fr[iy](day)?$/i => :friday,
|
24
|
+
/^sat(t?[ue]rday)?$/i => :saturday,
|
25
|
+
/^su[nm](day)?$/i => :sunday
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
'dayname-' << @type.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Chronic
|
2
|
+
class DayPortion < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary DayPortion
|
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
|
+
token.tag scan_for(token, self, patterns, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.patterns
|
18
|
+
@@patterns ||= {
|
19
|
+
/^ams?$/i => :am,
|
20
|
+
/^pms?$/i => :pm,
|
21
|
+
'a.m.' => :am,
|
22
|
+
'p.m.' => :pm,
|
23
|
+
'a' => :am,
|
24
|
+
'p' => :pm
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
'dayportion-' << @type.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Chronic
|
2
|
+
class DaySpecial < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary SeasonName
|
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
|
+
token.tag scan_for(token, self, patterns, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.patterns
|
18
|
+
@@patterns ||= {
|
19
|
+
/^yesterday$/i => :yesterday,
|
20
|
+
/^today$/i => :today,
|
21
|
+
/^tomorrow$/i => :tomorrow
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
'dayspecial-' << @type.to_s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Grabber < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Tokens and apply any necessary Grabber tags to
|
5
|
+
# each token.
|
6
|
+
#
|
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.
|
11
|
+
def self.scan(tokens, options)
|
12
|
+
tokens.each do |token|
|
13
|
+
token.tag scan_for(token, self, patterns, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.patterns
|
18
|
+
@@patterns ||= {
|
19
|
+
'last' => :last,
|
20
|
+
'this' => :this,
|
21
|
+
'next' => :next
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
'grabber-' << @type.to_s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Keyword < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary Keyword
|
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
|
+
token.tag scan_for(token, KeywordAt, { /^(at|@)$/i => :at })
|
14
|
+
token.tag scan_for(token, KeywordIn, { 'in' => :in })
|
15
|
+
token.tag scan_for(token, KeywordOn, { 'on' => :on })
|
16
|
+
token.tag scan_for(token, KeywordAnd, { 'and' => :and })
|
17
|
+
token.tag scan_for(token, KeywordTo, { 'to' => :to })
|
18
|
+
token.tag scan_for(token, KeywordQ, { :Q => :Q })
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
'keyword'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class KeywordAt < Keyword #:nodoc:
|
28
|
+
def to_s
|
29
|
+
super << '-at'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class KeywordIn < Keyword #:nodoc:
|
34
|
+
def to_s
|
35
|
+
super << '-in'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class KeywordOn < Keyword #:nodoc:
|
40
|
+
def to_s
|
41
|
+
super << '-on'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class KeywordAnd < Keyword #:nodoc:
|
46
|
+
def to_s
|
47
|
+
super << '-and'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class KeywordTo < Keyword #:nodoc:
|
52
|
+
def to_s
|
53
|
+
super << '-to'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class KeywordQ < Keyword #:nodoc:
|
58
|
+
def to_s
|
59
|
+
super << '-q'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Chronic
|
2
|
+
class MonthName < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary MonthName
|
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
|
+
token.tag scan_for(token, self, patterns, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.patterns
|
18
|
+
@@patterns ||= {
|
19
|
+
/^jan[:\.]?(uary)?$/i => :january,
|
20
|
+
/^feb[:\.]?(ruary)?$/i => :february,
|
21
|
+
/^mar[:\.]?(ch)?$/i => :march,
|
22
|
+
/^apr[:\.]?(il)?$/i => :april,
|
23
|
+
/^may$/i => :may,
|
24
|
+
/^jun[:\.]?e?$/i => :june,
|
25
|
+
/^jul[:\.]?y?$/i => :july,
|
26
|
+
/^aug[:\.]?(ust)?$/i => :august,
|
27
|
+
/^sep[:\.]?(t[:\.]?|tember)?$/i => :september,
|
28
|
+
/^oct[:\.]?(ober)?$/i => :october,
|
29
|
+
/^nov[:\.]?(ember)?$/i => :november,
|
30
|
+
/^dec[:\.]?(ember)?$/i => :december
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
'monthname-' << @type.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Ordinal < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary Ordinal
|
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_index do |i|
|
13
|
+
if tokens[i].word =~ /^\d+$/ and tokens[i + 1] and tokens[i + 1].word =~ /^st|nd|rd|th|\.$/
|
14
|
+
width = tokens[i].word.length
|
15
|
+
ordinal = tokens[i].word.to_i
|
16
|
+
tokens[i].tag(Ordinal.new(ordinal, width))
|
17
|
+
tokens[i].tag(OrdinalDay.new(ordinal, width)) if Chronic::Date::could_be_day?(ordinal, width)
|
18
|
+
tokens[i].tag(OrdinalMonth.new(ordinal, width)) if Chronic::Date::could_be_month?(ordinal, width)
|
19
|
+
if Chronic::Date::could_be_year?(ordinal, width)
|
20
|
+
year = Chronic::Date::make_year(ordinal, options[:ambiguous_year_future_bias])
|
21
|
+
tokens[i].tag(OrdinalYear.new(year.to_i, width))
|
22
|
+
end
|
23
|
+
elsif tokens[i].word =~ /^second$/
|
24
|
+
tokens[i].tag(Ordinal.new(2, 1))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
'ordinal'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class OrdinalDay < Ordinal #:nodoc:
|
35
|
+
def to_s
|
36
|
+
super << '-day-' << @type.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class OrdinalMonth < Ordinal #:nodoc:
|
41
|
+
def to_s
|
42
|
+
super << '-month-' << @type.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class OrdinalYear < Ordinal #:nodoc:
|
47
|
+
def to_s
|
48
|
+
super << '-year-' << @type.to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Pointer < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary Pointer
|
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
|
+
token.tag scan_for(token, self, patterns, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.patterns
|
18
|
+
@@patterns ||= {
|
19
|
+
/^ago|before|prior|till|to$/i => :past,
|
20
|
+
/^future|hence|from|after|past$/i => :future
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
'pointer-' << @type.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Rational < Tag
|
3
|
+
|
4
|
+
# Scan an Array of Token objects and apply any necessary Keyword
|
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
|
+
token.tag scan_for(token, RationalQuarter, { 'quarter' => Rational(1, 4) })
|
14
|
+
token.tag scan_for(token, RationalHalf, { 'half' => Rational(1, 2) })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
'rational'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class RationalQuarter < Rational #:nodoc:
|
24
|
+
def to_s
|
25
|
+
super << '-quarter'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class RationalHalf < Rational #:nodoc:
|
30
|
+
def to_s
|
31
|
+
super << '-half'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|