by_star 0.2.5
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.
- data/.gitignore +2 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +271 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/by_star.gemspec +57 -0
- data/lib/by_star.rb +316 -0
- data/rails/init.rb +2 -0
- data/spec/by_star_spec.rb +506 -0
- data/spec/fixtures/models.rb +70 -0
- data/spec/fixtures/schema.rb +24 -0
- data/spec/spec_helper.rb +27 -0
- metadata +80 -0
data/.gitignore
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
# by_*
|
2
|
+
|
3
|
+
|
4
|
+
by_* (byStar) is a plugin that allows you to find ActiveRecord objects given certain date objects. This was originally crafted for only finding objects within a given month, but now has extended out to much more. It now supports finding objects for:
|
5
|
+
|
6
|
+
* A given year
|
7
|
+
* A given month
|
8
|
+
* A given fortnight
|
9
|
+
* A given week
|
10
|
+
* A given weekend
|
11
|
+
* A given day
|
12
|
+
* The current weekend
|
13
|
+
* The current work week
|
14
|
+
* Between certain times
|
15
|
+
* As of a certain time
|
16
|
+
* Up to a certain time
|
17
|
+
|
18
|
+
|
19
|
+
It also allows you to do nested finds on the records returned which I personally think is the coolest feature of the whole plugin:
|
20
|
+
|
21
|
+
Post.by_month(1) do
|
22
|
+
{ :include => "tags", :conditions => ["tags.name = ?", 'ruby'] }
|
23
|
+
end
|
24
|
+
|
25
|
+
If you're not using the standard `created_at` field: don't worry! I've covered that scenario too.
|
26
|
+
|
27
|
+
|
28
|
+
## By Year (`by_year`)
|
29
|
+
|
30
|
+
To find records based on a year you can pass it a two or four digit number:
|
31
|
+
|
32
|
+
Post.by_year(09)
|
33
|
+
|
34
|
+
This will return all posts in 2009, whereas:
|
35
|
+
|
36
|
+
Post.by_year(99)
|
37
|
+
|
38
|
+
will return all the posts in the year 1999.
|
39
|
+
|
40
|
+
## By Month (`by_month`)
|
41
|
+
|
42
|
+
If you know the number of the month you want:
|
43
|
+
|
44
|
+
Post.by_month(1)
|
45
|
+
|
46
|
+
This will return all posts in the first month (January) of the current year.
|
47
|
+
|
48
|
+
If you like being verbose:
|
49
|
+
|
50
|
+
Post.by_month("January")
|
51
|
+
|
52
|
+
This will return all posts created in January of the current year.
|
53
|
+
|
54
|
+
If you want to find all posts in January of last year just do
|
55
|
+
|
56
|
+
Post.by_month(1, :year => 2007)
|
57
|
+
|
58
|
+
or
|
59
|
+
|
60
|
+
Post.by_month("January", :year => 2007)
|
61
|
+
|
62
|
+
This will perform a find using the column you've specified.
|
63
|
+
|
64
|
+
If you have a Time object you can use it to find the posts:
|
65
|
+
|
66
|
+
Post.by_month(Time.local(2008, 11, 24))
|
67
|
+
|
68
|
+
This will find all the posts in November 2008.
|
69
|
+
|
70
|
+
## By Fortnight (`by_fortnight`)
|
71
|
+
|
72
|
+
Fortnight numbering starts at 0. The beginning of a fortnight is Monday, 12am.
|
73
|
+
|
74
|
+
To find records from the current fortnight:
|
75
|
+
|
76
|
+
Post.by_fortnight
|
77
|
+
|
78
|
+
To find records based on a fortnight, you can pass in a number (representing the fortnight number) or a time object:
|
79
|
+
|
80
|
+
Post.by_fortnight(18)
|
81
|
+
|
82
|
+
This will return all posts in the 18th fortnight of the current year.
|
83
|
+
|
84
|
+
Post.by_fortnight(18, :year => 2008)
|
85
|
+
|
86
|
+
This will return all posts in the 18th fortnight week of 2008.
|
87
|
+
|
88
|
+
Post.by_fortnight(Time.local(2008,1,1))
|
89
|
+
|
90
|
+
This will return all posts from the first fortnight of 2008.
|
91
|
+
|
92
|
+
## By Week (`by_week`)
|
93
|
+
|
94
|
+
Week numbering starts at 0. The beginning of a week is Monday, 12am.
|
95
|
+
|
96
|
+
To find records from the current week:
|
97
|
+
|
98
|
+
Post.by_week
|
99
|
+
|
100
|
+
To find records based on a week, you can pass in a number (representing the week number) or a time object:
|
101
|
+
|
102
|
+
Post.by_week(36)
|
103
|
+
|
104
|
+
This will return all posts in the 36th week of the current year.
|
105
|
+
|
106
|
+
Post.by_week(36, :year => 2008)
|
107
|
+
|
108
|
+
This will return all posts in the 36th week of 2008.
|
109
|
+
|
110
|
+
Post.by_week(Time.local(2008,1,1))
|
111
|
+
|
112
|
+
This will return all posts from the first week of 2008.
|
113
|
+
|
114
|
+
## By Weekend (`by_weekend`)
|
115
|
+
|
116
|
+
If the time passed in (or the time now is a weekend) it will return posts from 12am Saturday to 11:59:59PM Sunday. If the time is a week day, it will show all posts for the coming weekend.
|
117
|
+
|
118
|
+
Post.by_weekend(Time.now)
|
119
|
+
|
120
|
+
## By Day (`by_day` or `today`)
|
121
|
+
|
122
|
+
To find records for today:
|
123
|
+
|
124
|
+
Post.by_day
|
125
|
+
Post.today
|
126
|
+
|
127
|
+
To find records for a certain day:
|
128
|
+
|
129
|
+
Post.by_day(Time.local(2008, 1, 1))
|
130
|
+
|
131
|
+
You can also pass a string:
|
132
|
+
|
133
|
+
Post.by_day("next tuesday")
|
134
|
+
|
135
|
+
This will return all posts for the given day.
|
136
|
+
|
137
|
+
## Current Weekend (`by_current_weekend`)
|
138
|
+
|
139
|
+
If you are currently in a weekend (between 3pm Friday and 3am Monday) this will find all records starting at 3pm the previous Friday up until 3am, Monday.
|
140
|
+
|
141
|
+
If you are not in a weekend (between 3am Monday and 3pm Friday) this will find all records from the next Friday 3pm to the following Monday 3am.
|
142
|
+
|
143
|
+
## Current Work Week (`by_current_work_week`)
|
144
|
+
|
145
|
+
If you are currently in a work week (between 3am Monday and 3pm Friday) this will find all records in that range. If you are currently in a weekend (between 3pm Friday and 3am Monday) this will return all records in the upcoming work week.
|
146
|
+
|
147
|
+
|
148
|
+
## Tomorrow (`tomorrow`)
|
149
|
+
|
150
|
+
*This method has been shown to be shifty when passed a `Date` object, it is recommended that you pass it a `Time` object instead.*
|
151
|
+
|
152
|
+
To find all posts from the day after the current date:
|
153
|
+
|
154
|
+
Post.tomorrow
|
155
|
+
|
156
|
+
To find all posts after a given Date or Time object:
|
157
|
+
|
158
|
+
Post.tomorrow(Date.today + 2)
|
159
|
+
Post.tomorrow(Time.now + 5.days)
|
160
|
+
|
161
|
+
You can also pass a string:
|
162
|
+
|
163
|
+
Post.tomorrow("next tuesday")
|
164
|
+
|
165
|
+
## Yesterday (`yesterday`)
|
166
|
+
|
167
|
+
*This method has been shown to be shifty when passed a `Date` object, it is recommended that you pass it a `Time` object instead.*
|
168
|
+
|
169
|
+
To find all posts from the day before the current date:
|
170
|
+
|
171
|
+
Post.yesterday
|
172
|
+
|
173
|
+
To find all posts before a given Date or Time object:
|
174
|
+
|
175
|
+
Post.yesterday(Date.today + 2)
|
176
|
+
Post.yesterday(Time.now + 5.days)
|
177
|
+
|
178
|
+
You can also pass a string:
|
179
|
+
|
180
|
+
Post.yesterday("next tuesday")
|
181
|
+
|
182
|
+
## Past (`past`)
|
183
|
+
|
184
|
+
To find all posts before the current time:
|
185
|
+
|
186
|
+
Post.past
|
187
|
+
|
188
|
+
To find all posts before certain time or date:
|
189
|
+
|
190
|
+
Post.past(Date.today + 2)
|
191
|
+
Post.past(Time.now + 5.days)
|
192
|
+
|
193
|
+
You can also pass a string:
|
194
|
+
|
195
|
+
Post.past("next tuesday")
|
196
|
+
|
197
|
+
## Future (`future`)
|
198
|
+
|
199
|
+
To find all posts after the current time:
|
200
|
+
|
201
|
+
Post.future
|
202
|
+
|
203
|
+
To find all posts after certain time or date:
|
204
|
+
|
205
|
+
Post.future(Date.today + 2)
|
206
|
+
Post.future(Time.now + 5.days)
|
207
|
+
|
208
|
+
You can also pass a string:
|
209
|
+
|
210
|
+
Post.future("next tuesday")
|
211
|
+
|
212
|
+
## Between (`between`)
|
213
|
+
|
214
|
+
To find records between two times:
|
215
|
+
|
216
|
+
Post.between(time1, time2)
|
217
|
+
|
218
|
+
Also works with dates:
|
219
|
+
|
220
|
+
Post.between(date1, date2)
|
221
|
+
|
222
|
+
And with strings:
|
223
|
+
|
224
|
+
Post.between("last tuesday", "next wednesday")
|
225
|
+
|
226
|
+
## As of (`as_of_<dynamic>`)
|
227
|
+
|
228
|
+
To find records as of a certain date up until the current time:
|
229
|
+
|
230
|
+
Post.as_of_2_weeks_ago
|
231
|
+
|
232
|
+
This uses the Chronic "human mind reading" (read: it's really good at determining what time you mean using written English) library to work it out.
|
233
|
+
|
234
|
+
## Up to (`up_to_<dynamic>`)
|
235
|
+
|
236
|
+
To find records up to a certain time from the current time:
|
237
|
+
|
238
|
+
Post.up_to_6_weeks_from_now
|
239
|
+
|
240
|
+
## Not using created_at? No worries!
|
241
|
+
|
242
|
+
If your database uses something other than `created_at` for storing a timestamp, you can specify the field option like this:
|
243
|
+
|
244
|
+
Post.by_month("January", :field => :something_else)
|
245
|
+
|
246
|
+
All methods support this extra option.
|
247
|
+
|
248
|
+
## Scoping the find
|
249
|
+
|
250
|
+
All the `by_*` methods takes a block which will then scope the find based on the options passed into it. The supported options are the same options that are supported by `ActiveRecord::Base.find`:
|
251
|
+
|
252
|
+
Post.by_month(1) do
|
253
|
+
{ :include => "tags", :conditions => ["tags.name = ?", 'ruby'] }
|
254
|
+
end
|
255
|
+
## "Chronicable string"
|
256
|
+
|
257
|
+
This means a string that can be parsed with the Chronic gem.
|
258
|
+
|
259
|
+
## Collaborators
|
260
|
+
|
261
|
+
Unfortunately I forget who exactly prompted me to write the plugin, but I would like to thank #rubyonrails for their support and the following people:
|
262
|
+
|
263
|
+
* Mislav Marohnic
|
264
|
+
* August Lilleas (leethal)
|
265
|
+
* gte351s
|
266
|
+
* Thomase Sinclair (anathematic)
|
267
|
+
* The dude(s) & gal(s) who created Chronic
|
268
|
+
|
269
|
+
## Suggestions?
|
270
|
+
|
271
|
+
If you have suggestions, please contact me at radarlistener@gmail.com
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "by_star"
|
8
|
+
gem.summary = %Q{ActiveRecord extension for easier date scopes and time ranges}
|
9
|
+
gem.description = %Q{ActiveRecord extension for easier date scopes and time ranges}
|
10
|
+
gem.email = "radarlistener@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/radar/by_star"
|
12
|
+
gem.authors = ["Ryan Bigg", "Mislav Marohnić"]
|
13
|
+
gem.add_development_dependency "rspec"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
if File.exist?('VERSION')
|
40
|
+
version = File.read('VERSION')
|
41
|
+
else
|
42
|
+
version = ""
|
43
|
+
end
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "by_star #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
50
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.5
|
data/by_star.gemspec
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{by_star}
|
8
|
+
s.version = "0.2.5"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ryan Bigg", "Mislav Marohni\304\207"]
|
12
|
+
s.date = %q{2009-10-15}
|
13
|
+
s.description = %q{ActiveRecord extension for easier date scopes and time ranges}
|
14
|
+
s.email = %q{radarlistener@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.markdown"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"MIT-LICENSE",
|
21
|
+
"README.markdown",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"by_star.gemspec",
|
25
|
+
"lib/by_star.rb",
|
26
|
+
"rails/init.rb",
|
27
|
+
"spec/by_star_spec.rb",
|
28
|
+
"spec/fixtures/models.rb",
|
29
|
+
"spec/fixtures/schema.rb",
|
30
|
+
"spec/spec_helper.rb",
|
31
|
+
"tmp/.gitignore"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/radar/by_star}
|
34
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.3.5}
|
37
|
+
s.summary = %q{ActiveRecord extension for easier date scopes and time ranges}
|
38
|
+
s.test_files = [
|
39
|
+
"spec/by_star_spec.rb",
|
40
|
+
"spec/fixtures/models.rb",
|
41
|
+
"spec/fixtures/schema.rb",
|
42
|
+
"spec/spec_helper.rb"
|
43
|
+
]
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
53
|
+
end
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
56
|
+
end
|
57
|
+
end
|
data/lib/by_star.rb
ADDED
@@ -0,0 +1,316 @@
|
|
1
|
+
require 'chronic'
|
2
|
+
module ByStar
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Examples:
|
10
|
+
# by_year(2010)
|
11
|
+
# # 2-digit year:
|
12
|
+
# by_year(10)
|
13
|
+
# # Time or Date object:
|
14
|
+
# by_year(time)
|
15
|
+
# # String:
|
16
|
+
# by_year("2010")
|
17
|
+
def by_year(time=Time.zone.now.year, options={}, &block)
|
18
|
+
year = work_out_year(time)
|
19
|
+
|
20
|
+
start_time = Time.utc(year, 1, 1)
|
21
|
+
end_time = start_time.end_of_year
|
22
|
+
by_star(start_time, end_time, options, &block)
|
23
|
+
rescue ArgumentError
|
24
|
+
raise ParseError, "Invalid arguments detected, year may possibly be outside of valid range (1902-2039). This is no longer a problem on Ruby versions > 1.8.7, so we recommend you upgrade to at least 1.8.7."
|
25
|
+
end
|
26
|
+
|
27
|
+
# Examples:
|
28
|
+
# by_month(1)
|
29
|
+
# by_month("January")
|
30
|
+
# by_month("January", :year => 2008)
|
31
|
+
# by_month(time)
|
32
|
+
def by_month(time=Time.zone.now.month, options={}, &block)
|
33
|
+
time = Time.zone.now.month if time.nil?
|
34
|
+
year = options[:year] ||= Time.zone.now.year
|
35
|
+
# Work out what actual month is.
|
36
|
+
month = if time.is_a?(Numeric) && (1..12).include?(time)
|
37
|
+
time
|
38
|
+
elsif valid_time_or_date?(time)
|
39
|
+
year = time.year
|
40
|
+
time.month
|
41
|
+
elsif time.is_a?(String) && Date::MONTHNAMES.include?(time)
|
42
|
+
Date::MONTHNAMES.index(time)
|
43
|
+
else
|
44
|
+
raise ParseError, "Value is not an integer (between 1 and 12), time object or string (make sure you typed the name right)."
|
45
|
+
end
|
46
|
+
|
47
|
+
start_time = Time.utc(year, month, 1)
|
48
|
+
end_time = start_time.end_of_month
|
49
|
+
|
50
|
+
by_star(start_time, end_time, options, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Examples:
|
54
|
+
# # 18th fortnight of 2004
|
55
|
+
# Post.by_fortnight(18, :year => 2004)
|
56
|
+
def by_fortnight(time=Time.zone.now, options = {}, &block)
|
57
|
+
time = parse(time)
|
58
|
+
|
59
|
+
# If options[:year] is passed in, use that year regardless.
|
60
|
+
year = work_out_year(options[:year]) if options[:year]
|
61
|
+
# If the first argument is a date or time, ask it for the year
|
62
|
+
year ||= time.year unless time.is_a?(Numeric)
|
63
|
+
# If the first argument is a fixnum, assume this year.
|
64
|
+
year ||= Time.zone.now.year
|
65
|
+
|
66
|
+
# Dodgy!
|
67
|
+
# Surely there's a method in Rails to do this.
|
68
|
+
start_time = if valid_time_or_date?(time)
|
69
|
+
time.beginning_of_year + (time.strftime("%U").to_i).weeks
|
70
|
+
elsif time.is_a?(Numeric) && time <= 26
|
71
|
+
Time.utc(year, 1, 1) + ((time.to_i) * 2).weeks
|
72
|
+
else
|
73
|
+
raise ParseError, "by_fortnight takes only a Time or Date object, a Fixnum (less than or equal to 26) or a Chronicable string."
|
74
|
+
end
|
75
|
+
start_time = start_time.beginning_of_week
|
76
|
+
end_time = start_time + 2.weeks
|
77
|
+
by_star(start_time, end_time, options, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Examples:
|
81
|
+
# # 36th week
|
82
|
+
# Post.by_week(36)
|
83
|
+
# Post.by_week(36.54)
|
84
|
+
# Post.by_week(36, :year => 2004)
|
85
|
+
# Post.by_week(<Time object>)
|
86
|
+
# Post.by_week(<Date object>)
|
87
|
+
# Post.by_week("next tuesday")
|
88
|
+
def by_week(time=Time.zone.now, options = {}, &block)
|
89
|
+
time = parse(time)
|
90
|
+
|
91
|
+
# If options[:year] is passed in, use that year regardless.
|
92
|
+
year = work_out_year(options[:year]) if options[:year]
|
93
|
+
# If the first argument is a date or time, ask it for the year
|
94
|
+
year ||= time.year unless time.is_a?(Numeric)
|
95
|
+
# If the first argument is a fixnum, assume this year.
|
96
|
+
year ||= Time.now.year
|
97
|
+
|
98
|
+
# Dodgy!
|
99
|
+
# Surely there's a method in Rails to do this.
|
100
|
+
start_time = if valid_time_or_date?(time)
|
101
|
+
weeks = time.strftime("%U").to_i
|
102
|
+
time.beginning_of_year
|
103
|
+
elsif time.is_a?(Numeric) && time < 53
|
104
|
+
weeks = time.to_i
|
105
|
+
Time.utc(year, 1, 1)
|
106
|
+
else
|
107
|
+
raise ParseError, "by_week takes only a Time or Date object, a Fixnum (less than or equal to 53) or a Chronicable string."
|
108
|
+
end
|
109
|
+
start_time += weeks.weeks
|
110
|
+
end_time = start_time + 1.week
|
111
|
+
by_star(start_time, end_time, options, &block)
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
# Examples:
|
116
|
+
# Post.by_weekend
|
117
|
+
# Post.by_weekend(Time.now + 5.days)
|
118
|
+
# Post.by_weekend(Date.today + 5)
|
119
|
+
# Post.by_weekend("next tuesday")
|
120
|
+
def by_weekend(time=Time.zone.now, options = {}, &block)
|
121
|
+
time = parse(time)
|
122
|
+
start_time = time.beginning_of_weekend
|
123
|
+
by_star(start_time, (start_time + 1.day).end_of_day, options, &block)
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# Examples:
|
128
|
+
# Post.by_current_weekend
|
129
|
+
def by_current_weekend(options = {}, &block)
|
130
|
+
time = Time.zone.now
|
131
|
+
# Friday, 3pm
|
132
|
+
start_time = time.beginning_of_weekend
|
133
|
+
# Monday, 3am
|
134
|
+
end_time = time.end_of_weekend
|
135
|
+
by_star(start_time, end_time, options, &block)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Examples:
|
139
|
+
# Post.by_current_work_week
|
140
|
+
def by_current_work_week(options = {}, &block)
|
141
|
+
time = Time.zone.now
|
142
|
+
# Monday, 3am
|
143
|
+
time = time + 1.week if time.wday == 6 || time.wday == 0
|
144
|
+
start_time = time.beginning_of_week + 3.hours
|
145
|
+
# Friday, 3pm
|
146
|
+
end_time = time.beginning_of_weekend
|
147
|
+
by_star(start_time, end_time, options, &block)
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
# Examples:
|
152
|
+
# Post.by_day
|
153
|
+
# Post.by_day(Time.yesterday)
|
154
|
+
# Post.by_day("next tuesday")
|
155
|
+
def by_day(time = Time.zone.now, options = {}, &block)
|
156
|
+
time = parse(time)
|
157
|
+
by_star(time.beginning_of_day, time.end_of_day, options, &block)
|
158
|
+
end
|
159
|
+
alias_method :today, :by_day
|
160
|
+
|
161
|
+
# Examples:
|
162
|
+
# Post.yesterday
|
163
|
+
# # 2 days ago:
|
164
|
+
# Post.yesterday(Time.yesterday)
|
165
|
+
# # day before next tuesday
|
166
|
+
# Post.yesterday("next tuesday")
|
167
|
+
def yesterday(time = Time.zone.now, options = {}, &block)
|
168
|
+
time = parse(time)
|
169
|
+
by_day(time.advance(:days => -1), options, &block)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Examples:
|
173
|
+
# Post.tomorrow
|
174
|
+
# # 2 days from now:
|
175
|
+
# Post.tomorrow(Time.tomorrow)
|
176
|
+
# # day after next tuesday
|
177
|
+
# Post.tomorrow("next tuesday")
|
178
|
+
def tomorrow(time = Time.zone.now, options = {}, &block)
|
179
|
+
time = parse(time)
|
180
|
+
by_day(time.advance(:days => 1), options, &block)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Scopes to records older than current or given time
|
184
|
+
# Post.past
|
185
|
+
# Post.past()
|
186
|
+
def past(time = Time.zone.now, options = {}, &block)
|
187
|
+
time = parse(time)
|
188
|
+
by_direction("<", time, options, &block)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Scopes to records newer than current or given time
|
192
|
+
def future(time = Time.zone.now, options = {}, &block)
|
193
|
+
time = parse(time)
|
194
|
+
by_direction(">", time, options, &block)
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def by_direction(condition, time, options = {}, &block)
|
200
|
+
field = connection.quote_table_name(table_name)
|
201
|
+
field << "." << connection.quote_column_name(options[:field] || "created_at")
|
202
|
+
with_scope(:find => { :conditions => ["#{field} #{condition} ?", time.utc] }) do
|
203
|
+
if block_given?
|
204
|
+
with_scope(:find => block.call) do
|
205
|
+
find(:all)
|
206
|
+
end
|
207
|
+
else
|
208
|
+
find(:all)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# scopes results between start_time and end_time
|
214
|
+
def by_star(start_time, end_time, options = {}, &block)
|
215
|
+
start_time = parse(start_time)
|
216
|
+
end_time = parse(end_time)
|
217
|
+
|
218
|
+
raise ParseError, "End time is before start time, searching like this will return no results." if end_time < start_time
|
219
|
+
|
220
|
+
field = options[:field] || "created_at"
|
221
|
+
with_scope(:find => { :conditions => { field => start_time.utc..end_time.utc } }) do
|
222
|
+
if block_given?
|
223
|
+
with_scope(:find => block.call) do
|
224
|
+
find(:all)
|
225
|
+
end
|
226
|
+
else
|
227
|
+
find(:all)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
alias :between :by_star
|
233
|
+
public :between
|
234
|
+
|
235
|
+
# This will work for the next 30 years (written in 2009)
|
236
|
+
def work_out_year(value)
|
237
|
+
case value
|
238
|
+
when 0..39
|
239
|
+
2000 + value
|
240
|
+
when 40..99
|
241
|
+
1900 + value
|
242
|
+
when nil
|
243
|
+
Time.zone.now.year
|
244
|
+
else
|
245
|
+
# We may be passed something that's not a straight out integer
|
246
|
+
# These things include: BigDecimals, Floats and Strings.
|
247
|
+
value.to_i
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Checks if the object is a Time, Date or TimeWithZone object.
|
252
|
+
def valid_time_or_date?(value)
|
253
|
+
value.is_a?(Time) || value.is_a?(Date) || value.is_a?(ActiveSupport::TimeWithZone)
|
254
|
+
end
|
255
|
+
|
256
|
+
def parse(object)
|
257
|
+
object = case object.class.to_s
|
258
|
+
when "NilClass"
|
259
|
+
o = Time.zone.now
|
260
|
+
when "String"
|
261
|
+
o = object
|
262
|
+
Chronic.parse(object, :now => Time.zone.now)
|
263
|
+
when "Date"
|
264
|
+
object.to_time(:utc)
|
265
|
+
else
|
266
|
+
object
|
267
|
+
end
|
268
|
+
raise ParseError, "Chronic couldn't work out #{o.inspect}; please be more precise." if object.nil?
|
269
|
+
object
|
270
|
+
end
|
271
|
+
|
272
|
+
def method_missing(method, *args)
|
273
|
+
if method.to_s =~ /^(as_of|up_to)_(.+)$/
|
274
|
+
method = $1
|
275
|
+
expr = $2.humanize
|
276
|
+
unless time = parse(expr)
|
277
|
+
raise ParseError, "Chronic couldn't work out #{expr.inspect}; please be more precise."
|
278
|
+
end
|
279
|
+
|
280
|
+
reference = args.first || Time.now
|
281
|
+
|
282
|
+
if "as_of" == method
|
283
|
+
between(time, reference)
|
284
|
+
else
|
285
|
+
between(reference, time)
|
286
|
+
end
|
287
|
+
else
|
288
|
+
super
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
class ParseError < Exception; end
|
294
|
+
class MonthNotFound < Exception; end
|
295
|
+
end
|
296
|
+
|
297
|
+
class Time
|
298
|
+
def beginning_of_weekend
|
299
|
+
friday = case self.wday
|
300
|
+
when 0
|
301
|
+
self.end_of_week.beginning_of_day.advance(:days => -2)
|
302
|
+
when 5
|
303
|
+
self.beginning_of_day
|
304
|
+
else
|
305
|
+
self.beginning_of_week.advance(:days => 4)
|
306
|
+
end
|
307
|
+
# 3pm, Friday.
|
308
|
+
(friday + 15.hours)
|
309
|
+
end
|
310
|
+
|
311
|
+
def end_of_weekend
|
312
|
+
# 3am, Monday.
|
313
|
+
# LOL I CHEATED.
|
314
|
+
beginning_of_weekend + 3.days - 12.hours
|
315
|
+
end
|
316
|
+
end
|