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
@@ -0,0 +1,86 @@
1
+ module Chronic
2
+ class RepeaterWeekday < Repeater #:nodoc:
3
+ DAY_SECONDS = 86400 # (24 * 60 * 60)
4
+ DAYS = {
5
+ :sunday => 0,
6
+ :monday => 1,
7
+ :tuesday => 2,
8
+ :wednesday => 3,
9
+ :thursday => 4,
10
+ :friday => 5,
11
+ :saturday => 6
12
+ }
13
+
14
+ def initialize(type, options = {})
15
+ super
16
+ @current_weekday_start = nil
17
+ end
18
+
19
+ def next(pointer)
20
+ super
21
+
22
+ direction = pointer == :future ? 1 : -1
23
+
24
+ unless @current_weekday_start
25
+ @current_weekday_start = Chronic.construct(@now.year, @now.month, @now.day)
26
+ @current_weekday_start += direction * DAY_SECONDS
27
+
28
+ until is_weekday?(@current_weekday_start.wday)
29
+ @current_weekday_start += direction * DAY_SECONDS
30
+ end
31
+ else
32
+ loop do
33
+ @current_weekday_start += direction * DAY_SECONDS
34
+ break if is_weekday?(@current_weekday_start.wday)
35
+ end
36
+ end
37
+
38
+ Span.new(@current_weekday_start, @current_weekday_start + DAY_SECONDS)
39
+ end
40
+
41
+ def this(pointer = :future)
42
+ super
43
+
44
+ case pointer
45
+ when :past
46
+ self.next(:past)
47
+ when :future, :none
48
+ self.next(:future)
49
+ end
50
+ end
51
+
52
+ def offset(span, amount, pointer)
53
+ direction = pointer == :future ? 1 : -1
54
+
55
+ num_weekdays_passed = 0; offset = 0
56
+ until num_weekdays_passed == amount
57
+ offset += direction * DAY_SECONDS
58
+ num_weekdays_passed += 1 if is_weekday?((span.begin+offset).wday)
59
+ end
60
+
61
+ span + offset
62
+ end
63
+
64
+ def width
65
+ DAY_SECONDS
66
+ end
67
+
68
+ def to_s
69
+ super << '-weekday'
70
+ end
71
+
72
+ private
73
+
74
+ def is_weekend?(day)
75
+ day == symbol_to_number(:saturday) || day == symbol_to_number(:sunday)
76
+ end
77
+
78
+ def is_weekday?(day)
79
+ !is_weekend?(day)
80
+ end
81
+
82
+ def symbol_to_number(sym)
83
+ DAYS[sym] || raise("Invalid symbol specified")
84
+ end
85
+ end
86
+ end
@@ -1,60 +1,67 @@
1
- class Chronic::RepeaterWeekend < Chronic::Repeater #:nodoc:
2
- WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
3
-
4
- def next(pointer)
5
- super
6
-
7
- if !@current_week_start
1
+ module Chronic
2
+ class RepeaterWeekend < Repeater #:nodoc:
3
+ WEEKEND_SECONDS = 172_800 # (2 * 24 * 60 * 60)
4
+
5
+ def initialize(type, 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
+
8
37
  case pointer
9
- when :future
10
- saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
38
+ when :future, :none
39
+ saturday_repeater = RepeaterDayName.new(:saturday)
11
40
  saturday_repeater.start = @now
12
- next_saturday_span = saturday_repeater.next(:future)
13
- @current_week_start = next_saturday_span.begin
41
+ this_saturday_span = saturday_repeater.this(:future)
42
+ Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
14
43
  when :past
15
- saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
16
- saturday_repeater.start = (@now + Chronic::RepeaterDay::DAY_SECONDS)
17
- last_saturday_span = saturday_repeater.next(:past)
18
- @current_week_start = last_saturday_span.begin
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)
19
48
  end
20
- else
49
+ end
50
+
51
+ def offset(span, amount, pointer)
21
52
  direction = pointer == :future ? 1 : -1
22
- @current_week_start += direction * Chronic::RepeaterWeek::WEEK_SECONDS
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))
23
57
  end
