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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.gitlab-ci.yml +14 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +3 -0
  6. data/HISTORY.md +252 -0
  7. data/LICENSE +21 -0
  8. data/README.md +188 -0
  9. data/Rakefile +68 -0
  10. data/chronic.gemspec +25 -0
  11. data/lib/chronic.rb +155 -0
  12. data/lib/chronic/date.rb +81 -0
  13. data/lib/chronic/definition.rb +128 -0
  14. data/lib/chronic/dictionary.rb +36 -0
  15. data/lib/chronic/handler.rb +97 -0
  16. data/lib/chronic/handlers.rb +672 -0
  17. data/lib/chronic/mini_date.rb +38 -0
  18. data/lib/chronic/parser.rb +222 -0
  19. data/lib/chronic/repeaters/repeater_day.rb +54 -0
  20. data/lib/chronic/repeaters/repeater_day_name.rb +53 -0
  21. data/lib/chronic/repeaters/repeater_day_portion.rb +109 -0
  22. data/lib/chronic/repeaters/repeater_fortnight.rb +72 -0
  23. data/lib/chronic/repeaters/repeater_hour.rb +59 -0
  24. data/lib/chronic/repeaters/repeater_minute.rb +59 -0
  25. data/lib/chronic/repeaters/repeater_month.rb +80 -0
  26. data/lib/chronic/repeaters/repeater_month_name.rb +95 -0
  27. data/lib/chronic/repeaters/repeater_quarter.rb +59 -0
  28. data/lib/chronic/repeaters/repeater_quarter_name.rb +40 -0
  29. data/lib/chronic/repeaters/repeater_season.rb +111 -0
  30. data/lib/chronic/repeaters/repeater_season_name.rb +43 -0
  31. data/lib/chronic/repeaters/repeater_second.rb +43 -0
  32. data/lib/chronic/repeaters/repeater_time.rb +159 -0
  33. data/lib/chronic/repeaters/repeater_week.rb +76 -0
  34. data/lib/chronic/repeaters/repeater_weekday.rb +86 -0
  35. data/lib/chronic/repeaters/repeater_weekend.rb +67 -0
  36. data/lib/chronic/repeaters/repeater_year.rb +78 -0
  37. data/lib/chronic/season.rb +26 -0
  38. data/lib/chronic/span.rb +31 -0
  39. data/lib/chronic/tag.rb +89 -0
  40. data/lib/chronic/tags/grabber.rb +29 -0
  41. data/lib/chronic/tags/ordinal.rb +52 -0
  42. data/lib/chronic/tags/pointer.rb +28 -0
  43. data/lib/chronic/tags/repeater.rb +160 -0
  44. data/lib/chronic/tags/scalar.rb +89 -0
  45. data/lib/chronic/tags/separator.rb +123 -0
  46. data/lib/chronic/tags/sign.rb +35 -0
  47. data/lib/chronic/tags/time_zone.rb +32 -0
  48. data/lib/chronic/time.rb +40 -0
  49. data/lib/chronic/token.rb +61 -0
  50. data/lib/chronic/tokenizer.rb +38 -0
  51. data/lib/chronic/version.rb +3 -0
  52. data/test/helper.rb +12 -0
  53. data/test/test_chronic.rb +203 -0
  54. data/test/test_daylight_savings.rb +122 -0
  55. data/test/test_handler.rb +128 -0
  56. data/test/test_mini_date.rb +32 -0
  57. data/test/test_parsing.rb +1537 -0
  58. data/test/test_repeater_day_name.rb +51 -0
  59. data/test/test_repeater_day_portion.rb +254 -0
  60. data/test/test_repeater_fortnight.rb +62 -0
  61. data/test/test_repeater_hour.rb +68 -0
  62. data/test/test_repeater_minute.rb +34 -0
  63. data/test/test_repeater_month.rb +50 -0
  64. data/test/test_repeater_month_name.rb +56 -0
  65. data/test/test_repeater_quarter.rb +70 -0
  66. data/test/test_repeater_quarter_name.rb +198 -0
  67. data/test/test_repeater_season.rb +40 -0
  68. data/test/test_repeater_time.rb +88 -0
  69. data/test/test_repeater_week.rb +115 -0
  70. data/test/test_repeater_weekday.rb +55 -0
  71. data/test/test_repeater_weekend.rb +74 -0
  72. data/test/test_repeater_year.rb +69 -0
  73. data/test/test_span.rb +23 -0
  74. data/test/test_token.rb +31 -0
  75. metadata +215 -0
