gitlab-chronic 0.10.3
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.gitlab-ci.yml +14 -0
- data/.travis.yml +10 -0
- data/Gemfile +3 -0
- data/HISTORY.md +252 -0
- data/LICENSE +21 -0
- data/README.md +188 -0
- data/Rakefile +68 -0
- data/chronic.gemspec +25 -0
- data/lib/chronic.rb +155 -0
- data/lib/chronic/date.rb +81 -0
- data/lib/chronic/definition.rb +128 -0
- data/lib/chronic/dictionary.rb +36 -0
- data/lib/chronic/handler.rb +97 -0
- data/lib/chronic/handlers.rb +672 -0
- data/lib/chronic/mini_date.rb +38 -0
- data/lib/chronic/parser.rb +222 -0
- data/lib/chronic/repeaters/repeater_day.rb +54 -0
- data/lib/chronic/repeaters/repeater_day_name.rb +53 -0
- data/lib/chronic/repeaters/repeater_day_portion.rb +109 -0
- data/lib/chronic/repeaters/repeater_fortnight.rb +72 -0
- data/lib/chronic/repeaters/repeater_hour.rb +59 -0
- data/lib/chronic/repeaters/repeater_minute.rb +59 -0
- data/lib/chronic/repeaters/repeater_month.rb +80 -0
- data/lib/chronic/repeaters/repeater_month_name.rb +95 -0
- data/lib/chronic/repeaters/repeater_quarter.rb +59 -0
- data/lib/chronic/repeaters/repeater_quarter_name.rb +40 -0
- data/lib/chronic/repeaters/repeater_season.rb +111 -0
- data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
- data/lib/chronic/repeaters/repeater_second.rb +43 -0
- data/lib/chronic/repeaters/repeater_time.rb +159 -0
- data/lib/chronic/repeaters/repeater_week.rb +76 -0
- data/lib/chronic/repeaters/repeater_weekday.rb +86 -0
- data/lib/chronic/repeaters/repeater_weekend.rb +67 -0
- data/lib/chronic/repeaters/repeater_year.rb +78 -0
- data/lib/chronic/season.rb +26 -0
- data/lib/chronic/span.rb +31 -0
- data/lib/chronic/tag.rb +89 -0
- data/lib/chronic/tags/grabber.rb +29 -0
- data/lib/chronic/tags/ordinal.rb +52 -0
- data/lib/chronic/tags/pointer.rb +28 -0
- data/lib/chronic/tags/repeater.rb +160 -0
- data/lib/chronic/tags/scalar.rb +89 -0
- data/lib/chronic/tags/separator.rb +123 -0
- data/lib/chronic/tags/sign.rb +35 -0
- data/lib/chronic/tags/time_zone.rb +32 -0
- data/lib/chronic/time.rb +40 -0
- data/lib/chronic/token.rb +61 -0
- data/lib/chronic/tokenizer.rb +38 -0
- data/lib/chronic/version.rb +3 -0
- data/test/helper.rb +12 -0
- data/test/test_chronic.rb +203 -0
- data/test/test_daylight_savings.rb +122 -0
- data/test/test_handler.rb +128 -0
- data/test/test_mini_date.rb +32 -0
- data/test/test_parsing.rb +1537 -0
- data/test/test_repeater_day_name.rb +51 -0
- data/test/test_repeater_day_portion.rb +254 -0
- data/test/test_repeater_fortnight.rb +62 -0
- data/test/test_repeater_hour.rb +68 -0
- data/test/test_repeater_minute.rb +34 -0
- data/test/test_repeater_month.rb +50 -0
- data/test/test_repeater_month_name.rb +56 -0
- data/test/test_repeater_quarter.rb +70 -0
- data/test/test_repeater_quarter_name.rb +198 -0
- data/test/test_repeater_season.rb +40 -0
- data/test/test_repeater_time.rb +88 -0
- data/test/test_repeater_week.rb +115 -0
- data/test/test_repeater_weekday.rb +55 -0
- data/test/test_repeater_weekend.rb +74 -0
- data/test/test_repeater_year.rb +69 -0
- data/test/test_span.rb +23 -0
- data/test/test_token.rb +31 -0
- metadata +215 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Scalar < Tag
|
3
|
+
DAY_PORTIONS = %w( am pm morning afternoon evening night )
|
4
|
+
|
5
|
+
# Scan an Array of Token objects and apply any necessary Scalar
|
6
|
+
# tags to each token.
|
7
|
+
#
|
8
|
+
# tokens - An Array of tokens to scan.
|
9
|
+
# options - The Hash of options specified in Chronic::parse.
|
10
|
+
#
|
11
|
+
# Returns an Array of tokens.
|
12
|
+
def self.scan(tokens, options)
|
13
|
+
tokens.each_index do |i|
|
14
|
+
token = tokens[i]
|
15
|
+
post_token = tokens[i + 1]
|
16
|
+
if token.word =~ /^\d+$/
|
17
|
+
width = token.word.length
|
18
|
+
scalar = token.word.to_i
|
19
|
+
token.tag(Scalar.new(scalar, width))
|
20
|
+
token.tag(ScalarWide.new(token.word, width)) if width == 4
|
21
|
+
token.tag(ScalarSubsecond.new(scalar, width)) if Chronic::Time::could_be_subsecond?(scalar, width)
|
22
|
+
token.tag(ScalarSecond.new(scalar, width)) if Chronic::Time::could_be_second?(scalar, width)
|
23
|
+
token.tag(ScalarMinute.new(scalar, width)) if Chronic::Time::could_be_minute?(scalar, width)
|
24
|
+
token.tag(ScalarHour.new(scalar, width)) if Chronic::Time::could_be_hour?(scalar, width, options[:hours24] == false)
|
25
|
+
unless post_token and DAY_PORTIONS.include?(post_token.word)
|
26
|
+
token.tag(ScalarDay.new(scalar, width)) if Chronic::Date::could_be_day?(scalar, width)
|
27
|
+
token.tag(ScalarMonth.new(scalar, width)) if Chronic::Date::could_be_month?(scalar, width)
|
28
|
+
if Chronic::Date::could_be_year?(scalar, width)
|
29
|
+
year = Chronic::Date::make_year(scalar, options[:ambiguous_year_future_bias])
|
30
|
+
token.tag(ScalarYear.new(year.to_i, width))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
'scalar'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ScalarWide < Scalar #:nodoc:
|
43
|
+
def to_s
|
44
|
+
super << '-wide-' << @type.to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class ScalarSubsecond < Scalar #:nodoc:
|
49
|
+
def to_s
|
50
|
+
super << '-subsecond-' << @type.to_s
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class ScalarSecond < Scalar #:nodoc:
|
55
|
+
def to_s
|
56
|
+
super << '-second-' << @type.to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ScalarMinute < Scalar #:nodoc:
|
61
|
+
def to_s
|
62
|
+
super << '-minute-' << @type.to_s
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class ScalarHour < Scalar #:nodoc:
|
67
|
+
def to_s
|
68
|
+
super << '-hour-' << @type.to_s
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class ScalarDay < Scalar #:nodoc:
|
73
|
+
def to_s
|
74
|
+
super << '-day-' << @type.to_s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class ScalarMonth < Scalar #:nodoc:
|
79
|
+
def to_s
|
80
|
+
super << '-month-' << @type.to_s
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class ScalarYear < Scalar #:nodoc:
|
85
|
+
def to_s
|
86
|
+
super << '-year-' << @type.to_s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Separator < Tag
|
3
|
+
|
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
|
+
token.tag scan_for(token, SeparatorComma, { ','.to_sym => :comma })
|
14
|
+
token.tag scan_for(token, SeparatorDot, { '.'.to_sym => :dot })
|
15
|
+
token.tag scan_for(token, SeparatorColon, { ':'.to_sym => :colon })
|
16
|
+
token.tag scan_for(token, SeparatorSpace, { ' '.to_sym => :space })
|
17
|
+
token.tag scan_for(token, SeparatorSlash, { '/'.to_sym => :slash })
|
18
|
+
token.tag scan_for(token, SeparatorDash, { :- => :dash })
|
19
|
+
token.tag scan_for(token, SeparatorAt, { /^(at|@)$/i => :at })
|
20
|
+
token.tag scan_for(token, SeparatorIn, { 'in' => :in })
|
21
|
+
token.tag scan_for(token, SeparatorOn, { 'on' => :on })
|
22
|
+
token.tag scan_for(token, SeparatorAnd, { 'and' => :and })
|
23
|
+
token.tag scan_for(token, SeparatorT, { :T => :T })
|
24
|
+
token.tag scan_for(token, SeparatorW, { :W => :W })
|
25
|
+
token.tag scan_for_quote(token)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# token - The Token object we want to scan.
|
30
|
+
#
|
31
|
+
# Returns a new SeparatorQuote object.
|
32
|
+
def self.scan_for_quote(token)
|
33
|
+
scan_for token, SeparatorQuote,
|
34
|
+
{
|
35
|
+
"'".to_sym => :single_quote,
|
36
|
+
'"'.to_sym => :double_quote
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
'separator'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class SeparatorComma < Separator #:nodoc:
|
46
|
+
def to_s
|
47
|
+
super << '-comma'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class SeparatorDot < Separator #:nodoc:
|
52
|
+
def to_s
|
53
|
+
super << '-dot'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class SeparatorColon < Separator #:nodoc:
|
58
|
+
def to_s
|
59
|
+
super << '-colon'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class SeparatorSpace < Separator #:nodoc:
|
64
|
+
def to_s
|
65
|
+
super << '-space'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class SeparatorSlash < Separator #:nodoc:
|
70
|
+
def to_s
|
71
|
+
super << '-slash'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class SeparatorDash < Separator #:nodoc:
|
76
|
+
def to_s
|
77
|
+
super << '-dash'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class SeparatorQuote < Separator #:nodoc:
|
82
|
+
def to_s
|
83
|
+
super << '-quote-' << @type.to_s
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class SeparatorAt < Separator #:nodoc:
|
88
|
+
def to_s
|
89
|
+
super << '-at'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class SeparatorIn < Separator #:nodoc:
|
94
|
+
def to_s
|
95
|
+
super << '-in'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class SeparatorOn < Separator #:nodoc:
|
100
|
+
def to_s
|
101
|
+
super << '-on'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class SeparatorAnd < Separator #:nodoc:
|
106
|
+
def to_s
|
107
|
+
super << '-and'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class SeparatorT < Separator #:nodoc:
|
112
|
+
def to_s
|
113
|
+
super << '-T'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class SeparatorW < Separator #:nodoc:
|
118
|
+
def to_s
|
119
|
+
super << '-W'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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
|
+
token.tag scan_for(token, SignPlus, { :+ => :plus })
|
14
|
+
token.tag scan_for(token, SignMinus, { :- => :minus })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
'sign'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class SignPlus < Sign #:nodoc:
|
24
|
+
def to_s
|
25
|
+
super << '-plus'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class SignMinus < Sign #:nodoc:
|
30
|
+
def to_s
|
31
|
+
super << '-minus'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Chronic
|
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
|
+
token.tag scan_for_all(token)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# token - The Token object we want to scan.
|
18
|
+
#
|
19
|
+
# Returns a new Pointer object.
|
20
|
+
def self.scan_for_all(token)
|
21
|
+
scan_for token, self,
|
22
|
+
{
|
23
|
+
/[PMCE][DS]T|UTC/i => :tz,
|
24
|
+
/(tzminus)?\d{2}:?\d{2}/ => :tz
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
'timezone'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/chronic/time.rb
ADDED
@@ -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, width = nil, hours12 = false)
|
10
|
+
hour >= 0 && hour <= (hours12 ? 12 : 24) && (width.nil? || width > 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Checks if given number could be minute
|
14
|
+
def self.could_be_minute?(minute, width = nil)
|
15
|
+
minute >= 0 && minute <= 60 && (width.nil? || width <= 2)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Checks if given number could be second
|
19
|
+
def self.could_be_second?(second, width = nil)
|
20
|
+
second >= 0 && second <= 60 && (width.nil? || width <= 2)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Checks if given number could be subsecond
|
24
|
+
def self.could_be_subsecond?(subsecond, width = nil)
|
25
|
+
subsecond >= 0 && subsecond <= 999999 && (width.nil? || width > 0)
|
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
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Chronic
|
2
|
+
class Token
|
3
|
+
|
4
|
+
attr_accessor :word
|
5
|
+
attr_accessor :tags
|
6
|
+
|
7
|
+
attr_reader :text
|
8
|
+
attr_reader :position
|
9
|
+
|
10
|
+
def initialize(word, text = nil, position = 0)
|
11
|
+
@word = word
|
12
|
+
@tags = []
|
13
|
+
@text = text
|
14
|
+
@position = position
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(token)
|
18
|
+
token.word == @word.downcase
|
19
|
+
end
|
20
|
+
|
21
|
+
# Tag this token with the specified tag.
|
22
|
+
#
|
23
|
+
# new_tag - The new Tag object.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def tag(new_tag)
|
27
|
+
@tags << new_tag if new_tag
|
28
|
+
end
|
29
|
+
|
30
|
+
# Remove all tags of the given class.
|
31
|
+
#
|
32
|
+
# tag_class - The tag Class to remove.
|
33
|
+
#
|
34
|
+
# Returns nothing.
|
35
|
+
def untag(tag_class)
|
36
|
+
@tags.delete_if { |m| m.kind_of? tag_class }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns true if this token has any tags.
|
40
|
+
def tagged?
|
41
|
+
@tags.size > 0
|
42
|
+
end
|
43
|
+
|
44
|
+
# tag_class - The tag Class to search for.
|
45
|
+
#
|
46
|
+
# Returns The first Tag that matches the given class.
|
47
|
+
def get_tag(tag_class)
|
48
|
+
@tags.find { |m| m.kind_of? tag_class }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Print this Token in a pretty way
|
52
|
+
def to_s
|
53
|
+
@word + '(' + @tags.join(', ') + ') '
|
54
|
+
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Chronic
|
2
|
+
module Tokenizer
|
3
|
+
def self.char_type(char)
|
4
|
+
case char
|
5
|
+
when '.'
|
6
|
+
:period
|
7
|
+
when /[[:alpha:]]/
|
8
|
+
:letter
|
9
|
+
when /[[:digit:]]/
|
10
|
+
:digit
|
11
|
+
when /[[:space:]]/
|
12
|
+
:space
|
13
|
+
when /[[:punct:]]/
|
14
|
+
:punct
|
15
|
+
else
|
16
|
+
:other
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Proccess text to tokens
|
21
|
+
def self.tokenize(text)
|
22
|
+
tokens = []
|
23
|
+
index = 0
|
24
|
+
previos_index = 0
|
25
|
+
text.each_char do |char|
|
26
|
+
type = char_type(char)
|
27
|
+
if type == :space
|
28
|
+
tokens << Token.new(text[previos_index...index], text, previos_index)
|
29
|
+
previos_index = index+1
|
30
|
+
end
|
31
|
+
index += 1
|
32
|
+
end
|
33
|
+
tokens << Token.new(text[previos_index...index], text, previos_index)
|
34
|
+
tokens
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
unless defined? Chronic
|
2
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
3
|
+
require 'chronic'
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'minitest/autorun'
|
7
|
+
|
8
|
+
class TestCase < MiniTest::Test
|
9
|
+
def self.test(name, &block)
|
10
|
+
define_method("test_#{name.gsub(/\W/, '_')}", &block) if block
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestChronic < TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
# Wed Aug 16 14:00:00 UTC 2006
|
7
|
+
@now = Time.local(2006, 8, 16, 14, 0, 0, 0)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_pre_normalize
|
11
|
+
assert_equal Chronic::Parser.new.pre_normalize('12:55 pm'), Chronic::Parser.new.pre_normalize('12.55 pm')
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_pre_normalize_numerized_string
|
15
|
+
string = 'two and a half years'
|
16
|
+
assert_equal Numerizer.numerize(string), Chronic::Parser.new.pre_normalize(string)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_post_normalize_am_pm_aliases
|
20
|
+
# affect wanted patterns
|
21
|
+
|
22
|
+
tokens = [Chronic::Token.new("5:00"), Chronic::Token.new("morning")]
|
23
|
+
tokens[0].tag(Chronic::RepeaterTime.new("5:00"))
|
24
|
+
tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
|
25
|
+
|
26
|
+
assert_equal :morning, tokens[1].tags[0].type
|
27
|
+
|
28
|
+
tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
|
29
|
+
|
30
|
+
assert_equal :am, tokens[1].tags[0].type
|
31
|
+
assert_equal 2, tokens.size
|
32
|
+
|
33
|
+
# don't affect unwanted patterns
|
34
|
+
|
35
|
+
tokens = [Chronic::Token.new("friday"), Chronic::Token.new("morning")]
|
36
|
+
tokens[0].tag(Chronic::RepeaterDayName.new(:friday))
|
37
|
+
tokens[1].tag(Chronic::RepeaterDayPortion.new(:morning))
|
38
|
+
|
39
|
+
assert_equal :morning, tokens[1].tags[0].type
|
40
|
+
|
41
|
+
tokens = Chronic::Handlers.dealias_and_disambiguate_times(tokens, {})
|
42
|
+
|
43
|
+
assert_equal :morning, tokens[1].tags[0].type
|
44
|
+
assert_equal 2, tokens.size
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_guess
|
48
|
+
span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0))
|
49
|
+
assert_equal Time.local(2006, 8, 16, 12), Chronic::Parser.new.guess(span)
|
50
|
+
|
51
|
+
span = Chronic::Span.new(Time.local(2006, 8, 16, 0), Time.local(2006, 8, 17, 0, 0, 1))
|
52
|
+
assert_equal Time.local(2006, 8, 16, 12), Chronic::Parser.new.guess(span)
|
53
|
+
|
54
|
+
span = Chronic::Span.new(Time.local(2006, 11), Time.local(2006, 12))
|
55
|
+
assert_equal Time.local(2006, 11, 16), Chronic::Parser.new.guess(span)
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_endian_definitions
|
59
|
+
# middle, little
|
60
|
+
endians = [
|
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
|
+
Chronic::Handler.new([:scalar_day, :repeater_month_name, :scalar_year, :separator_at?, 'time?'], :handle_sd_rmn_sy)
|
66
|
+
]
|
67
|
+
|
68
|
+
assert_equal endians, Chronic::SpanDictionary.new.definitions[:endian]
|
69
|
+
|
70
|
+
defs = Chronic::SpanDictionary.new(:endian_precedence => :little).definitions
|
71
|
+
assert_equal endians.reverse, defs[:endian]
|
72
|
+
|
73
|
+
defs = Chronic::SpanDictionary.new(:endian_precedence => [:little, :middle]).definitions
|
74
|
+
assert_equal endians.reverse, defs[:endian]
|
75
|
+
|
76
|
+
assert_raises(ArgumentError) do
|
77
|
+
Chronic::SpanDictionary.new(:endian_precedence => :invalid).definitions
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_debug
|
82
|
+
require 'stringio'
|
83
|
+
$stdout = StringIO.new
|
84
|
+
Chronic.debug = true
|
85
|
+
|
86
|
+
Chronic.parse 'now'
|
87
|
+
assert $stdout.string.include?('this(grabber-this)')
|
88
|
+
ensure
|
89
|
+
$stdout = STDOUT
|
90
|
+
Chronic.debug = false
|
91
|
+
end
|
92
|
+
|
93
|
+
# Chronic.construct
|
94
|
+
|
95
|
+
def test_normal
|
96
|
+
assert_equal Time.local(2006, 1, 2, 0, 0, 0), Chronic.construct(2006, 1, 2, 0, 0, 0)
|
97
|
+
assert_equal Time.local(2006, 1, 2, 3, 0, 0), Chronic.construct(2006, 1, 2, 3, 0, 0)
|
98
|
+
assert_equal Time.local(2006, 1, 2, 3, 4, 0), Chronic.construct(2006, 1, 2, 3, 4, 0)
|
99
|
+
assert_equal Time.local(2006, 1, 2, 3, 4, 5), Chronic.construct(2006, 1, 2, 3, 4, 5)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_second_overflow
|
103
|
+
assert_equal Time.local(2006, 1, 1, 0, 1, 30), Chronic.construct(2006, 1, 1, 0, 0, 90)
|
104
|
+
assert_equal Time.local(2006, 1, 1, 0, 5, 0), Chronic.construct(2006, 1, 1, 0, 0, 300)
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_minute_overflow
|
108
|
+
assert_equal Time.local(2006, 1, 1, 1, 30), Chronic.construct(2006, 1, 1, 0, 90)
|
109
|
+
assert_equal Time.local(2006, 1, 1, 5), Chronic.construct(2006, 1, 1, 0, 300)
|
110
|
+
end
|
111
|
+
|
112
|
+
def test_hour_overflow
|
113
|
+
assert_equal Time.local(2006, 1, 2, 12), Chronic.construct(2006, 1, 1, 36)
|
114
|
+
assert_equal Time.local(2006, 1, 7), Chronic.construct(2006, 1, 1, 144)
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_day_overflow
|
118
|
+
assert_equal Time.local(2006, 2, 1), Chronic.construct(2006, 1, 32)
|
119
|
+
assert_equal Time.local(2006, 3, 5), Chronic.construct(2006, 2, 33)
|
120
|
+
assert_equal Time.local(2004, 3, 4), Chronic.construct(2004, 2, 33)
|
121
|
+
assert_equal Time.local(2000, 3, 4), Chronic.construct(2000, 2, 33)
|
122
|
+
|
123
|
+
assert_raises(RuntimeError) do
|
124
|
+
Chronic.construct(2006, 1, 57)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_month_overflow
|
129
|
+
assert_equal Time.local(2006, 1), Chronic.construct(2005, 13)
|
130
|
+
assert_equal Time.local(2005, 12), Chronic.construct(2000, 72)
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_time
|
134
|
+
org = Chronic.time_class
|
135
|
+
begin
|
136
|
+
Chronic.time_class = ::Time
|
137
|
+
assert_equal ::Time.new(2013, 8, 27, 20, 30, 40, '+05:30'), Chronic.construct(2013, 8, 27, 20, 30, 40, '+05:30')
|
138
|
+
assert_equal ::Time.new(2013, 8, 27, 20, 30, 40, '-08:00'), Chronic.construct(2013, 8, 27, 20, 30, 40, -28800)
|
139
|
+
ensure
|
140
|
+
Chronic.time_class = org
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_date
|
145
|
+
org = Chronic.time_class
|
146
|
+
begin
|
147
|
+
Chronic.time_class = ::Date
|
148
|
+
assert_equal Date.new(2013, 8, 27), Chronic.construct(2013, 8, 27)
|
149
|
+
ensure
|
150
|
+
Chronic.time_class = org
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def test_datetime
|
155
|
+
org = Chronic.time_class
|
156
|
+
begin
|
157
|
+
Chronic.time_class = ::DateTime
|
158
|
+
assert_equal DateTime.new(2013, 8, 27, 20, 30, 40, '+05:30'), Chronic.construct(2013, 8, 27, 20, 30, 40, '+05:30')
|
159
|
+
assert_equal DateTime.new(2013, 8, 27, 20, 30, 40, '-08:00'), Chronic.construct(2013, 8, 27, 20, 30, 40, -28800)
|
160
|
+
ensure
|
161
|
+
Chronic.time_class = org
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_valid_options
|
166
|
+
options = {
|
167
|
+
:context => :future,
|
168
|
+
:now => nil,
|
169
|
+
:hours24 => nil,
|
170
|
+
:week_start => :sunday,
|
171
|
+
:guess => true,
|
172
|
+
:ambiguous_time_range => 6,
|
173
|
+
:endian_precedence => [:middle, :little],
|
174
|
+
:ambiguous_year_future_bias => 50
|
175
|
+
}
|
176
|
+
refute_nil Chronic.parse('now', options)
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_invalid_options
|
180
|
+
assert_raises(ArgumentError) { Chronic.parse('now', foo: 'boo') }
|
181
|
+
assert_raises(ArgumentError) { Chronic.parse('now', time_class: Time) }
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_activesupport
|
185
|
+
=begin
|
186
|
+
# ActiveSupport needs MiniTest '~> 4.2' which conflicts with '~> 5.0'
|
187
|
+
require 'active_support/time'
|
188
|
+
org = Chronic.time_class
|
189
|
+
org_zone = ::Time.zone
|
190
|
+
begin
|
191
|
+
::Time.zone = "Tokyo"
|
192
|
+
Chronic.time_class = ::Time.zone
|
193
|
+
assert_equal Time.new(2013, 8, 27, 20, 30, 40, '+09:00'), Chronic.construct(2013, 8, 27, 20, 30, 40)
|
194
|
+
::Time.zone = "Indiana (East)"
|
195
|
+
Chronic.time_class = ::Time.zone
|
196
|
+
assert_equal Time.new(2013, 8, 27, 20, 30, 40, -14400), Chronic.construct(2013, 8, 27, 20, 30, 40)
|
197
|
+
ensure
|
198
|
+
Chronic.time_class = org
|
199
|
+
::Time.zone = org_zone
|
200
|
+
end
|
201
|
+
=end
|
202
|
+
end
|
203
|
+
end
|