by_star 2.1.0.beta2 → 2.2.0.rc1
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 +5 -4
- data/.travis.yml +35 -25
- data/CHANGELOG.md +21 -0
- data/Gemfile +25 -4
- data/MIT-LICENSE +20 -20
- data/{README.markdown → README.md} +414 -348
- data/Rakefile +18 -2
- data/UPGRADING +10 -0
- data/by_star.gemspec +32 -31
- data/cleaner.rb +24 -24
- data/lib/by_star.rb +15 -71
- data/lib/by_star/base.rb +53 -0
- data/lib/by_star/between.rb +80 -0
- data/lib/by_star/directional.rb +21 -0
- data/lib/by_star/kernel.rb +41 -0
- data/lib/by_star/normalization.rb +118 -0
- data/lib/by_star/orm/active_record/by_star.rb +52 -0
- data/lib/by_star/orm/mongoid/by_star.rb +59 -0
- data/lib/by_star/version.rb +3 -3
- data/spec/database.yml +15 -15
- data/spec/fixtures/active_record/models.rb +6 -13
- data/spec/fixtures/active_record/schema.rb +11 -36
- data/spec/fixtures/mongoid/models.rb +15 -65
- data/spec/fixtures/shared/seeds.rb +16 -35
- data/spec/integration/active_record/active_record_spec.rb +50 -0
- data/spec/integration/mongoid/mongoid_spec.rb +43 -0
- data/spec/integration/shared/by_calendar_month.rb +55 -0
- data/spec/integration/shared/by_day.rb +84 -0
- data/spec/integration/shared/by_direction.rb +55 -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/offset_parameter.rb +31 -0
- data/spec/spec_helper.rb +29 -35
- data/spec/unit/kernel_time_spec.rb +57 -0
- data/spec/unit/normalization_spec.rb +255 -0
- metadata +131 -56
- data/Gemfile.lock +0 -94
- data/lib/by_star/by_day.rb +0 -34
- data/lib/by_star/by_direction.rb +0 -52
- data/lib/by_star/by_fortnight.rb +0 -58
- data/lib/by_star/by_month.rb +0 -47
- data/lib/by_star/by_quarter.rb +0 -32
- data/lib/by_star/by_week.rb +0 -32
- data/lib/by_star/by_weekend.rb +0 -20
- data/lib/by_star/by_year.rb +0 -62
- data/lib/by_star/instance_methods.rb +0 -13
- data/lib/by_star/time_ext.rb +0 -21
- data/lib/mongoid/by_star.rb +0 -83
- data/spec/by_star/active_record/active_record_spec.rb +0 -50
- data/spec/by_star/mongoid/mongoid_spec.rb +0 -44
- data/spec/by_star/shared/by_day.rb +0 -62
- data/spec/by_star/shared/by_direction.rb +0 -85
- data/spec/by_star/shared/by_fortnight.rb +0 -47
- data/spec/by_star/shared/by_month.rb +0 -109
- data/spec/by_star/shared/by_quarter.rb +0 -33
- data/spec/by_star/shared/by_week.rb +0 -41
- data/spec/by_star/shared/by_weekend.rb +0 -13
- data/spec/by_star/shared/by_year.rb +0 -54
- data/spec/time_ext_spec.rb +0 -10
data/Rakefile
CHANGED
|
@@ -1,2 +1,18 @@
|
|
|
1
|
-
require 'bundler'
|
|
2
|
-
|
|
1
|
+
require 'bundler'
|
|
2
|
+
require 'rspec/core/rake_task'
|
|
3
|
+
|
|
4
|
+
Bundler::GemHelper.install_tasks
|
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
6
|
+
|
|
7
|
+
def orm_test(orm)
|
|
8
|
+
RSpec::Core::RakeTask.new(orm) do |task|
|
|
9
|
+
task.pattern = "./spec/{unit,integration/#{orm}}/{,/*/**}/*_spec.rb"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
namespace :spec do
|
|
14
|
+
orm_test 'active_record'
|
|
15
|
+
orm_test 'mongoid'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
task default: :spec
|
data/UPGRADING
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Upgrading to ByStar 2.2.0
|
|
2
|
+
-------------------------
|
|
3
|
+
|
|
4
|
+
* For Mongoid only, ByStar's `.between` method has been removed and replaced with `.between_times`
|
|
5
|
+
(previously `.between_times` was an alias to `.between`). Mongoid already provides a native `.between`
|
|
6
|
+
finder method that we do not want to mask. ActiveRecord users may continue to use either method.
|
|
7
|
+
|
|
8
|
+
* Chronic gem (used for time string parsing) has been removed as a hard dependency for ByStar,
|
|
9
|
+
however it is still supported. If you would like to use Chronic with ByStar, please explicitly
|
|
10
|
+
include `gem chronic` into your Gemfile.
|
data/by_star.gemspec
CHANGED
|
@@ -1,31 +1,32 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
|
-
require File.expand_path("../lib/by_star/version", __FILE__)
|
|
3
|
-
|
|
4
|
-
Gem::Specification.new do |s|
|
|
5
|
-
s.name = "by_star"
|
|
6
|
-
s.version = ByStar::VERSION
|
|
7
|
-
s.
|
|
8
|
-
s.
|
|
9
|
-
s.
|
|
10
|
-
s.
|
|
11
|
-
s.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
s.
|
|
16
|
-
|
|
17
|
-
s.add_development_dependency "
|
|
18
|
-
s.add_development_dependency "
|
|
19
|
-
s.add_development_dependency "
|
|
20
|
-
s.add_development_dependency "
|
|
21
|
-
s.add_development_dependency "
|
|
22
|
-
s.add_development_dependency "
|
|
23
|
-
s.add_development_dependency "
|
|
24
|
-
|
|
25
|
-
s.
|
|
26
|
-
s.
|
|
27
|
-
|
|
28
|
-
s.files
|
|
29
|
-
s.executables
|
|
30
|
-
s.
|
|
31
|
-
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path("../lib/by_star/version", __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
s.name = "by_star"
|
|
6
|
+
s.version = ByStar::VERSION
|
|
7
|
+
s.authors = ["Ryan Bigg", "Johnny Shields"]
|
|
8
|
+
s.email = ["radarlistener@gmail.com"]
|
|
9
|
+
s.homepage = "http://github.com/radar/by_star"
|
|
10
|
+
s.summary = "ActiveRecord and Mongoid extension for easier date scopes and time ranges"
|
|
11
|
+
s.description = "ActiveRecord and Mongoid extension for easier date scopes and time ranges"
|
|
12
|
+
|
|
13
|
+
s.post_install_message = File.read('UPGRADING') if File.exists?('UPGRADING')
|
|
14
|
+
|
|
15
|
+
s.add_dependency "activesupport"
|
|
16
|
+
|
|
17
|
+
s.add_development_dependency "chronic"
|
|
18
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
|
19
|
+
s.add_development_dependency "sqlite3"
|
|
20
|
+
s.add_development_dependency "activerecord"
|
|
21
|
+
s.add_development_dependency "mongoid"
|
|
22
|
+
s.add_development_dependency "pg"
|
|
23
|
+
s.add_development_dependency "mysql2"
|
|
24
|
+
s.add_development_dependency "rspec-rails", "~> 2.14"
|
|
25
|
+
s.add_development_dependency "timecop", "~> 0.3"
|
|
26
|
+
s.add_development_dependency "pry"
|
|
27
|
+
|
|
28
|
+
s.files = `git ls-files`.split($/)
|
|
29
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
30
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
|
31
|
+
s.require_paths = ['lib']
|
|
32
|
+
end
|
data/cleaner.rb
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
files = Dir["**/*"]
|
|
2
|
-
ignored_files = [
|
|
3
|
-
/log\/.*/,
|
|
4
|
-
]
|
|
5
|
-
|
|
6
|
-
files.delete_if do |file|
|
|
7
|
-
if File.directory?(file)
|
|
8
|
-
true
|
|
9
|
-
else
|
|
10
|
-
ignored_files.any? do |condition|
|
|
11
|
-
if condition.is_a?(String)
|
|
12
|
-
file == condition
|
|
13
|
-
else
|
|
14
|
-
condition.match(file)
|
|
15
|
-
end
|
|
16
|
-
end || false
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
for file in files - ignored_files
|
|
21
|
-
if File.file?(file)
|
|
22
|
-
lines = File.readlines(file).map { |line| line.gsub(/^\s+$/, "\n") }
|
|
23
|
-
File.open(file, "w+") { |f| f.write(lines.join) }
|
|
24
|
-
end
|
|
1
|
+
files = Dir["**/*"]
|
|
2
|
+
ignored_files = [
|
|
3
|
+
/log\/.*/,
|
|
4
|
+
]
|
|
5
|
+
|
|
6
|
+
files.delete_if do |file|
|
|
7
|
+
if File.directory?(file)
|
|
8
|
+
true
|
|
9
|
+
else
|
|
10
|
+
ignored_files.any? do |condition|
|
|
11
|
+
if condition.is_a?(String)
|
|
12
|
+
file == condition
|
|
13
|
+
else
|
|
14
|
+
condition.match(file)
|
|
15
|
+
end
|
|
16
|
+
end || false
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
for file in files - ignored_files
|
|
21
|
+
if File.file?(file)
|
|
22
|
+
lines = File.readlines(file).map { |line| line.gsub(/^\s+$/, "\n") }
|
|
23
|
+
File.open(file, "w+") { |f| f.write(lines.join) }
|
|
24
|
+
end
|
|
25
25
|
end
|
data/lib/by_star.rb
CHANGED
|
@@ -1,71 +1,15 @@
|
|
|
1
|
-
require '
|
|
2
|
-
|
|
3
|
-
require 'by_star/
|
|
4
|
-
require 'by_star/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
require 'by_star/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def by_star_field(field=nil)
|
|
18
|
-
@by_star_field ||= field
|
|
19
|
-
@by_star_field || "#{self.table_name}.created_at"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
include ByDirection
|
|
23
|
-
include ByYear
|
|
24
|
-
include ByMonth
|
|
25
|
-
include ByFortnight
|
|
26
|
-
include ByWeek
|
|
27
|
-
include ByWeekend
|
|
28
|
-
include ByDay
|
|
29
|
-
include ByQuarter
|
|
30
|
-
|
|
31
|
-
class ParseError < StandardError
|
|
32
|
-
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Returns all records between a given start and finish time.
|
|
36
|
-
#
|
|
37
|
-
# Currently only supports Time objects.
|
|
38
|
-
def between(start, finish, options={})
|
|
39
|
-
field = options[:field] || by_star_field
|
|
40
|
-
scope = where("#{field} >= ? AND #{field} <= ?",
|
|
41
|
-
start, finish)
|
|
42
|
-
scope = scope.order(options[:order]) if options[:order]
|
|
43
|
-
scope
|
|
44
|
-
end
|
|
45
|
-
alias_method :between_times, :between
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
# Used inside the by_* methods to determine what kind of object "time" is.
|
|
50
|
-
# These methods take the result of the time_klass method, and call other methods
|
|
51
|
-
# using it, such as by_year_Time and by_year_String.
|
|
52
|
-
def time_klass(time)
|
|
53
|
-
case time
|
|
54
|
-
when ActiveSupport::TimeWithZone
|
|
55
|
-
Time
|
|
56
|
-
else
|
|
57
|
-
time.class
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
if defined?(ActiveRecord)
|
|
64
|
-
ActiveRecord::Base.send :extend, ByStar
|
|
65
|
-
ActiveRecord::Relation.send :extend, ByStar
|
|
66
|
-
ActiveRecord::Base.send :include, ByStar::InstanceMethods
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
if defined?(Mongoid)
|
|
70
|
-
require 'mongoid/by_star'
|
|
71
|
-
end
|
|
1
|
+
require 'by_star/kernel'
|
|
2
|
+
require 'by_star/normalization'
|
|
3
|
+
require 'by_star/between'
|
|
4
|
+
require 'by_star/directional'
|
|
5
|
+
require 'by_star/base'
|
|
6
|
+
|
|
7
|
+
if defined?(ActiveRecord)
|
|
8
|
+
require 'by_star/orm/active_record/by_star'
|
|
9
|
+
ActiveRecord::Base.send :include, ByStar::ActiveRecord
|
|
10
|
+
ActiveRecord::Relation.send :extend, ByStar::ActiveRecord::ClassMethods
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if defined?(Mongoid)
|
|
14
|
+
require 'by_star/orm/mongoid/by_star'
|
|
15
|
+
end
|
data/lib/by_star/base.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module ByStar
|
|
2
|
+
|
|
3
|
+
module Base
|
|
4
|
+
|
|
5
|
+
include ByStar::Between
|
|
6
|
+
include ByStar::Directional
|
|
7
|
+
|
|
8
|
+
def by_star_field(start_field = nil, end_field = nil, options = {})
|
|
9
|
+
@by_star_start_field ||= start_field
|
|
10
|
+
@by_star_end_field ||= end_field
|
|
11
|
+
@by_star_offset ||= options[:offset]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def by_star_offset(options = {})
|
|
15
|
+
(options[:offset] || @by_star_offset || 0).seconds
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def by_star_start_field(options={})
|
|
19
|
+
field = options[:field] ||
|
|
20
|
+
options[:start_field] ||
|
|
21
|
+
@by_star_start_field ||
|
|
22
|
+
by_star_default_field
|
|
23
|
+
field.to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def by_star_end_field(options={})
|
|
27
|
+
field = options[:field] ||
|
|
28
|
+
options[:end_field] ||
|
|
29
|
+
@by_star_end_field ||
|
|
30
|
+
by_star_start_field
|
|
31
|
+
field.to_s
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
|
|
36
|
+
# Wrapper function which extracts time and options for each by_star query.
|
|
37
|
+
# Note the following syntax examples are valid:
|
|
38
|
+
#
|
|
39
|
+
# Post.by_month # defaults to current time
|
|
40
|
+
# Post.by_month(2, :year => 2004) # February, 2004
|
|
41
|
+
# Post.by_month(Time.now)
|
|
42
|
+
# Post.by_month(Time.now, :field => "published_at")
|
|
43
|
+
# Post.by_month(:field => "published_at")
|
|
44
|
+
#
|
|
45
|
+
def with_by_star_options(*args, &block)
|
|
46
|
+
options = args.extract_options!.symbolize_keys!
|
|
47
|
+
time = args.first
|
|
48
|
+
time ||= Time.zone.local(options[:year]) if options[:year]
|
|
49
|
+
time ||= Time.zone.now
|
|
50
|
+
block.call(time, options)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module ByStar
|
|
2
|
+
|
|
3
|
+
module Between
|
|
4
|
+
|
|
5
|
+
def between_times(start, finish, options={})
|
|
6
|
+
offset = by_star_offset(options)
|
|
7
|
+
between_times_query(start + offset, finish + offset, options)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def by_day(*args)
|
|
11
|
+
with_by_star_options(*args) do |time, options|
|
|
12
|
+
time = ByStar::Normalization.time(time)
|
|
13
|
+
between_times(time.beginning_of_day, time.end_of_day, options)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def today(options={})
|
|
18
|
+
by_day(Time.zone.now, options)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def yesterday(options={})
|
|
22
|
+
by_day(Time.zone.now.yesterday, options)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def tomorrow(options={})
|
|
26
|
+
by_day(Time.zone.now.tomorrow, options)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def by_week(*args)
|
|
30
|
+
with_by_star_options(*args) do |time, options|
|
|
31
|
+
time = ByStar::Normalization.week(time, options)
|
|
32
|
+
start_day = Array(options[:start_day])
|
|
33
|
+
between_times(time.beginning_of_week(*start_day), time.end_of_week(*start_day), options)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def by_weekend(*args)
|
|
38
|
+
with_by_star_options(*args) do |time, options|
|
|
39
|
+
time = ByStar::Normalization.week(time, options)
|
|
40
|
+
between_times(time.beginning_of_weekend, time.end_of_weekend, options)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def by_fortnight(*args)
|
|
45
|
+
with_by_star_options(*args) do |time, options|
|
|
46
|
+
time = ByStar::Normalization.fortnight(time, options)
|
|
47
|
+
between_times(time.beginning_of_fortnight, time.end_of_fortnight, options)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def by_month(*args)
|
|
52
|
+
with_by_star_options(*args) do |time, options|
|
|
53
|
+
time = ByStar::Normalization.month(time, options)
|
|
54
|
+
between_times(time.beginning_of_month, time.end_of_month, options)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def by_calendar_month(*args)
|
|
59
|
+
with_by_star_options(*args) do |time, options|
|
|
60
|
+
time = ByStar::Normalization.month(time, options)
|
|
61
|
+
start_day = Array(options[:start_day])
|
|
62
|
+
between_times(time.beginning_of_calendar_month(*start_day), time.end_of_calendar_month(*start_day), options)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def by_quarter(*args)
|
|
67
|
+
with_by_star_options(*args) do |time, options|
|
|
68
|
+
time = ByStar::Normalization.quarter(time, options)
|
|
69
|
+
between_times(time.beginning_of_quarter, time.end_of_quarter, options)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def by_year(*args)
|
|
74
|
+
with_by_star_options(*args) do |time, options|
|
|
75
|
+
time = ByStar::Normalization.year(time, options)
|
|
76
|
+
between_times(time.beginning_of_year, time.end_of_year, options)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module ByStar
|
|
2
|
+
|
|
3
|
+
module Directional
|
|
4
|
+
|
|
5
|
+
def before(*args)
|
|
6
|
+
with_by_star_options(*args) do |time, options|
|
|
7
|
+
time = ByStar::Normalization.time(time)
|
|
8
|
+
before_query(time, options)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
alias_method :before_now, :before
|
|
12
|
+
|
|
13
|
+
def after(*args)
|
|
14
|
+
with_by_star_options(*args) do |time, options|
|
|
15
|
+
time = ByStar::Normalization.time(time)
|
|
16
|
+
after_query(time, options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
alias_method :after_now, :after
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module ByStar
|
|
2
|
+
|
|
3
|
+
module Kernel
|
|
4
|
+
|
|
5
|
+
module Time
|
|
6
|
+
|
|
7
|
+
# A "Weekend" is defined as the 60-hour period from 15:00 Friday to 03:00 Monday.
|
|
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 => 4) + 15.hours
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def end_of_weekend
|
|
15
|
+
(beginning_of_weekend + 59.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 + ((self - beginning_of_year) / 2.weeks).to_i * 2.weeks
|
|
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
|
|
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,118 @@
|
|
|
1
|
+
module ByStar
|
|
2
|
+
|
|
3
|
+
class ParseError < StandardError; end
|
|
4
|
+
|
|
5
|
+
module Normalization
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
def time(value)
|
|
10
|
+
case value
|
|
11
|
+
when String then time_string(value)
|
|
12
|
+
when DateTime then value.to_time
|
|
13
|
+
when Date then value.to_time_in_current_zone
|
|
14
|
+
else value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def time_string(value)
|
|
19
|
+
defined?(Chronic) ? time_string_chronic(value) : time_string_fallback(value)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def time_string_chronic(value)
|
|
23
|
+
Chronic.time_class = Time.zone
|
|
24
|
+
Chronic.parse(value) || raise(ByStar::ParseError, "Chronic could not parse String #{value.inspect}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def time_string_fallback(value)
|
|
28
|
+
Time.zone.parse(value) || raise(ByStar::ParseError, "Cannot parse String #{value.inspect}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def week(value, options={})
|
|
32
|
+
value = try_string_to_int(value)
|
|
33
|
+
case value
|
|
34
|
+
when Fixnum then week_fixnum(value, options)
|
|
35
|
+
else time(value)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def week_fixnum(value, options={})
|
|
40
|
+
raise ParseError, 'Week number must be between 0 and 52' unless value.in?(0..52)
|
|
41
|
+
time = Time.zone.local(options[:year] || Time.zone.now.year)
|
|
42
|
+
time.beginning_of_year + value.to_i.weeks
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def fortnight(value, options={})
|
|
46
|
+
value = try_string_to_int(value)
|
|
47
|
+
case value
|
|
48
|
+
when Fixnum then fortnight_fixnum(value, options)
|
|
49
|
+
else time(value)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fortnight_fixnum(value, options={})
|
|
54
|
+
raise ParseError, 'Fortnight number must be between 0 and 26' unless value.in?(0..26)
|
|
55
|
+
time = Time.zone.local(options[:year] || Time.zone.now.year)
|
|
56
|
+
time + (value * 2).weeks
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def quarter(value, options={})
|
|
60
|
+
value = try_string_to_int(value)
|
|
61
|
+
case value
|
|
62
|
+
when Fixnum then quarter_fixnum(value, options)
|
|
63
|
+
else time(value)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def quarter_fixnum(value, options={})
|
|
68
|
+
raise ParseError, 'Quarter number must be between 1 and 4' unless value.in?(1..4)
|
|
69
|
+
time = Time.zone.local(options[:year] || Time.zone.now.year)
|
|
70
|
+
time.beginning_of_year + ((value - 1) * 3).months
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def month(value, options={})
|
|
74
|
+
value = try_string_to_int(value)
|
|
75
|
+
case value
|
|
76
|
+
when Fixnum, String then month_fixnum(value, options)
|
|
77
|
+
else time(value)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def month_fixnum(value, options={})
|
|
82
|
+
year = options[:year] || Time.zone.now.year
|
|
83
|
+
Time.zone.parse "#{year}-#{value}-01"
|
|
84
|
+
rescue
|
|
85
|
+
raise ParseError, 'Month must be a number between 1 and 12 or a month name'
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def year(value, options={})
|
|
89
|
+
value = try_string_to_int(value)
|
|
90
|
+
case value
|
|
91
|
+
when Fixnum then year_fixnum(value)
|
|
92
|
+
else time(value)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def year_fixnum(value)
|
|
97
|
+
Time.zone.local(extrapolate_year(value))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def extrapolate_year(value)
|
|
101
|
+
case value.to_i
|
|
102
|
+
when 0..69
|
|
103
|
+
2000 + value
|
|
104
|
+
when 70..99
|
|
105
|
+
1900 + value
|
|
106
|
+
else
|
|
107
|
+
value.to_i
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def try_string_to_int(value)
|
|
112
|
+
value.is_a?(String) ? Integer(value) : value
|
|
113
|
+
rescue
|
|
114
|
+
value
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|