@@ -0,0 +1,67 @@
1
+ module Chronic
2
+ class RepeaterWeekend < Repeater #:nodoc:
3
+ WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
4
+
5
+ def initialize(type, width = nil, options = {})
6
+ super
7
+ @current_week_start = nil
8
+ end
9
+
10
+ def next(pointer)
11
+ super
12
+
13
+ unless @current_week_start
14
+ case pointer
15
+ when :future
16
+ saturday_repeater = RepeaterDayName.new(:saturday)
17
+ saturday_repeater.start = @now
18
+ next_saturday_span = saturday_repeater.next(:future)
19
+ @current_week_start = next_saturday_span.begin
20
+ when :past
21
+ saturday_repeater = RepeaterDayName.new(:saturday)
22
+ saturday_repeater.start = (@now + RepeaterDay::DAY_SECONDS)
23
+ last_saturday_span = saturday_repeater.next(:past)
24
+ @current_week_start = last_saturday_span.begin
25
+ end
26
+ else
27
+ direction = pointer == :future ? 1 : -1
28
+ @current_week_start += direction * RepeaterWeek::WEEK_SECONDS
29
+ end
30
+
31
+ Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
32
+ end
33
+
34
+ def this(pointer = :future)
35
+ super
36
+
37
+ case pointer
38
+ when :future, :none
39
+ saturday_repeater = RepeaterDayName.new(:saturday)
40
+ saturday_repeater.start = @now
41
+ this_saturday_span = saturday_repeater.this(:future)
42
+ Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
43
+ when :past
44
+ saturday_repeater = RepeaterDayName.new(:saturday)
45
+ saturday_repeater.start = @now
46
+ last_saturday_span = saturday_repeater.this(:past)
47
+ Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
48
+ end
49
+ end
50
+
51
+ def offset(span, amount, pointer)
52
+ direction = pointer == :future ? 1 : -1
53
+ weekend = RepeaterWeekend.new(:weekend)
54
+ weekend.start = span.begin
55
+ start = weekend.next(pointer).begin + (amount - 1) * direction * RepeaterWeek::WEEK_SECONDS
56
+ Span.new(start, start + (span.end - span.begin))
57
+ end
58
+
59
+ def width
60
+ WEEKEND_SECONDS
61
+ end
62
+
63
+ def to_s
64
+ super << '-weekend'
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,78 @@
1
+ module Chronic
2
+ class RepeaterYear < Repeater #:nodoc:
3
+ YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
4
+
5
+ def initialize(type, width = nil, options = {})
6
+ super
7
+ @current_year_start = nil
8
+ end
9
+
10
+ def next(pointer)
11
+ super
12
+
13
+ unless @current_year_start
14
+ case pointer
15
+ when :future
16
+ @current_year_start = Chronic.construct(@now.year + 1)
17
+ when :past
18
+ @current_year_start = Chronic.construct(@now.year - 1)
19
+ end
20
+ else
21
+ diff = pointer == :future ? 1 : -1
22
+ @current_year_start = Chronic.construct(@current_year_start.year + diff)
23
+ end
24
+
25
+ Span.new(@current_year_start, Chronic.construct(@current_year_start.year + 1))
26
+ end
27
+
28
+ def this(pointer = :future)
29
+ super
30
+
31
+ case pointer
32
+ when :future
33
+ this_year_start = Chronic.construct(@now.year, @now.month, @now.day + 1)
34
+ this_year_end = Chronic.construct(@now.year + 1, 1, 1)
35
+ when :past
36
+ this_year_start = Chronic.construct(@now.year, 1, 1)
37
+ this_year_end = Chronic.construct(@now.year, @now.month, @now.day)
38
+ when :none
39
+ this_year_start = Chronic.construct(@now.year, 1, 1)
40
+ this_year_end = Chronic.construct(@now.year + 1, 1, 1)
41
+ end
42
+
43
+ Span.new(this_year_start, this_year_end)
44
+ end
45
+
46
+ def offset(span, amount, pointer)
47
+ direction = pointer == :future ? 1 : -1
48
+ new_begin = build_offset_time(span.begin, amount, direction)
49
+ new_end = build_offset_time(span.end, amount, direction)
50
+ Span.new(new_begin, new_end)
51
+ end
52
+
53
+ def width
54
+ YEAR_SECONDS
55
+ end
56
+
57
+ def to_s
58
+ super << '-year'
59
+ end
60
+
61
+ private
62
+
63
+ def build_offset_time(time, amount, direction)
64
+ year = time.year + (amount * direction)
65
+ days = month_days(year, time.month)
66
+ day = time.day > days ? days : time.day
67
+ Chronic.construct(year, time.month, day, time.hour, time.min, time.sec)
68
+ end
69
+
70
+ def month_days(year, month)
71
+ if ::Date.leap?(year)
72
+ RepeaterMonth::MONTH_DAYS_LEAP[month - 1]
73
+ else
74
+ RepeaterMonth::MONTH_DAYS[month - 1]
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,26 @@
1
+ module Chronic
2
+ class Season
3
+
4
+ attr_reader :start
5
+ attr_reader :end
6
+
7
+ def initialize(start_date, end_date)
8
+ @start = start_date
9
+ @end = end_date
10
+ end
11
+
12
+ def self.find_next_season(season, pointer)
13
+ lookup = [:spring, :summer, :autumn, :winter]
14
+ next_season_num = (lookup.index(season) + 1 * pointer) % 4
15
+ lookup[next_season_num]
16
+ end
17
+
18
+ def self.season_after(season)
19
+ find_next_season(season, +1)
20
+ end
21
+
22
+ def self.season_before(season)
23
+ find_next_season(season, -1)
24
+ end
25
+ end
26
+ 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,89 @@
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
+ # time - Set the start Time for this Tag.
17
+ def start=(time)
18
+ @now = time
19
+ end
20
+
21
+ class << self
22
+ # Public: Scan an Array of Token objects.
23
+ #
24
+ # tokens - An Array of tokens to scan.
25
+ # options - The Hash of options specified in Chronic::parse.
26
+ #
27
+ # Returns an Array of tokens.
28
+ def scan(tokens, options)
29
+ raise NotImplementedError, 'Subclasses must override scan!'
30
+ end
31
+
32
+ private
33
+
34
+ # Internal: Match item and create respective Tag class.
35
+ # When item is a Symbol it will match only when it's identical to Token.
36
+ # When it's a String it will case-insesitively match partial token,
37
+ # but only if item's last char have different type than token text's next char.
38
+ # When item is a Regexp it will match by it.
39
+ #
40
+ # item - Item to match. It can be String, Symbol or Regexp.
41
+ # klass - Tag class to create.
42
+ # symbol - Tag type as symbol or string to pass to Tag class.
43
+ # token - Token to match against.
44
+ # options - Options as hash to pass to Tag class.
45
+ #
46
+ # Returns an instance of specified Tag klass or nil if item didn't match.
47
+ def match_item(item, klass, symbol, token, options)
48
+ match = false
49
+ case item
50
+ when String
51
+ item_type = Tokenizer.char_type(item.to_s[-1])
52
+ text_type = token.text[token.position+item.length]
53
+ text_type = Tokenizer.char_type(text_type) if text_type
54
+ compatible = true
55
+ compatible = item_type != text_type if text_type && (item_type == :letter || item_type == :digit)
56
+ match = compatible && token.text[token.position, item.length].casecmp(item).zero?
57
+ when Symbol
58
+ match = token.word == item.to_s
59
+ when Regexp
60
+ match = token.word =~ item
61
+ end
62
+ return klass.new(symbol, nil, options) if match
63
+ nil
64
+ end
65
+
66
+ # Internal: Scan for specified items and create respective Tag class.
67
+ #
68
+ # token - Token to match against.
69
+ # klass - Tag class to create.
70
+ # items - Item(s) to match. It can be Hash, String, Symbol or Regexp.
71
+ # Hash keys can be String, Symbol or Regexp, but values much be Symbol.
72
+ # options - Options as hash to pass to Tag class.
73
+ #
74
+ # Returns an instance of specified Tag klass or nil if item(s) didn't match.
75
+ def scan_for(token, klass, items, options = {})
76
+ if items.kind_of?(Hash)
77
+ items.each do |item, symbol|
78
+ scanned = match_item(item, klass, symbol, token, options)
79
+ return scanned if scanned
80
+ end
81
+ else
82
+ return match_item(items, klass, token.word, token, options)
83
+ end
84
+ nil
85
+ end
86
+ end
87
+
88
+ end
89
+ 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,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+)(st|nd|rd|th|\.)$/
14
+ width = $1.length
15
+ ordinal = $1.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
+ 'past' => :past,
20
+ /^future|in$/i => :future,
21
+ }
22
+ end
23
+
24
+ def to_s
25
+ 'pointer-' << @type.to_s
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,160 @@
1
+ module Chronic
2
+ class Repeater < Tag
3
+
4
+ # Scan an Array of Token objects and apply any necessary Repeater
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_quarter_names(token, options)
14
+ token.tag scan_for_season_names(token, options)
15
+ token.tag scan_for_month_names(token, options)
16
+ token.tag scan_for_day_names(token, options)
17
+ token.tag scan_for_day_portions(token, options)
18
+ token.tag scan_for_times(token, options)
19
+ token.tag scan_for_units(token, options)
20
+ end
21
+ end
22
+
23
+ # token - The Token object we want to scan.
24
+ #
25
+ # Returns a new Repeater object.
26
+ def self.scan_for_quarter_names(token, options = {})
27
+ scan_for token, RepeaterQuarterName,
28
+ {
29
+ /^q1$/ => :q1,
30
+ /^q2$/ => :q2,
31
+ /^q3$/ => :q3,
32
+ /^q4$/ => :q4
33
+ }, options
34
+ end
35
+
36
+ # token - The Token object we want to scan.
37
+ #
38
+ # Returns a new Repeater object.
39
+ def self.scan_for_season_names(token, options = {})
40
+ scan_for token, RepeaterSeasonName,
41
+ {
42
+ /^springs?$/ => :spring,
43
+ /^summers?$/ => :summer,
44
+ /^(autumn)|(fall)s?$/ => :autumn,
45
+ /^winters?$/ => :winter
46
+ }, options
47
+ end
48
+
49
+ # token - The Token object we want to scan.
50
+ #
51
+ # Returns a new Repeater object.
52
+ def self.scan_for_month_names(token, options = {})
53
+ scan_for token, RepeaterMonthName,
54
+ {
55
+ /^jan[:\.]?(uary)?$/ => :january,
56
+ /^feb[:\.]?(ruary)?$/ => :february,
57
+ /^mar[:\.]?(ch)?$/ => :march,
58
+ /^apr[:\.]?(il)?$/ => :april,
59
+ /^may$/ => :may,
60
+ /^jun[:\.]?e?$/ => :june,
61
+ /^jul[:\.]?y?$/ => :july,
62
+ /^aug[:\.]?(ust)?$/ => :august,
63
+ /^sep[:\.]?(t[:\.]?|tember)?$/ => :september,
64
+ /^oct[:\.]?(ober)?$/ => :october,
65
+ /^nov[:\.]?(ember)?$/ => :november,
66
+ /^dec[:\.]?(ember)?$/ => :december
67
+ }, options
68
+ end
69
+
70
+ # token - The Token object we want to scan.
71
+ #
72
+ # Returns a new Repeater object.
73
+ def self.scan_for_day_names(token, options = {})
74
+ scan_for token, RepeaterDayName,
75
+ {
76
+ /^m[ou]n(day)?$/ => :monday,
77
+ /^t(ue|eu|oo|u)s?(day)?$/ => :tuesday,
78
+ /^we(d|dnes|nds|nns)(day)?$/ => :wednesday,
79
+ /^th(u|ur|urs|ers)(day)?$/ => :thursday,
80
+ /^fr[iy](day)?$/ => :friday,
81
+ /^sat(t?[ue]rday)?$/ => :saturday,
82
+ /^su[nm](day)?$/ => :sunday
83
+ }, options
84
+ end
85
+
86
+ # token - The Token object we want to scan.
87
+ #
88
+ # Returns a new Repeater object.
89
+ def self.scan_for_day_portions(token, options = {})
90
+ scan_for token, RepeaterDayPortion,
91
+ {
92
+ /^ams?$/ => :am,
93
+ /^pms?$/ => :pm,
94
+ /^mornings?$/ => :morning,
95
+ /^afternoons?$/ => :afternoon,
96
+ /^evenings?$/ => :evening,
97
+ /^(night|nite)s?$/ => :night
98
+ }, options
99
+ end
100
+
101
+ # token - The Token object we want to scan.
102
+ #
103
+ # Returns a new Repeater object.
104
+ def self.scan_for_times(token, options = {})
105
+ scan_for token, RepeaterTime, /^\d{1,2}(:?\d{1,2})?([\.:]?\d{1,2}([\.:]\d{1,6})?)?$/, options
106
+ end
107
+
108
+ # token - The Token object we want to scan.
109
+ #
110
+ # Returns a new Repeater object.
111
+ def self.scan_for_units(token, options = {})
112
+ {
113
+ /^years?$/ => :year,
114
+ /^q$/ => :quarter,
115
+ /^seasons?$/ => :season,
116
+ /^months?$/ => :month,
117
+ /^fortnights?$/ => :fortnight,
118
+ /^weeks?$/ => :week,
119
+ /^weekends?$/ => :weekend,
120
+ /^(week|business)days?$/ => :weekday,
121
+ /^days?$/ => :day,
122
+ /^hrs?$/ => :hour,
123
+ /^hours?$/ => :hour,
124
+ /^mins?$/ => :minute,
125
+ /^minutes?$/ => :minute,
126
+ /^secs?$/ => :second,
127
+ /^seconds?$/ => :second
128
+ }.each do |item, symbol|
129
+ if item =~ token.word
130
+ klass_name = 'Repeater' + symbol.to_s.capitalize
131
+ klass = Chronic.const_get(klass_name)
132
+ return klass.new(symbol, nil, options)
133
+ end
134
+ end
135
+ return nil
136
+ end
137
+
138
+ def <=>(other)
139
+ width <=> other.width
140
+ end
141
+
142
+ # returns the width (in seconds or months) of this repeatable.
143
+ def width
144
+ raise('Repeater#width must be overridden in subclasses')
145
+ end
146
+
147
+ # returns the next occurance of this repeatable.
148
+ def next(pointer)
149
+ raise('Start point must be set before calling #next') unless @now
150
+ end
151
+
152
+ def this(pointer)
153
+ raise('Start point must be set before calling #this') unless @now
154
+ end
155
+
156
+ def to_s
157
+ 'repeater'
158
+ end
159
+ end
160
+ end