qreport_time_parser 0.0.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.
@@ -0,0 +1,64 @@
1
+ require 'qreport/time_parser'
2
+
3
+ module Qreport
4
+ class TimeParser
5
+ def self.examples
6
+ now = ::Time.parse("2011-03-10T15:10:37.981304-06:00")
7
+ examples = {
8
+ "now" => "nil 2011-03-10T15:10:37.981304-06:00",
9
+ now.to_s => "nil 2011-03-10T15:10:37.000000-06:00",
10
+ "2011-03-10 15:10:37.981304 -0600" => "nil 2011-03-10T15:10:37.981304-06:00",
11
+ "today" => ":day 2011-03-10T00:00:00.000000-06:00",
12
+ "tomorrow" => ":day 2011-03-11T00:00:00.000000-06:00",
13
+ "yesterday" => ":day 2011-03-09T00:00:00.000000-06:00",
14
+ "9:15am yesterday" => ":min 2011-03-09T09:15:00.000000-06:00",
15
+ "yesterday 9:15am" => ":day 2011-03-09T00:00:00.000000-06:00", # FIXME
16
+ "10 days ago" => ":day 2011-02-28T00:00:00.000000-06:00",
17
+ "10 s ago" => ":sec 2011-03-10T15:10:27.000000-06:00",
18
+ "day before yesterday" => ":day 2011-03-08T00:00:00.000000-06:00",
19
+ "hr before tomorrow" => ":hour 2011-03-10T23:00:00.000000-06:00",
20
+ "3 days before today" => ":day 2011-03-07T00:00:00.000000-06:00",
21
+ "5 days after today" => ":day 2011-03-15T00:00:00.000000-05:00",
22
+ "5 days before now" => "nil 2011-03-05T15:10:37.981304-06:00",
23
+ "3 days before this minute" => ":min 2011-03-07T15:10:00.000000-06:00",
24
+ "5 days before yesterday" => ":day 2011-03-04T00:00:00.000000-06:00",
25
+ "2 days before 50 hours after tomorrow" => ":hour 2011-03-11T02:00:00.000000-06:00",
26
+ "2 centuries after today" => ":day 2211-01-21T00:00:00.000000-06:00",
27
+ "1pm" => ":hour 2011-03-10T13:00:00.000000-06:00",
28
+ "12:30pm" => ":min 2011-03-10T12:30:00.000000-06:00",
29
+ "9:20am tomorrow" => ":min 2011-03-11T09:20:00.000000-06:00",
30
+ "6am 3 days from yesterday" => ":hour 2011-03-12T06:00:00.000000-06:00",
31
+ "2001/01" => ":mon 2001-01-01T00:00:00.000000-06:00",
32
+ "2001-01" => ":mon 2001-01-01T00:00:00.000000-06:00",
33
+ # "01/2001" => ":mon 2001-01-01T00:00:00.000000-06:00",
34
+ "01/2001" => "#<Qreport::TimeParser::Error::Syntax: syntax error at position 2: \"01 |^| /2001\">",
35
+ "2001/02/03 12:23pm" => ":min 2001-02-03T12:23:00.000000-06:00",
36
+ "12/31 12:59pm" => ":min 2011-12-31T12:59:00.000000-06:00",
37
+ "12/31 last year" => ":day 2010-12-31T00:00:00.000000-06:00",
38
+ "12:59:59pm 12/31 next year" => ":sec 2012-12-31T12:59:59.000000-06:00",
39
+ "1:23:45pm 1/2 in 2 years" => ":sec 2013-01-01T13:23:45.000000-06:00",
40
+ "2011-03-10T15:10:37-06:00" => "nil 2011-03-10T15:10:37.000000-06:00",
41
+ "2011-03-10T15:10:37.981304-06:00" => "nil 2011-03-10T15:10:37.981304-06:00",
42
+ "2011-03-10T15:10:37-06:00 plus 10 sec" => ":sec 2011-03-10T15:10:47.000000-06:00",
43
+ "2011-03-10T15:10:37.981304-06:00 - 2 weeks" => "nil 2011-02-24T15:10:37.981304-06:00",
44
+ "now minus 2.5 weeks" => "nil 2011-03-10T15:10:35.481304-06:00",
45
+ "t - 10 sec" => "nil 2011-03-10T15:10:27.981304-06:00",
46
+ "123.45 sec ago" => "nil 2011-03-10T15:08:34.531303-06:00",
47
+ "year 2010" => ":year 2010-01-01T00:00:00.000000-06:00",
48
+ "between 12:45pm and 1:15pm" => ":min 2011-03-10T12:45:00.000000-06:00 ... :min 2011-03-10T13:15:00.000000-06:00",
49
+ "before 1:23pm tomorrow" => ":min 2011-03-11T13:22:00.000000-06:00",
50
+ "this minute" => ":min 2011-03-10T15:10:00.000000-06:00",
51
+ "last hour" => ":hour 2011-03-10T14:00:00.000000-06:00",
52
+ "previous hour" => ":hour 2011-03-10T14:00:00.000000-06:00",
53
+ "last day" => [ ":day 2011-03-09T00:00:00.000000-06:00", ":day 2011-03-09T00:00:00.000000-06:00 ... :day 2011-03-10T00:00:00.000000-06:00" ],
54
+ "previous day" => ":day 2011-03-09T00:00:00.000000-06:00",
55
+ " 2001-01 + 1234 ajsdkfsd hours" => "#<Qreport::TimeParser::Error::Syntax: syntax error at position 17: \" 2001-01 + 1234 |^| ajsdkfsd hours\">",
56
+ "15 sec" => "#<Qreport::TimeParser::Error: Qreport::TimeParser::Error>",
57
+ "12 minutes" => "#<Qreport::TimeParser::Error: Qreport::TimeParser::Error>",
58
+ }
59
+ examples[:now] = now
60
+ examples
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,117 @@
1
+ require 'qreport/time_parser'
2
+ require 'qreport/time_parser/time_unit'
3
+ require 'rational'
4
+
5
+ module Qreport
6
+ class TimeParser
7
+ class TimeInterval
8
+ include TimeUnit
9
+ include Comparable
10
+ attr_reader :amount
11
+
12
+ def initialize amount, unit
13
+ @amount, @unit = amount, unit
14
+ normalize!
15
+ end
16
+
17
+ def to_unit! unit
18
+ @amount *= unit_multiplier(unit).to_r / unit_multipler
19
+ @unit = unit
20
+ normalize!
21
+ self
22
+ end
23
+
24
+ def + x
25
+ case x
26
+ when Numeric
27
+ new(@amount + x, @unit)
28
+ when TimeInterval
29
+ new(@amount + x.to_sec, :sec)
30
+ else
31
+ raise TypeError, x.class.to_s
32
+ end
33
+ end
34
+
35
+ def - x
36
+ case x
37
+ when Numeric
38
+ new(@amount - x, @unit)
39
+ when TimeInterval
40
+ new(@amount - x.to_sec, :sec)
41
+ else
42
+ raise TypeError, x.class.to_s
43
+ end
44
+ end
45
+
46
+ def * x
47
+ case x
48
+ when Numeric
49
+ new(@amount * x, @unit)
50
+ else
51
+ raise TypeError, x.class.to_s
52
+ end
53
+ end
54
+
55
+ def / x
56
+ case x
57
+ when Numeric
58
+ new(@amount / x, @unit)
59
+ when TimeInterval
60
+ to_sec / x.to_sec
61
+ else
62
+ raise TypeError, x.class.to_s
63
+ end
64
+ end
65
+
66
+ def normalize!
67
+ @unit = nil if Float === @amount
68
+
69
+ super
70
+
71
+ case @amount
72
+ when String, Symbol
73
+ @amount =
74
+ case @amount.to_s
75
+ when /\A(?:this)\b/i
76
+ 0
77
+ when /\A(?:last|previous)\b/i
78
+ -1
79
+ when /\A(?:next|after)\b/i
80
+ 1
81
+ else
82
+ raise ArgumentError, amount.inspect
83
+ end
84
+ end
85
+
86
+ self
87
+ end
88
+
89
+ def to_int; to_sec; end # see ruby/time.c
90
+
91
+ def to_sec
92
+ @to_sec ||=
93
+ @amount * unit_multiplier
94
+ end
95
+
96
+ def <=> x
97
+ case x
98
+ when TimeInterval
99
+ to_sec <=> x.to_sec
100
+ when Numeric
101
+ to_sec <=> x
102
+ else
103
+ raise TypeError, x.inspect
104
+ end
105
+ end
106
+
107
+ def to_s
108
+ "#{@amount.inspect} #{@unit.inspect}"
109
+ end
110
+
111
+ def inspect
112
+ "#<#{self.class} #{to_s}>"
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,46 @@
1
+ require 'qreport/time_parser'
2
+ require 'qreport/time_parser/time_unit'
3
+
4
+ module Qreport
5
+ class TimeParser
6
+ class TimeRange
7
+ include Comparable
8
+ attr_reader :a, :b
9
+
10
+ def initialize a, b
11
+ @a = a
12
+ @b = b
13
+ end
14
+
15
+ def inspect
16
+ "#<#{self.class} #{to_s}>"
17
+ end
18
+
19
+ def min
20
+ a <= b ? a : b
21
+ end
22
+
23
+ def max
24
+ a <= b ? b : a
25
+ end
26
+
27
+ def <=> x
28
+ min <=> x.min
29
+ end
30
+
31
+ def to_s
32
+ "#{a} ... #{b}"
33
+ end
34
+
35
+ def to_TimeRange; self; end
36
+
37
+ def to_range
38
+ if a <= b
39
+ (a.to_time ... b.to_time)
40
+ else
41
+ (b.to_time ... a.to_time)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,77 @@
1
+ require 'qreport/time_parser'
2
+ require 'qreport/time_parser/time_unit'
3
+
4
+ module Qreport
5
+ class TimeParser
6
+ class TimeRelative
7
+ include TimeUnit
8
+ SLOTS = [ :year, :mon, :day, :hour, :min, :sec, :zone ].freeze
9
+ attr_accessor *SLOTS
10
+
11
+ def unit
12
+ case
13
+ when Float === @sec
14
+ nil
15
+ when @sec
16
+ :sec
17
+ when @min
18
+ :min
19
+ when @hour
20
+ :hour
21
+ when @day
22
+ :day
23
+ when @mon
24
+ :mon
25
+ when @year
26
+ :year
27
+ else
28
+ nil
29
+ end
30
+ end
31
+
32
+ def merge! x
33
+ unless self.class === x
34
+ x = x.to_time if x.respond_to?(:to_time)
35
+ end
36
+ @year ||= x.year
37
+ @mon ||= x.mon
38
+ @day ||= x.day
39
+ @hour ||= x.hour
40
+ @min ||= x.min
41
+ @sec ||= x.sec
42
+ @zone ||= x.zone
43
+ self
44
+ end
45
+
46
+ def to_s
47
+ str = ''
48
+
49
+ [ [ '', @year, 4 ],
50
+ [ '-', @mon, 2 ],
51
+ [ '-', @day, 2 ],
52
+ [ 'T', @hour, 2 ],
53
+ [ ':', @min, 2 ],
54
+ [ ':', @sec, 2 ],
55
+ [ '-', @zone, ],
56
+ ].each do | sep, val, size |
57
+ str << sep << (size ? (val ? "%0#{size}d" % val : '?' * size) : val).to_s
58
+ end
59
+
60
+ str
61
+ end
62
+
63
+ def inspect
64
+ "#<#{self.class} #{to_s}>"
65
+ end
66
+
67
+ def from_time! t
68
+ merge! t
69
+ end
70
+
71
+ def to_time
72
+ Time.send(@zone == 'UTC' ? :utc : :local,
73
+ @year || 0, @mon || 1, @day || 1, @hour || 0, @min || 0, @sec || 0)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,110 @@
1
+ require 'qreport/time_parser'
2
+
3
+ module Qreport
4
+ class TimeParser
5
+ module TimeUnit
6
+ include Comparable
7
+ attr_reader :unit
8
+
9
+ def new *args
10
+ self.class.new *args
11
+ end
12
+
13
+ def normalize!
14
+ case @unit
15
+ when nil
16
+ when Symbol
17
+ @unit = @@unit_alias[@unit] || @unit
18
+ when String
19
+ @unit = @unit.downcase.to_sym
20
+ @unit = @@unit_alias[@unit] || @unit
21
+ when TimeInterval, TimeWithUnit
22
+ @unit = @unit.unit
23
+ else
24
+ raise ArgumentError, @unit.inspect
25
+ end
26
+ self
27
+ end
28
+
29
+ @@unit_alias = {
30
+ :second => :sec,
31
+ :s => :sec,
32
+ :minute => :min,
33
+ :min => :min,
34
+ :m => :min,
35
+ :hr => :hour,
36
+ :h => :hour,
37
+ :d => :day,
38
+ :w => :week,
39
+ :mo => :mon,
40
+ :month => :mon,
41
+ :mth => :mon,
42
+ :yr => :year,
43
+ :y => :year,
44
+ }
45
+
46
+ # in seconds
47
+ @@unit_interval = {
48
+ nil => 1,
49
+ :sec => 1,
50
+ :min => 60,
51
+ :hour => 60 * 60,
52
+ :day => 60 * 60 * 24, # + 1 leap second
53
+ :week => 60 * 60 * 24 * 7,
54
+ :mon => (60 * 60 * 24) * 31,
55
+ :year => (60 * 60 * 24) * 365, # + 1 leap day
56
+ :decade => (60 * 60 * 24) * 3650,
57
+ :century => (60 * 60 * 24) * 36500,
58
+ :millenium => (60 * 60 * 24) * 365000,
59
+ }
60
+
61
+ def self.pluralize str
62
+ str = str.to_s
63
+ case str
64
+ when "day"
65
+ "days"
66
+ when /y\Z/
67
+ str.sub(/y\Z/, 'ies')
68
+ else
69
+ str + "s"
70
+ end
71
+ end
72
+
73
+ (@@unit_alias.to_a.flatten + @@unit_interval.keys).each do | x |
74
+ next unless x
75
+ next if x.size == 1
76
+ x = x.to_sym
77
+ @@unit_alias[pluralize(x).to_sym] ||= @@unit_alias[x] || x
78
+ end
79
+
80
+ def unit_multiplier unit = @unit
81
+ @@unit_interval[unit] || 1
82
+ end
83
+
84
+ UNIT_REGEXP =
85
+ (@@unit_interval.keys +
86
+ @@unit_alias.keys +
87
+ @@unit_alias.values).
88
+ uniq.
89
+ map(&:to_s).
90
+ reject(&:empty?).
91
+ sort_by(&:size) * '|'
92
+ # $stderr.puts "UNIT_REGEXP = #{UNIT_REGEXP}"
93
+
94
+ def unit_interval
95
+ @unit_interval ||=
96
+ TimeInterval.new(1, @unit)
97
+ end
98
+
99
+ def <=> x
100
+ case x
101
+ when TimeInterval
102
+ unit_interval <=> x.unit_interval
103
+ else
104
+ unit_interval <=> x
105
+ end
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,155 @@
1
+ require 'qreport/time_parser'
2
+ require 'qreport/time_parser/time_unit'
3
+
4
+ module Qreport
5
+ class TimeParser
6
+ class TimeWithUnit
7
+ include TimeUnit
8
+ include Comparable
9
+ attr_reader :time
10
+
11
+ def initialize time, unit
12
+ @time, @unit = time, unit
13
+ normalize!
14
+ end
15
+
16
+ def interval(amount, unit = nil)
17
+ TimeInterval.new(amount, unit || @unit)
18
+ end
19
+
20
+ def + amount
21
+ # debugger
22
+ amount = interval(amount) unless TimeInterval === amount
23
+ raise TypeError, amount.to_s unless TimeInterval === amount
24
+ new(@time + amount.to_sec,
25
+ [ unit_interval, amount.unit_interval ] )
26
+ end
27
+
28
+ def - x
29
+ x = interval(x) if Numeric === x
30
+ case x
31
+ when TimeInterval
32
+ new(@time - x.to_sec, unit_interval)
33
+ when TimeWithUnit, ::Time
34
+ interval(@time.to_f - x.to_f, unit_interval)
35
+ else
36
+ raise TypeError, x.inspect
37
+ end
38
+ end
39
+
40
+ def to_time
41
+ @time
42
+ end
43
+
44
+ def to_f
45
+ @time.to_f
46
+ end
47
+
48
+ def <=> x
49
+ case x
50
+ when TimeWithUnit
51
+ @time <=> x.to_time
52
+ else
53
+ @time <=> x
54
+ end
55
+ end
56
+
57
+ def normalize!
58
+ case @unit
59
+ when Array
60
+ @unit = @unit.reduce{|a, b| a < b ? a : b}
61
+ end
62
+
63
+ super
64
+
65
+ case @time
66
+ when nil
67
+ return
68
+ when ::Time
69
+ when String
70
+ @time = ::Time.parse(@time)
71
+ when Numeric
72
+ @time = ::Time.utc(@time)
73
+ when ::Date
74
+ @time = @time.to_time
75
+ when TimeWithUnit
76
+ @time = @time.to_time
77
+ else
78
+ raise TypeError, @time.inspect
79
+ end
80
+
81
+ # debugger
82
+ args =
83
+ case @unit
84
+ when nil
85
+ return self
86
+ when :decade
87
+ [ @time.year % 10 * 10, 1, 1, 0, 0, 0 ]
88
+ when :century
89
+ [ @time.year % 100 / 100, 1, 1, 0, 0, 0 ]
90
+ when :millenium
91
+ [ @time.year % 1000 / 1000, 1, 1, 0, 0, 0 ]
92
+ else
93
+ @@unit_args[@unit] or
94
+ raise ArgumentError, @unit.inspect
95
+ end
96
+ if args.any? { | x | Symbol === x }
97
+ args = args.map do | x |
98
+ x = @time.send(x) if Symbol === x
99
+ x
100
+ end
101
+ end
102
+ # $stderr.puts " @time #{inspect} => "
103
+ # $stderr.puts " args = #{args.inspect}"
104
+ @time = @time.class.send(zone_method, *args)
105
+ # $stderr.puts " #{inspect}"
106
+ self
107
+ end
108
+
109
+ @@unit_args = {
110
+ :sec =>
111
+ [ :year, :mon, :day, :hour, :min, :sec ],
112
+ :min =>
113
+ [ :year, :mon, :day, :hour, :min, 0 ],
114
+ :hour =>
115
+ [ :year, :mon, :day, :hour, 0, 0 ],
116
+ :day =>
117
+ [ :year, :mon, :day, 0, 0, 0 ],
118
+ :week =>
119
+ [ :year, :mon, :day, 0, 0, 0 ],
120
+ :mon =>
121
+ [ :year, :mon, 1, 0, 0, 0 ],
122
+ :year =>
123
+ [ :year, 1, 1, 0, 0, 0 ],
124
+ }
125
+
126
+ UTC = 'UTC'.freeze
127
+
128
+ def zone_method
129
+ case @time.zone
130
+ when UTC
131
+ :utc
132
+ else
133
+ :local
134
+ end
135
+ end
136
+
137
+ def inspect
138
+ "#<#{self.class} #{to_s}>"
139
+ end
140
+
141
+ def to_s
142
+ "#{@unit.inspect} #{@time && @time.iso8601(6)}"
143
+ end
144
+
145
+ def to_TimeRange
146
+ TimeRange.new(self, self + unit_interval)
147
+ end
148
+
149
+ def to_range
150
+ to_TimeRange.to_range
151
+ end
152
+ end
153
+ end
154
+ end
155
+