24
-
25
- Chronic::Span.new(@current_week_start, @current_week_start + WEEKEND_SECONDS)
26
- end
27
-
28
- def this(pointer = :future)
29
- super
30
-
31
- case pointer
32
- when :future, :none
33
- saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
34
- saturday_repeater.start = @now
35
- this_saturday_span = saturday_repeater.this(:future)
36
- Chronic::Span.new(this_saturday_span.begin, this_saturday_span.begin + WEEKEND_SECONDS)
37
- when :past
38
- saturday_repeater = Chronic::RepeaterDayName.new(:saturday)
39
- saturday_repeater.start = @now
40
- last_saturday_span = saturday_repeater.this(:past)
41
- Chronic::Span.new(last_saturday_span.begin, last_saturday_span.begin + WEEKEND_SECONDS)
58
+
59
+ def width
60
+ WEEKEND_SECONDS
61
+ end
62
+
63
+ def to_s
64
+ super << '-weekend'
42
65
  end
43
66
  end
44
-
45
- def offset(span, amount, pointer)
46
- direction = pointer == :future ? 1 : -1
47
- weekend = Chronic::RepeaterWeekend.new(:weekend)
48
- weekend.start = span.begin
49
- start = weekend.next(pointer).begin + (amount - 1) * direction * Chronic::RepeaterWeek::WEEK_SECONDS
50
- Chronic::Span.new(start, start + (span.end - span.begin))
51
- end
52
-
53
- def width
54
- WEEKEND_SECONDS
55
- end
56
-
57
- def to_s
58
- super << '-weekend'
59
- end
60
- end
67
+ end
@@ -1,58 +1,78 @@
1
- class Chronic::RepeaterYear < Chronic::Repeater #:nodoc:
2
-
3
- def next(pointer)
4
- super
5
-
6
- if !@current_year_start
1
+ module Chronic
2
+ class RepeaterYear < Repeater #:nodoc:
3
+ YEAR_SECONDS = 31536000 # 365 * 24 * 60 * 60
4
+
5
+ def initialize(type, 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
+
7
31
  case pointer
8
32
  when :future
9
- @current_year_start = Time.construct(@now.year + 1)
33
+ this_year_start = Chronic.construct(@now.year, @now.month, @now.day + 1)
34
+ this_year_end = Chronic.construct(@now.year + 1, 1, 1)
10
35
  when :past
11
- @current_year_start = Time.construct(@now.year - 1)
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]
12
75
  end
13
- else
14
- diff = pointer == :future ? 1 : -1
15
- @current_year_start = Time.construct(@current_year_start.year + diff)
16
76
  end
17
-
18
- Chronic::Span.new(@current_year_start, Time.construct(@current_year_start.year + 1))
19
- end
20
-
21
- def this(pointer = :future)
22
- super
23
-
24
- case pointer
25
- when :future
26
- this_year_start = Time.construct(@now.year, @now.month, @now.day) + Chronic::RepeaterDay::DAY_SECONDS
27
- this_year_end = Time.construct(@now.year + 1, 1, 1)
28
- when :past
29
- this_year_start = Time.construct(@now.year, 1, 1)
30
- this_year_end = Time.construct(@now.year, @now.month, @now.day)
31
- when :none
32
- this_year_start = Time.construct(@now.year, 1, 1)
33
- this_year_end = Time.construct(@now.year + 1, 1, 1)
34
- end
35
-
36
- Chronic::Span.new(this_year_start, this_year_end)
37
- end
38
-
39
- def offset(span, amount, pointer)
40
- direction = pointer == :future ? 1 : -1
41
-
42
- sb = span.begin
43
- new_begin = Time.construct(sb.year + (amount * direction), sb.month, sb.day, sb.hour, sb.min, sb.sec)
44
-
45
- se = span.end
46
- new_end = Time.construct(se.year + (amount * direction), se.month, se.day, se.hour, se.min, se.sec)
47
-
48
- Chronic::Span.new(new_begin, new_end)
49
- end
50
-
51
- def width
52
- (365 * 24 * 60 * 60)
53
- end
54
-
55
- def to_s
56
- super << '-year'
57
77
  end
58
- end
78
+ end
@@ -1,74 +1,81 @@
1
1
  module Chronic
2
+ class Scalar < Tag
3
+ DAY_PORTIONS = %w( am pm morning afternoon evening night )
2
4
 
