Permalink
Browse files

* Unit-test specs for normalization

* Two-digit year now considers 70 to be 1970, and 69 to be 2069 (was previously 40 -> 1940)
  • Loading branch information...
1 parent a72b183 commit d59e451bcff7dc1883ee8e26585450f59c8bdca6 @johnnyshields johnnyshields committed Jan 11, 2014
Showing with 256 additions and 12 deletions.
  1. +2 −0 CHANGELOG.md
  2. +19 −12 lib/by_star/normalization.rb
  3. +235 −0 spec/unit/normalization_spec.rb
View
@@ -8,12 +8,14 @@
* Decouple gem from ActiveRecord, and put ActiveRecord and Mongoid specific methods in ORM modules.
* Consolidate all normalization/parsing functions into new Normalization module
* Remove meta-programming methods, e.g. `send("by_week_#{time_klass}")`
+* Support matching timespan-type objects with distinct start and end times (previously only point-in-time matching was supported)
* Make Chronic gem optional; use it only if user has included it externally
* `by_week` always returns a calendar week (i.e. beginning Monday or as specified by Rails setting), regardless of whether Date or Fixnum is given as a parameter.
* `by_week` and `by_calendar_month` now supports optional `:start_day` option (:monday, :tuesday, etc)
* Separate `by_calendar_month` into it's own class
* Rename `between` to `between_times` internally, as Mongoid already defines `between`. ActiveRecord has an alias of `between` to interface stays consistent.
* Add `:offset` option to all query methods, in order to offset the time the day begins/ends (for example supposing business cycle begins at 8:00 each day until 7:59:59 the next day)
* `by_weekend` can now take a fixnum (parsing logic is same as by_week)
+* Two-digit year now considers 70 to be 1970, and 69 to be 2069 (was previously 40 -> 1940)
* Add Time kernel extensions for fortnight and calendar_month
* Add Johnny Shields as a gem co-author
@@ -9,19 +9,25 @@ class << self
def time(value)
case value
when String then time_string(value)
- when Date, DateTime then value.to_time
+ when DateTime then value.to_time
+ when Date then value.to_time_in_current_zone
else value
end
end
def time_string(value)
- if defined?(Chronic)
- time = Chronic.parse(value)
- raise ByStar::ParseError, "Chronic could not parse #{value.inspect}" unless time
- time
- else
- Time.parse(value)
- end
+ defined?(Chronic) ? time_string_chronic(value) : time_string_fallback(value)
+ end
+
+ def time_string_chronic(value)
+ Chronic.time_class = Time.zone
+ time = Chronic.parse(value)
+ raise ByStar::ParseError, "Chronic could not parse #{value.inspect}" unless time
+ time
+ end
+
+ def time_string_fallback(value)
+ Time.zone.parse(value)
end
def week(value, options={})
@@ -68,6 +74,7 @@ def quarter_fixnum(value, options={})
end
def month(value, options={})
+ value = try_string_to_int(value)
case value
when Fixnum, String then month_fixnum(value, options)
else time(value)
@@ -76,7 +83,7 @@ def month(value, options={})
def month_fixnum(value, options={})
year = options[:year] || Time.zone.now.year
- Date.parse("#{year}-#{value}-01").to_time
+ Time.zone.parse "#{year}-#{value}-01"
rescue
raise ParseError, "Month must be a number between 1 and 12 or the full month name (e.g. 'January')"
end
@@ -90,14 +97,14 @@ def year(value, options={})
end
def year_fixnum(value)
- "#{extrapolate_year(value)}-01-01".to_time
+ Time.zone.local(extrapolate_year(value))
end
def extrapolate_year(value)
case value.to_i
- when 0..39
+ when 0..69
2000 + value
- when 40..99
+ when 70..99
1900 + value
else
value.to_i
@@ -0,0 +1,235 @@
+require 'spec_helper'
+
+describe ByStar::Normalization do
+
+ let(:options){ {} }
+
+ shared_examples_for 'time normalization from string' do
+
+ context 'date String' do
+ let(:input){ '2014-01-01' }
+ it { should eq Time.zone.parse('2014-01-01 12:00:00') }
+ end
+
+ context 'time String' do
+ let(:input){ '2014-01-01 15:00:00' }
+ it { should eq Time.zone.parse('2014-01-01 15:00:00') }
+ end
+ end
+
+ shared_examples_for 'time normalization from date/time' do
+ context 'Date' do
+ let(:input){ Date.parse('2014-01-01') }
+ it { should eq Time.zone.parse('2014-01-01 00:00:00') }
+ end
+
+ context 'DateTime' do
+ let(:input){ DateTime.parse('2014-01-01 15:00:00') }
+ it { should eq Time.zone.parse('2014-01-01 15:00:00') }
+ end
+
+ context 'Time' do
+ let(:input){ Time.zone.parse('2014-01-01 15:00:00') }
+ it { should eq Time.zone.parse('2014-01-01 15:00:00') }
+ end
+ end
+
+ describe '#time' do
+ subject { ByStar::Normalization.time(input) }
+ it_behaves_like 'time normalization from string'
+ it_behaves_like 'time normalization from date/time'
+ end
+
+ describe '#time_string_fallback' do
+
+ it 'should parse date String to Time at beginning of day' do
+ d = '2014-01-01'
+ subject.time_string_fallback(d).should eq Time.zone.parse('2014-01-01 00:00:00')
+ end
+
+ it 'should parse time String to Time' do
+ dt = '2014-01-01 15:00:00'
+ subject.time_string_fallback(dt).should eq Time.zone.parse('2014-01-01 15:00:00')
+ end
+ end
+
+ describe '#week' do
+ subject { ByStar::Normalization.week(input, options) }
+ it_behaves_like 'time normalization from string'
+ it_behaves_like 'time normalization from date/time'
+
+ context 'Fixnum 0' do
+ let(:input){ 0 }
+ it { should eq Time.zone.parse('2014-01-01 00:00:00') }
+ end
+
+ context 'Fixnum 20' do
+ let(:input){ 20 }
+ it { should eq Time.zone.parse('2014-05-21 00:00:00') }
+ end
+
+ context 'with year option' do
+ let(:options){ { :year => 2011 } }
+
+ context 'Fixnum 0' do
+ let(:input){ 0 }
+ it { should eq Time.zone.parse('2011-01-01 00:00:00') }
+ end
+
+ context 'Fixnum 20' do
+ let(:input){ 20 }
+ it { should eq Time.zone.parse('2011-05-21 00:00:00') }
+ end
+ end
+ end
+
+ describe '#fortnight' do
+ subject { ByStar::Normalization.fortnight(input, options) }
+ it_behaves_like 'time normalization from string'
+ it_behaves_like 'time normalization from date/time'
+
+ context 'Fixnum 0' do
+ let(:input){ 0 }
+ it { should eq Time.zone.parse('2014-01-01 00:00:00') }
+ end
+
+ context 'Fixnum 26' do
+ let(:input){ 26 }
+ it { should eq Time.zone.parse('2014-12-31 00:00:00') }
+ end
+
+ context 'out of range' do
+ specify { ->{ ByStar::Normalization.fortnight(-1) }.should raise_error }
+ specify { ->{ ByStar::Normalization.fortnight(27) }.should raise_error }
+ end
+
+ context 'with year option' do
+ let(:options){ { :year => 2011 } }
+
+ context 'Fixnum 0' do
+ let(:input){ 0 }
+ it { should eq Time.zone.parse('2011-01-01 00:00:00') }
+ end
+
+ context 'Fixnum 26' do
+ let(:input){ 26 }
+ it { should eq Time.zone.parse('2011-12-31 00:00:00') }
+ end
+ end
+ end
+
+ describe '#month' do
+ subject { ByStar::Normalization.month(input, options) }
+ it_behaves_like 'time normalization from date/time'
+
+ context 'month abbr String' do
+ let(:input){ 'Feb' }
+ it { should eq Time.zone.parse('2014-02-01 00:00:00') }
+ end
+
+ context 'month full String' do
+ let(:input){ 'February' }
+ it { should eq Time.zone.parse('2014-02-01 00:00:00') }
+ end
+
+ context 'number String' do
+ let(:input){ '2' }
+ it { should eq Time.zone.parse('2014-02-01 00:00:00') }
+ end
+
+ context 'Fixnum' do
+ let(:input){ 2 }
+ it { should eq Time.zone.parse('2014-02-01 00:00:00') }
+ end
+
+ context 'with year option' do
+ let(:options){ { :year => 2011 } }
+
+ context 'month abbr String' do
+ let(:input){ 'Dec' }
+ it { should eq Time.zone.parse('2011-12-01 00:00:00') }
+ end
+
+ context 'Fixnum 12' do
+ let(:input){ 10 }
+ it { should eq Time.zone.parse('2011-10-01 00:00:00') }
+ end
+ end
+ end
+
+ describe '#quarter' do
+ subject { ByStar::Normalization.quarter(input, options) }
+ it_behaves_like 'time normalization from string'
+ it_behaves_like 'time normalization from date/time'
+
+ context 'Fixnum 1' do
+ let(:input){ 1 }
+ it { should eq Time.zone.parse('2014-01-01 00:00:00') }
+ end
+
+ context 'Fixnum 2' do
+ let(:input){ 2 }
+ it { should eq Time.zone.parse('2014-04-01 00:00:00') }
+ end
+
+ context 'Fixnum 3' do
+ let(:input){ 3 }
+ it { should eq Time.zone.parse('2014-07-01 00:00:00') }
+ end
+
+ context 'Fixnum 4' do
+ let(:input){ 4 }
+ it { should eq Time.zone.parse('2014-10-01 00:00:00') }
+ end
+
+ context 'with year option' do
+ let(:options){ { :year => 2011 } }
+
+ context 'Fixnum 3' do
+ let(:input){ 3 }
+ it { should eq Time.zone.parse('2011-07-01 00:00:00') }
+ end
+ end
+
+ context 'out of range' do
+ specify { ->{ ByStar::Normalization.quarter(0) }.should raise_error }
+ specify { ->{ ByStar::Normalization.quarter(5) }.should raise_error }
+ end
+ end
+
+ describe '#year' do
+ subject { ByStar::Normalization.year(input, options) }
+ it_behaves_like 'time normalization from string'
+ it_behaves_like 'time normalization from date/time'
+
+ context 'Fixnum 69' do
+ let(:input){ 69 }
+ it { should eq Time.zone.parse('2069-01-01 00:00:00') }
+ end
+
+ context 'Fixnum 99' do
+ let(:input){ 99 }
+ it { should eq Time.zone.parse('1999-01-01 00:00:00') }
+ end
+
+ context 'Fixnum 2001' do
+ let(:input){ 1 }
+ it { should eq Time.zone.parse('2001-01-01 00:00:00') }
+ end
+
+ context 'String 01' do
+ let(:input){ '01' }
+ it { should eq Time.zone.parse('2001-01-01 00:00:00') }
+ end
+
+ context 'String 70' do
+ let(:input){ '70' }
+ it { should eq Time.zone.parse('1970-01-01 00:00:00') }
+ end
+
+ context 'String 2001' do
+ let(:input){ '2001' }
+ it { should eq Time.zone.parse('2001-01-01 00:00:00') }
+ end
+ end
+end

0 comments on commit d59e451

Please sign in to comment.