ta_by_star 4.0.0
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/CHANGELOG.md +59 -0
- data/Gemfile +18 -0
- data/MIT-LICENSE +20 -0
- data/README.md +616 -0
- data/Rakefile +18 -0
- data/UPGRADING +4 -0
- data/by_star.gemspec +34 -0
- data/cleaner.rb +25 -0
- data/lib/by_star/base.rb +76 -0
- data/lib/by_star/between.rb +190 -0
- data/lib/by_star/directional.rb +35 -0
- data/lib/by_star/kernel/date.rb +41 -0
- data/lib/by_star/kernel/in_time_zone.rb +20 -0
- data/lib/by_star/kernel/time.rb +41 -0
- data/lib/by_star/normalization.rb +156 -0
- data/lib/by_star/orm/active_record/by_star.rb +75 -0
- data/lib/by_star/orm/mongoid/by_star.rb +90 -0
- data/lib/by_star/orm/mongoid/reorder.rb +23 -0
- data/lib/by_star/version.rb +3 -0
- data/lib/by_star.rb +18 -0
- data/spec/database.yml +15 -0
- data/spec/fixtures/active_record/models.rb +12 -0
- data/spec/fixtures/active_record/schema.rb +19 -0
- data/spec/fixtures/mongoid/models.rb +31 -0
- data/spec/fixtures/shared/seeds.rb +36 -0
- data/spec/gemfiles/Gemfile.rails +5 -0
- data/spec/gemfiles/Gemfile.rails32 +7 -0
- data/spec/gemfiles/Gemfile.rails40 +7 -0
- data/spec/gemfiles/Gemfile.rails41 +7 -0
- data/spec/gemfiles/Gemfile.rails42 +7 -0
- data/spec/gemfiles/Gemfile.rails50 +7 -0
- data/spec/gemfiles/Gemfile.rails51 +7 -0
- data/spec/gemfiles/Gemfile.rails52 +7 -0
- data/spec/gemfiles/Gemfile.rails60 +7 -0
- data/spec/gemfiles/Gemfile.rails61 +7 -0
- data/spec/integration/active_record/active_record_spec.rb +41 -0
- data/spec/integration/mongoid/mongoid_spec.rb +39 -0
- data/spec/integration/shared/at_time.rb +53 -0
- data/spec/integration/shared/between_dates.rb +99 -0
- data/spec/integration/shared/between_times.rb +99 -0
- data/spec/integration/shared/by_calendar_month.rb +55 -0
- data/spec/integration/shared/by_cweek.rb +54 -0
- data/spec/integration/shared/by_day.rb +120 -0
- data/spec/integration/shared/by_direction.rb +126 -0
- data/spec/integration/shared/by_fortnight.rb +48 -0
- data/spec/integration/shared/by_month.rb +50 -0
- data/spec/integration/shared/by_quarter.rb +49 -0
- data/spec/integration/shared/by_week.rb +54 -0
- data/spec/integration/shared/by_weekend.rb +49 -0
- data/spec/integration/shared/by_year.rb +48 -0
- data/spec/integration/shared/index_scope_parameter.rb +111 -0
- data/spec/integration/shared/offset_parameter.rb +32 -0
- data/spec/integration/shared/order_parameter.rb +36 -0
- data/spec/integration/shared/relative.rb +174 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/unit/kernel_date_spec.rb +113 -0
- data/spec/unit/kernel_time_spec.rb +57 -0
- data/spec/unit/normalization_spec.rb +384 -0
- data/tmp/.gitignore +1 -0
- metadata +298 -0
data/lib/by_star/base.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module ByStar
|
2
|
+
|
3
|
+
module Base
|
4
|
+
|
5
|
+
include ByStar::Between
|
6
|
+
include ByStar::Directional
|
7
|
+
|
8
|
+
def by_star_field(*args)
|
9
|
+
options = args.extract_options!
|
10
|
+
@by_star_start_field ||= args[0]
|
11
|
+
@by_star_end_field ||= args[1]
|
12
|
+
@by_star_offset ||= options[:offset]
|
13
|
+
@by_star_scope ||= options[:scope]
|
14
|
+
@by_star_index_scope ||= options[:index_scope]
|
15
|
+
@by_star_field_type ||= options[:field_type]
|
16
|
+
end
|
17
|
+
|
18
|
+
def by_star_offset(options = {})
|
19
|
+
(options[:offset] || @by_star_offset || 0).seconds
|
20
|
+
end
|
21
|
+
|
22
|
+
def by_star_start_field(options={})
|
23
|
+
field = options[:field] ||
|
24
|
+
options[:start_field] ||
|
25
|
+
@by_star_start_field ||
|
26
|
+
by_star_default_field
|
27
|
+
field.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def by_star_end_field(options={})
|
31
|
+
field = options[:field] ||
|
32
|
+
options[:end_field] ||
|
33
|
+
@by_star_end_field ||
|
34
|
+
by_star_start_field
|
35
|
+
field.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def by_star_field_type(options={})
|
39
|
+
field = options[:field_type] ||
|
40
|
+
@by_star_field_type
|
41
|
+
field.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
# Wrapper function which extracts time and options for each by_star query.
|
47
|
+
# Note the following syntax examples are valid:
|
48
|
+
#
|
49
|
+
# Post.by_month # defaults to current time
|
50
|
+
# Post.by_month(2, year: 2004) # February, 2004
|
51
|
+
# Post.by_month(Time.now)
|
52
|
+
# Post.by_month(Time.now, field: "published_at")
|
53
|
+
# Post.by_month(field: "published_at")
|
54
|
+
#
|
55
|
+
def with_by_star_options(*args, &block)
|
56
|
+
options = args.extract_options!.symbolize_keys!
|
57
|
+
time = args.first || Time.zone.now
|
58
|
+
block.call(time, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
def by_star_eval_index_scope(start_time, end_time, options)
|
62
|
+
value = options[:index_scope] || @by_star_index_scope
|
63
|
+
value = value.call(start_time, end_time, options) if value.is_a?(Proc)
|
64
|
+
case value
|
65
|
+
when nil, false then nil
|
66
|
+
when Time, DateTime, Date then value.in_time_zone
|
67
|
+
when ActiveSupport::Duration then start_time - value
|
68
|
+
when Numeric then start_time - value.seconds
|
69
|
+
when :beginning_of_day
|
70
|
+
offset = options[:offset] || 0
|
71
|
+
(start_time - offset).beginning_of_day + offset
|
72
|
+
else raise 'ByStar :index_scope option value is not a supported type.'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module ByStar
|
2
|
+
|
3
|
+
module Between
|
4
|
+
|
5
|
+
def between_times(*args)
|
6
|
+
options = args.extract_options!.symbolize_keys!
|
7
|
+
|
8
|
+
start_time, end_time = ByStar::Normalization.extract_range(args)
|
9
|
+
offset = options[:offset] || 0
|
10
|
+
field_type = by_star_field_type(options)
|
11
|
+
|
12
|
+
if start_time.is_a?(Date)
|
13
|
+
start_time = field_type == 'date' ?
|
14
|
+
start_time :
|
15
|
+
ByStar::Normalization.apply_offset_start(start_time.in_time_zone, offset)
|
16
|
+
elsif start_time
|
17
|
+
start_time += offset.seconds
|
18
|
+
end
|
19
|
+
|
20
|
+
if end_time.is_a?(Date)
|
21
|
+
end_time = field_type == 'date' ?
|
22
|
+
end_time :
|
23
|
+
ByStar::Normalization.apply_offset_end(end_time.in_time_zone, offset)
|
24
|
+
elsif end_time
|
25
|
+
end_time += offset.seconds
|
26
|
+
end
|
27
|
+
|
28
|
+
start_field = by_star_start_field(options)
|
29
|
+
end_field = by_star_end_field(options)
|
30
|
+
|
31
|
+
scope = self
|
32
|
+
scope = if !start_time && !end_time
|
33
|
+
scope # do nothing
|
34
|
+
elsif !end_time
|
35
|
+
by_star_after_query(scope, start_field, start_time)
|
36
|
+
elsif !start_time
|
37
|
+
by_star_before_query(scope, start_field, end_time)
|
38
|
+
elsif start_field == end_field
|
39
|
+
by_star_point_query(scope, start_field, start_time, end_time)
|
40
|
+
elsif options[:strict]
|
41
|
+
by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
|
42
|
+
else
|
43
|
+
by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
scope = by_star_order(scope, options[:order]) if options[:order]
|
47
|
+
scope
|
48
|
+
end
|
49
|
+
|
50
|
+
def between_dates(*args)
|
51
|
+
options = args.extract_options!
|
52
|
+
start_date, end_date = ByStar::Normalization.extract_range(args)
|
53
|
+
start_date = ByStar::Normalization.date(start_date)
|
54
|
+
end_date = ByStar::Normalization.date(end_date)
|
55
|
+
between_times(start_date, end_date, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def at_time(*args)
|
59
|
+
with_by_star_options(*args) do |time, options|
|
60
|
+
start_field = by_star_start_field(options)
|
61
|
+
end_field = by_star_end_field(options)
|
62
|
+
|
63
|
+
scope = self
|
64
|
+
scope = if start_field == end_field
|
65
|
+
by_star_point_overlap_query(scope, start_field, time)
|
66
|
+
else
|
67
|
+
by_star_span_overlap_query(scope, start_field, end_field, time, options)
|
68
|
+
end
|
69
|
+
scope = by_star_order(scope, options[:order]) if options[:order]
|
70
|
+
scope
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def by_day(*args)
|
75
|
+
with_by_star_options(*args) do |time, options|
|
76
|
+
date = ByStar::Normalization.date(time)
|
77
|
+
between_dates(date, date, options)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def by_week(*args)
|
82
|
+
with_by_star_options(*args) do |time, options|
|
83
|
+
date = ByStar::Normalization.week(time, options)
|
84
|
+
start_day = Array(options[:start_day])
|
85
|
+
between_dates(date.beginning_of_week(*start_day), date.end_of_week(*start_day), options)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def by_cweek(*args)
|
90
|
+
with_by_star_options(*args) do |time, options|
|
91
|
+
by_week(ByStar::Normalization.cweek(time, options), options)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def by_weekend(*args)
|
96
|
+
with_by_star_options(*args) do |time, options|
|
97
|
+
date = ByStar::Normalization.week(time, options)
|
98
|
+
between_dates(date.beginning_of_weekend, date.end_of_weekend, options)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def by_fortnight(*args)
|
103
|
+
with_by_star_options(*args) do |time, options|
|
104
|
+
date = ByStar::Normalization.fortnight(time, options)
|
105
|
+
between_dates(date.beginning_of_fortnight, date.end_of_fortnight, options)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def by_month(*args)
|
110
|
+
with_by_star_options(*args) do |time, options|
|
111
|
+
date = ByStar::Normalization.month(time, options)
|
112
|
+
between_dates(date.beginning_of_month, date.end_of_month, options)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def by_calendar_month(*args)
|
117
|
+
with_by_star_options(*args) do |time, options|
|
118
|
+
date = ByStar::Normalization.month(time, options)
|
119
|
+
start_day = Array(options[:start_day])
|
120
|
+
between_dates(date.beginning_of_calendar_month(*start_day), date.end_of_calendar_month(*start_day), options)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def by_quarter(*args)
|
125
|
+
with_by_star_options(*args) do |time, options|
|
126
|
+
date = ByStar::Normalization.quarter(time, options)
|
127
|
+
between_dates(date.beginning_of_quarter, date.end_of_quarter, options)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def by_year(*args)
|
132
|
+
with_by_star_options(*args) do |time, options|
|
133
|
+
date = ByStar::Normalization.year(time, options)
|
134
|
+
between_dates(date.beginning_of_year, date.to_date.end_of_year, options)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def today(options = {})
|
139
|
+
by_day(Date.current, options)
|
140
|
+
end
|
141
|
+
|
142
|
+
def yesterday(options = {})
|
143
|
+
by_day(Date.yesterday, options)
|
144
|
+
end
|
145
|
+
|
146
|
+
def tomorrow(options = {})
|
147
|
+
by_day(Date.tomorrow, options)
|
148
|
+
end
|
149
|
+
|
150
|
+
def past_day(options = {})
|
151
|
+
between_times(Time.current - 1.day, Time.current, options)
|
152
|
+
end
|
153
|
+
|
154
|
+
def past_week(options = {})
|
155
|
+
between_times(Time.current - 1.week, Time.current, options)
|
156
|
+
end
|
157
|
+
|
158
|
+
def past_fortnight(options = {})
|
159
|
+
between_times(Time.current - 2.weeks, Time.current, options)
|
160
|
+
end
|
161
|
+
|
162
|
+
def past_month(options = {})
|
163
|
+
between_times(Time.current - 1.month, Time.current, options)
|
164
|
+
end
|
165
|
+
|
166
|
+
def past_year(options = {})
|
167
|
+
between_times(Time.current - 1.year, Time.current, options)
|
168
|
+
end
|
169
|
+
|
170
|
+
def next_day(options = {})
|
171
|
+
between_times(Time.current, Time.current + 1.day, options)
|
172
|
+
end
|
173
|
+
|
174
|
+
def next_week(options = {})
|
175
|
+
between_times(Time.current, Time.current + 1.week, options)
|
176
|
+
end
|
177
|
+
|
178
|
+
def next_fortnight(options = {})
|
179
|
+
between_times(Time.current, Time.current + 2.weeks, options)
|
180
|
+
end
|
181
|
+
|
182
|
+
def next_month(options = {})
|
183
|
+
between_times(Time.current, Time.current + 1.month, options)
|
184
|
+
end
|
185
|
+
|
186
|
+
def next_year(options = {})
|
187
|
+
between_times(Time.current, Time.current + 1.year, options)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ByStar
|
2
|
+
|
3
|
+
module Directional
|
4
|
+
|
5
|
+
def before(*args)
|
6
|
+
with_by_star_options(*args) do |time, options|
|
7
|
+
field = by_star_start_field(options)
|
8
|
+
time = ByStar::Normalization.time(time)
|
9
|
+
by_star_before_query(self, field, time)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
alias_method :before_now, :before
|
13
|
+
|
14
|
+
def after(*args)
|
15
|
+
with_by_star_options(*args) do |time, options|
|
16
|
+
field = by_star_start_field(options)
|
17
|
+
time = ByStar::Normalization.time(time)
|
18
|
+
by_star_after_query(self, field, time)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
alias_method :after_now, :after
|
22
|
+
|
23
|
+
def oldest(*args)
|
24
|
+
with_by_star_options(*args) do |time, options|
|
25
|
+
oldest_query(options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def newest(*args)
|
30
|
+
with_by_star_options(*args) do |time, options|
|
31
|
+
newest_query(options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ByStar
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
|
5
|
+
module Date
|
6
|
+
|
7
|
+
# A "Weekend" is defined as beginning of Saturday to end of Sunday.
|
8
|
+
# The weekend for a given date will be the the next weekend if the day Mon-Thurs,
|
9
|
+
# otherwise the current weekend if the day is Fri-Sun.
|
10
|
+
def beginning_of_weekend
|
11
|
+
beginning_of_week(:monday).advance(days: 5)
|
12
|
+
end
|
13
|
+
|
14
|
+
def end_of_weekend
|
15
|
+
beginning_of_weekend + 1
|
16
|
+
end
|
17
|
+
|
18
|
+
# A "Fortnight" is defined as a two week period, with the first fortnight of the
|
19
|
+
# year beginning on 1st January.
|
20
|
+
def beginning_of_fortnight
|
21
|
+
beginning_of_year + 14 * ((self - beginning_of_year) / 14).to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def end_of_fortnight
|
25
|
+
beginning_of_fortnight + 13
|
26
|
+
end
|
27
|
+
|
28
|
+
# A "Calendar Month" is defined as a month as it appears on a calendar, including days form
|
29
|
+
# previous/following months which are part of the first/last weeks of the given month.
|
30
|
+
def beginning_of_calendar_month(*args)
|
31
|
+
beginning_of_month.beginning_of_week(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def end_of_calendar_month(*args)
|
35
|
+
end_of_month.end_of_week(*args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
::Date.__send__(:include, ByStar::Kernel::Date)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ByStar
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
|
5
|
+
module InTimeZone
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
if method_defined?(:to_time_in_current_zone) && !method_defined?(:in_time_zone)
|
10
|
+
alias_method :in_time_zone, :to_time_in_current_zone
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
::Date.__send__(:include, ByStar::Kernel::InTimeZone)
|
18
|
+
::Time.__send__(:include, ByStar::Kernel::InTimeZone)
|
19
|
+
::DateTime.__send__(:include, ByStar::Kernel::InTimeZone)
|
20
|
+
::ActiveSupport::TimeWithZone.__send__(:include, ByStar::Kernel::InTimeZone)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ByStar
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
|
5
|
+
module Time
|
6
|
+
|
7
|
+
# A "Weekend" is defined as beginning of Saturday to end of Sunday.
|
8
|
+
# The weekend for a given date will be the the next weekend if the day Mon-Thurs,
|
9
|
+
# otherwise the current weekend if the day is Fri-Sun.
|
10
|
+
def beginning_of_weekend
|
11
|
+
beginning_of_week(:monday).advance(days: 5)
|
12
|
+
end
|
13
|
+
|
14
|
+
def end_of_weekend
|
15
|
+
(beginning_of_weekend + 47.hours).end_of_hour
|
16
|
+
end
|
17
|
+
|
18
|
+
# A "Fortnight" is defined as a two week period, with the first fortnight of the
|
19
|
+
# year beginning on 1st January.
|
20
|
+
def beginning_of_fortnight
|
21
|
+
(beginning_of_year + 1.fortnight * ((self - beginning_of_year) / 1.fortnight).to_i).beginning_of_day
|
22
|
+
end
|
23
|
+
|
24
|
+
def end_of_fortnight
|
25
|
+
(beginning_of_fortnight + 13.days).end_of_day
|
26
|
+
end
|
27
|
+
|
28
|
+
# A "Calendar Month" is defined as a month as it appears on a calendar, including days form
|
29
|
+
# previous/following months which are part of the first/last weeks of the given month.
|
30
|
+
def beginning_of_calendar_month(*args)
|
31
|
+
beginning_of_month.beginning_of_week(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def end_of_calendar_month(*args)
|
35
|
+
end_of_month.end_of_week(*args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
::Time.__send__(:include, ByStar::Kernel::Time)
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module ByStar
|
2
|
+
|
3
|
+
class ParseError < StandardError; end
|
4
|
+
|
5
|
+
module Normalization
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def date(value)
|
10
|
+
value = parse_time(value) if value.is_a?(String)
|
11
|
+
value = value.try(:in_time_zone) unless value.is_a?(Date)
|
12
|
+
value.try(:to_date)
|
13
|
+
end
|
14
|
+
|
15
|
+
def time(value)
|
16
|
+
value = parse_time(value) if value.is_a?(String)
|
17
|
+
value.try(:in_time_zone)
|
18
|
+
end
|
19
|
+
|
20
|
+
def week(value, options={})
|
21
|
+
value = try_string_to_int(value)
|
22
|
+
case value
|
23
|
+
when Integer then week_integer(value, options)
|
24
|
+
else date(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def week_integer(value, options={})
|
29
|
+
raise ParseError, 'Week number must be between 0 and 52' unless value.in?(0..52)
|
30
|
+
time = Time.zone.local(options[:year] || Time.zone.now.year)
|
31
|
+
time.beginning_of_year + value.to_i.weeks
|
32
|
+
end
|
33
|
+
|
34
|
+
def cweek(value, options={})
|
35
|
+
_value = value
|
36
|
+
if _value.is_a?(Integer)
|
37
|
+
raise ParseError, 'cweek number must be between 1 and 53' unless value.in?(1..53)
|
38
|
+
_value -= 1
|
39
|
+
end
|
40
|
+
week(_value, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def fortnight(value, options={})
|
44
|
+
value = try_string_to_int(value)
|
45
|
+
case value
|
46
|
+
when Integer then fortnight_integer(value, options)
|
47
|
+
else date(value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def fortnight_integer(value, options={})
|
52
|
+
raise ParseError, 'Fortnight number must be between 0 and 26' unless value.in?(0..26)
|
53
|
+
time = Time.zone.local(options[:year] || Time.zone.now.year)
|
54
|
+
time + (value * 2).weeks
|
55
|
+
end
|
56
|
+
|
57
|
+
def quarter(value, options={})
|
58
|
+
value = try_string_to_int(value)
|
59
|
+
case value
|
60
|
+
when Integer then quarter_integer(value, options)
|
61
|
+
else date(value)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def quarter_integer(value, options={})
|
66
|
+
raise ParseError, 'Quarter number must be between 1 and 4' unless value.in?(1..4)
|
67
|
+
time = Time.zone.local(options[:year] || Time.zone.now.year)
|
68
|
+
time.beginning_of_year + ((value - 1) * 3).months
|
69
|
+
end
|
70
|
+
|
71
|
+
def month(value, options={})
|
72
|
+
value = try_string_to_int(value)
|
73
|
+
case value
|
74
|
+
when Integer, String then month_integer(value, options)
|
75
|
+
else date(value)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def month_integer(value, options={})
|
80
|
+
year = options[:year] || Time.zone.now.year
|
81
|
+
Time.zone.parse "#{year}-#{value}-01"
|
82
|
+
rescue
|
83
|
+
raise ParseError, 'Month must be a number between 1 and 12 or a month name'
|
84
|
+
end
|
85
|
+
|
86
|
+
def year(value, options={})
|
87
|
+
value = try_string_to_int(value)
|
88
|
+
case value
|
89
|
+
when Integer then year_integer(value)
|
90
|
+
else date(value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def year_integer(value)
|
95
|
+
Time.zone.local(extrapolate_year(value))
|
96
|
+
end
|
97
|
+
|
98
|
+
def extrapolate_year(value)
|
99
|
+
case value.to_i
|
100
|
+
when 0..69
|
101
|
+
2000 + value
|
102
|
+
when 70..99
|
103
|
+
1900 + value
|
104
|
+
else
|
105
|
+
value.to_i
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def try_string_to_int(value)
|
110
|
+
value.is_a?(String) ? Integer(value) : value
|
111
|
+
rescue
|
112
|
+
value
|
113
|
+
end
|
114
|
+
|
115
|
+
def time_in_units(seconds)
|
116
|
+
days = seconds / 1.day
|
117
|
+
time = Time.at(seconds).utc
|
118
|
+
{ days: days, hour: time.hour, min: time.min, sec: time.sec }
|
119
|
+
end
|
120
|
+
|
121
|
+
def apply_offset_start(time, offset)
|
122
|
+
units = time_in_units(offset)
|
123
|
+
time += units.delete(:days).days
|
124
|
+
time.change(units)
|
125
|
+
end
|
126
|
+
|
127
|
+
def apply_offset_end(time, offset)
|
128
|
+
units = time_in_units(offset)
|
129
|
+
time += units.delete(:days).days
|
130
|
+
(time + 1.day).change(units) - 1.second
|
131
|
+
end
|
132
|
+
|
133
|
+
def extract_range(args)
|
134
|
+
case args[0]
|
135
|
+
when Array, Range then [args[0].first, args[0].last]
|
136
|
+
else args[0..1]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def parse_time(value)
|
143
|
+
defined?(Chronic) ? parse_time_chronic(value) : parse_time_fallback(value)
|
144
|
+
end
|
145
|
+
|
146
|
+
def parse_time_chronic(value)
|
147
|
+
Chronic.time_class = Time.zone
|
148
|
+
Chronic.parse(value) || raise(ByStar::ParseError, "Chronic could not parse String #{value.inspect}")
|
149
|
+
end
|
150
|
+
|
151
|
+
def parse_time_fallback(value)
|
152
|
+
Time.zone.parse(value) || raise(ByStar::ParseError, "Cannot parse String #{value.inspect}")
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module ByStar
|
2
|
+
module ActiveRecord
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
include ::ByStar::Base
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def by_star_default_field
|
11
|
+
"#{self.table_name}.created_at"
|
12
|
+
end
|
13
|
+
|
14
|
+
def by_star_point_query(scope, field, start_time, end_time)
|
15
|
+
scope.where("#{field} >= ? AND #{field} <= ?", start_time, end_time)
|
16
|
+
end
|
17
|
+
|
18
|
+
def by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
|
19
|
+
scope.where("#{start_field} >= ? AND #{start_field} <= ? AND #{end_field} >= ? AND #{end_field} <= ?", start_time, end_time, start_time, end_time)
|
20
|
+
end
|
21
|
+
|
22
|
+
def by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
|
23
|
+
index_scope = by_star_eval_index_scope(start_time, end_time, options)
|
24
|
+
scope = scope.where("#{end_field} > ? AND #{start_field} < ?", start_time, end_time)
|
25
|
+
scope = scope.where("#{start_field} >= ?", index_scope) if index_scope
|
26
|
+
scope
|
27
|
+
end
|
28
|
+
|
29
|
+
def by_star_point_overlap_query(scope, field, time)
|
30
|
+
scope.where("#{field} = ?", time)
|
31
|
+
end
|
32
|
+
|
33
|
+
def by_star_span_overlap_query(scope, start_field, end_field, time, options)
|
34
|
+
index_scope = by_star_eval_index_scope(time, time, options)
|
35
|
+
scope = scope.where("#{end_field} > ? AND #{start_field} <= ?", time, time)
|
36
|
+
scope = scope.where("#{start_field} >= ?", index_scope) if index_scope
|
37
|
+
scope
|
38
|
+
end
|
39
|
+
|
40
|
+
def by_star_before_query(scope, field, time)
|
41
|
+
scope.where("#{field} <= ?", time)
|
42
|
+
end
|
43
|
+
|
44
|
+
def by_star_after_query(scope, field, time)
|
45
|
+
scope.where("#{field} >= ?", time)
|
46
|
+
end
|
47
|
+
|
48
|
+
def by_star_order(scope, order)
|
49
|
+
scope.order(order)
|
50
|
+
end
|
51
|
+
|
52
|
+
def oldest_query(options={})
|
53
|
+
field = by_star_start_field(options)
|
54
|
+
reorder("#{field} ASC").first
|
55
|
+
end
|
56
|
+
|
57
|
+
def newest_query(options={})
|
58
|
+
field = by_star_start_field(options)
|
59
|
+
reorder("#{field} DESC").first
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def previous(options={})
|
64
|
+
field = self.class.by_star_start_field(options)
|
65
|
+
value = self.send(field.split(".").last)
|
66
|
+
self.class.where("#{field} < ?", value).reorder("#{field} DESC").first
|
67
|
+
end
|
68
|
+
|
69
|
+
def next(options={})
|
70
|
+
field = self.class.by_star_start_field(options)
|
71
|
+
value = self.send(field.split(".").last)
|
72
|
+
self.class.where("#{field} > ?", value).reorder("#{field} ASC").first
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|