3
- class Scalar < Tag #:nodoc:
4
- def self.scan(tokens)
5
- # for each token
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)
6
13
  tokens.each_index do |i|
7
- if t = self.scan_for_scalars(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
8
- if t = self.scan_for_days(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
9
- if t = self.scan_for_months(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
10
- if t = self.scan_for_years(tokens[i], tokens[i + 1]) then tokens[i].tag(t) end
11
- end
12
- tokens
13
- end
14
-
15
- def self.scan_for_scalars(token, post_token)
16
- if token.word =~ /^\d*$/
17
- unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
18
- return Scalar.new(token.word.to_i)
14
+ token = tokens[i]
15
+ post_token = tokens[i + 1]
16
+ if token.word =~ /^\d+$/
17
+ scalar = token.word.to_i
18
+ token.tag(Scalar.new(scalar))
19
+ token.tag(ScalarSubsecond.new(scalar)) if Chronic::Time::could_be_subsecond?(scalar)
20
+ token.tag(ScalarSecond.new(scalar)) if Chronic::Time::could_be_second?(scalar)
21
+ token.tag(ScalarMinute.new(scalar)) if Chronic::Time::could_be_minute?(scalar)
22
+ token.tag(ScalarHour.new(scalar)) if Chronic::Time::could_be_hour?(scalar)
23
+ unless post_token and DAY_PORTIONS.include?(post_token.word)
24
+ token.tag(ScalarDay.new(scalar)) if Chronic::Date::could_be_day?(scalar)
25
+ token.tag(ScalarMonth.new(scalar)) if Chronic::Date::could_be_month?(scalar)
26
+ if Chronic::Date::could_be_year?(scalar)
27
+ year = Chronic::Date::make_year(scalar, options[:ambiguous_year_future_bias])
28
+ token.tag(ScalarYear.new(year.to_i))
29
+ end
30
+ end
19
31
  end
20
32
  end
21
- return nil
22
33
  end
23
-
24
- def self.scan_for_days(token, post_token)
25
- if token.word =~ /^\d\d?$/
26
- unless token.word.to_i > 31 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
27
- return ScalarDay.new(token.word.to_i)
28
- end
29
- end
30
- return nil
34
+
35
+ def to_s
36
+ 'scalar'
31
37
  end
32
-
33
- def self.scan_for_months(token, post_token)
34
- if token.word =~ /^\d\d?$/
35
- unless token.word.to_i > 12 || (post_token && %w{am pm morning afternoon evening night}.include?(post_token))
36
- return ScalarMonth.new(token.word.to_i)
37
- end
38
- end
39
- return nil
38
+ end
39
+
40
+ class ScalarSubsecond < Scalar #:nodoc:
41
+ def to_s
42
+ super << '-subsecond-' << @type.to_s
40
43
  end
41
-
42
- def self.scan_for_years(token, post_token)
43
- if token.word =~ /^([1-9]\d)?\d\d?$/
44
- unless post_token && %w{am pm morning afternoon evening night}.include?(post_token)
45
- return ScalarYear.new(token.word.to_i)
46
- end
47
- end
48
- return nil
44
+ end
45
+
46
+ class ScalarSecond < Scalar #:nodoc:
47
+ def to_s
48
+ super << '-second-' << @type.to_s
49
49
  end
50
-
50
+ end
51
+
52
+ class ScalarMinute < Scalar #:nodoc:
51
53
  def to_s
52
- 'scalar'
54
+ super << '-minute-' << @type.to_s
55
+ end
56
+ end
57
+
58
+ class ScalarHour < Scalar #:nodoc:
59
+ def to_s
60
+ super << '-hour-' << @type.to_s
53
61
  end
54
62
  end
55
-
63
+
56
64
  class ScalarDay < Scalar #:nodoc:
57
65
  def to_s
58
66
  super << '-day-' << @type.to_s
59
67
  end
60
68
  end
61
-
69
+
62
70
  class ScalarMonth < Scalar #:nodoc:
63
71
  def to_s
64
72
  super << '-month-' << @type.to_s
65
73
  end
66
74
  end
67
-
75
+
68
76
  class ScalarYear < Scalar #:nodoc:
69
77
  def to_s
70
78
  super << '-year-' << @type.to_s
71
79
  end
72
80
  end
73
-
74
81
  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