Skip to content

Commit 367632c

Browse files
committed
Add by_semester method, for the 6-month intervals of the year
1 parent 3b53809 commit 367632c

File tree

7 files changed

+141
-1
lines changed

7 files changed

+141
-1
lines changed

README.md

+32-1
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ See sections below for detailed argument usage of each:
7373
| `by_month` | Query by month. Allows integer arg, e.g. `11` for November. |
7474
| `by_calendar_month` | Month as it appears on a calendar; days form previous/following months which are part of the first/last weeks of the given month. |
7575
| `by_quarter` | 3-month intervals of the year. |
76+
| `by_semester` | 6-month intervals of the year. |
7677
| `by_year` | Query by year. Allows integer arg, e.g. `2017`. |
7778

7879
### Relative Scopes
@@ -544,11 +545,41 @@ Post.by_quarter(2, year: 2012)
544545
This will return all posts in the 2nd quarter of 2012.
545546

546547
```ruby
547-
Post.by_week(Time.local(2012,1,1))
548+
Post.by_quarter(Time.local(2012,1,1))
548549
```
549550

550551
This will return all posts from the first quarter of 2012.
551552

553+
### by_semester
554+
555+
Finds records by 6-month biannual period of year. Semester numbering starts at 1. The two semesters of the year begin on Jan 1 and Jul 1 respectively.
556+
557+
To find records from the current semester:
558+
559+
```ruby
560+
Post.by_semester
561+
```
562+
563+
To find records based on a semester, you can pass in a number (representing the semester number) or a time object:
564+
565+
```ruby
566+
Post.by_semester(1)
567+
```
568+
569+
This will return all posts in the 1st semester of the current year.
570+
571+
```ruby
572+
Post.by_semester(2, year: 2012)
573+
```
574+
575+
This will return all posts in the 2nd semester of 2012.
576+
577+
```ruby
578+
Post.by_semester(Time.local(2012,1,1))
579+
```
580+
581+
This will return all posts from the first semester of 2012.
582+
552583
## Version Support
553584

554585
ByStar is tested against the following versions:

lib/by_star/between.rb

+14
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,20 @@ def by_quarter(*args)
123123
end
124124
end
125125

126+
def by_semester(*args)
127+
with_by_star_options(*args) do |time, options|
128+
date = ByStar::Normalization.semester(time, options)
129+
130+
first_semester_month = date.month - (5 + date.month) % 6
131+
beginning_of_semester = date.beginning_of_month.change(month: first_semester_month)
132+
133+
last_semester_month = date.month + (12 - date.month) % 6
134+
end_of_semester = date.beginning_of_month.change(month: last_semester_month).end_of_month
135+
136+
between_dates(beginning_of_semester, end_of_semester, options)
137+
end
138+
end
139+
126140
def by_year(*args)
127141
with_by_star_options(*args) do |time, options|
128142
date = ByStar::Normalization.year(time, options)

lib/by_star/normalization.rb

+14
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ def quarter_integer(value, options={})
6868
time.beginning_of_year + ((value - 1) * 3).months
6969
end
7070

71+
def semester(value, options={})
72+
value = try_string_to_int(value)
73+
case value
74+
when Integer then semester_integer(value, options)
75+
else date(value)
76+
end
77+
end
78+
79+
def semester_integer(value, options={})
80+
raise ParseError, 'Semester number must be between 1 and 2' unless value.in?(1..2)
81+
time = Time.zone.local(options[:year] || Time.zone.now.year)
82+
time.beginning_of_year + ((value - 1) * 6).months
83+
end
84+
7185
def month(value, options={})
7286
value = try_string_to_int(value)
7387
case value

spec/integration/active_record/active_record_spec.rb

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
it_behaves_like 'by month'
3333
it_behaves_like 'by calendar month'
3434
it_behaves_like 'by quarter'
35+
it_behaves_like 'by semester'
3536
it_behaves_like 'by week'
3637
it_behaves_like 'by cweek'
3738
it_behaves_like 'by weekend'

spec/integration/mongoid/mongoid_spec.rb

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
it_behaves_like 'by month'
3232
it_behaves_like 'by calendar month'
3333
it_behaves_like 'by quarter'
34+
it_behaves_like 'by semester'
3435
it_behaves_like 'by week'
3536
it_behaves_like 'by cweek'
3637
it_behaves_like 'by weekend'
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require 'spec_helper'
2+
3+
shared_examples_for 'by semester' do
4+
5+
describe '#by_semester' do
6+
7+
context 'point-in-time' do
8+
subject { Post.by_semester(1) }
9+
it { expect(subject.count).to eql(12) }
10+
end
11+
12+
context 'timespan' do
13+
subject { Event.by_semester(1) }
14+
it { expect(subject.count).to eql(14) }
15+
end
16+
17+
context 'timespan strict' do
18+
subject { Event.by_semester(Date.parse('2014-02-01'), strict: true) }
19+
it { expect(subject.count).to eql(9) }
20+
end
21+
22+
context 'with :year option' do
23+
24+
context 'point-in-time' do
25+
subject { Post.by_semester(2, year: 2013) }
26+
it { expect(subject.count).to eql(10) }
27+
end
28+
29+
context 'timespan' do
30+
subject { Event.by_semester(2, year: 2013) }
31+
it { expect(subject.count).to eql(13) }
32+
end
33+
34+
context 'timespan strict' do
35+
subject { Event.by_semester(2, year: 2013, strict: true) }
36+
it { expect(subject.count).to eql(8) }
37+
end
38+
end
39+
40+
it 'should raise an error when given an invalid argument' do
41+
expect{ Post.by_semester(0) }.to raise_error(ByStar::ParseError, 'Semester number must be between 1 and 2')
42+
expect{ Post.by_semester(5) }.to raise_error(ByStar::ParseError, 'Semester number must be between 1 and 2')
43+
end
44+
45+
it 'should be able to use an alternative field' do
46+
expect(Event.by_semester(1, field: 'end_time').count).to eq(14)
47+
end
48+
end
49+
end

spec/unit/normalization_spec.rb

+30
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,36 @@
317317
specify { expect{ ByStar::Normalization.quarter(5) }.to raise_error(ByStar::ParseError, 'Quarter number must be between 1 and 4') }
318318
end
319319
end
320+
321+
describe '#semester' do
322+
subject { ByStar::Normalization.semester(input, options) }
323+
it_behaves_like 'date normalization from string'
324+
it_behaves_like 'date normalization from time value'
325+
326+
context 'Integer 1' do
327+
let(:input){ 1 }
328+
it { expect eq Date.parse('2014-01-01') }
329+
end
330+
331+
context 'Integer 2' do
332+
let(:input){ 2 }
333+
it { expect eq Date.parse('2014-07-01') }
334+
end
335+
336+
context 'with year option' do
337+
let(:options){ { year: 2011 } }
338+
339+
context 'Integer 2' do
340+
let(:input){ 2 }
341+
it { expect eq Date.parse('2011-07-01') }
342+
end
343+
end
344+
345+
context 'out of range' do
346+
specify { expect{ ByStar::Normalization.semester(0) }.to raise_error(ByStar::ParseError, 'Semester number must be between 1 and 2') }
347+
specify { expect{ ByStar::Normalization.semester(5) }.to raise_error(ByStar::ParseError, 'Semester number must be between 1 and 2') }
348+
end
349+
end
320350

321351
describe '#year' do
322352
subject { ByStar::Normalization.year(input, options) }

0 commit comments

Comments
 (0)