qreport_time_parser 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+