Commit f8253f1e by liyijie

Initial commit

parents
# CHANGELOG
## v4.0.0 - Unreleased
* BREAKING CHANGE - `#between` method has been removed (was deprecated in 2.2.0)
* BREAKING CHANGE - `:order` arg for `#between_times` has been removed.
* BREAKING CHANGE - Drop support for option `:year` used as a standalone. Use `by_year` instead.
* BREAKING CHANGE - `#between_times` now queries on records until end of day on `end_time`, if it is a date.
* `#between_times` now accepts one-sided arguments, e.g. `Time, nil` or `nil, Time`.
* `#between_times` now accepts `Range` or `Array` as an argument, while continuing to support existing `Time, Time` interface.
* Add `#at_time` method for point-in-time query.
* Timespan "strict" query now sets double-sided constraints on both fields to ensure database indexes are used properly.
* Add optional `:index_scope` option for timespan "non-strict" queries to improve database performance.
* Fixes `offset` option to set hour, min, and sec of day for queries on dates for better DST support.
* More consistent application of `Time.zone`.
* Re-add test coverage for Rails 3.2. This will be removed when we upgrade to Ruby 2.2+ as minimum version.
## v3.0.0
* Upgrade Travis for broader coverage of Ruby, ActiveRecord, and Mongoid versions - @johnnyshields
* Removed support for Ruby < 2.0 and Rails < 4.0. They are over 5 years old, and so it's time to upgrade. - @radar
* Removed references to deprecated Fixnum constant - @rgioia - #78
* Mongoid `newest`, `oldest`, `previous`, and `next` now use `reorder` to ignore any default scope, consistent with ActiveRecord - @johnnyshields
* Mongoid 3.x: Add support for `Criteria#reorder` method from version 4+ - @johnnyshields
* Upgrade Rspec tests to version 3.1 - @nhocki
## v2.2.1 - 2014-04-21
* Allow `previous` and `next` to take the current record in their scope - @pnomolos / @johnnyshields
* Alias `Date#in_time_zone` to `#to_time_in_current_zone` if not already defined (e.g. for Rails <= 3) - @jcypret / @johnnyshields
* Add `oldest` and `newest` methods
## v2.2.0 - 2014-04-01
* Add `:scope` parameter support on all finders - @pnomolos / @johnnyshields
* Feature: Add `past_*` and `next_*` finders - @davegudge
* Bug Fix: `:field`, `:start_field`, and `:end_field` options were being ignored on finder - @johnnyshields / @gamov
* Bug Fix: `by_star_field` should accept options without start/end_time args - @johnnyshields
* Improve readme documentation - @johnnyshields
## v2.2.0.rc1 - 2014-01-14
* Begin tracking CHANGELOG
* Drop official support for Ruby <= 1.9.2
* Add Ruby 2.1.0, Rubinius, and JRuby to Travis
* 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` method to `between_times` internally, as Mongoid already defines `between`. ActiveRecord has an alias of `between` so 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
source 'http://rubygems.org'
gemspec
ar_version = ENV['ACTIVE_RECORD_VERSION']
ar_version = case ar_version
when 'master' then {github: 'rails'}
when String then "~> #{ar_version}"
end
mo_version = ENV['MONGOID_VERSION']
mo_version = case mo_version
when 'master' then {github: 'mongoid'}
when String then "~> #{mo_version}"
end
gem 'activerecord', ar_version if ar_version
gem 'mongoid', mo_version if mo_version
Copyright (c) 2008 Ryan Bigg
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
This diff is collapsed. Click to expand it.
require 'bundler'
require 'rspec/core/rake_task'
Bundler::GemHelper.install_tasks
RSpec::Core::RakeTask.new(:spec)
def orm_test(orm)
RSpec::Core::RakeTask.new(orm) do |task|
task.pattern = "./spec/{unit,integration/#{orm}}/{,/*/**}/*_spec.rb"
end
end
namespace :spec do
orm_test 'active_record'
orm_test 'mongoid'
end
task default: :spec
Upgrading ByStar
----------------
* As of version 4.0.0, ByStar changes the way it handles `Date` arg to the `#between_times` method. If a `Date` is given as the second (end) arg, the query will use `Date.end_of_day` to include all time values which fall inside that date.
# -*- encoding: utf-8 -*-
require File.expand_path("../lib/by_star/version", __FILE__)
Gem::Specification.new do |s|
s.name = "by_star"
s.version = ByStar::VERSION
s.authors = ["Ryan Bigg", "Johnny Shields"]
s.email = ["radarlistener@gmail.com"]
s.homepage = "http://github.com/radar/by_star"
s.summary = "ActiveRecord and Mongoid extension for easier date scopes and time ranges"
s.description = "ActiveRecord and Mongoid extension for easier date scopes and time ranges"
s.required_ruby_version = '>= 2.0.0'
s.post_install_message = File.read('UPGRADING') if File.exists?('UPGRADING')
s.add_dependency "activesupport", ">= 3.2.0"
s.add_development_dependency "chronic"
s.add_development_dependency "bundler"
s.add_development_dependency "sqlite3"
s.add_development_dependency "activerecord"
s.add_development_dependency "mongoid"
s.add_development_dependency "pg"
s.add_development_dependency "mysql2"
s.add_development_dependency "rspec-rails", "~> 3.1"
s.add_development_dependency "timecop", "~> 0.3"
s.add_development_dependency "pry"
s.files = `git ls-files`.split($/)
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
s.test_files = s.files.grep(%r{^(test|spec|features)/})
s.require_paths = ['lib']
end
files = Dir["**/*"]
ignored_files = [
/log\/.*/,
]
files.delete_if do |file|
if File.directory?(file)
true
else
ignored_files.any? do |condition|
if condition.is_a?(String)
file == condition
else
condition.match(file)
end
end || false
end
end
for file in files - ignored_files
if File.file?(file)
lines = File.readlines(file).map { |line| line.gsub(/^\s+$/, "\n") }
File.open(file, "w+") { |f| f.write(lines.join) }
end
end
\ No newline at end of file
require 'by_star/kernel/in_time_zone'
require 'by_star/kernel/time'
require 'by_star/kernel/date'
require 'by_star/normalization'
require 'by_star/between'
require 'by_star/directional'
require 'by_star/base'
if defined?(ActiveRecord)
require 'by_star/orm/active_record/by_star'
ActiveRecord::Base.send :include, ByStar::ActiveRecord
ActiveRecord::Relation.send :extend, ByStar::ActiveRecord::ClassMethods
end
if defined?(Mongoid)
require 'by_star/orm/mongoid/reorder'
require 'by_star/orm/mongoid/by_star'
end
module ByStar
module Base
include ByStar::Between
include ByStar::Directional
def by_star_field(*args)
options = args.extract_options!
@by_star_start_field ||= args[0]
@by_star_end_field ||= args[1]
@by_star_offset ||= options[:offset]
@by_star_scope ||= options[:scope]
@by_star_index_scope ||= options[:index_scope]
@by_star_field_type ||= options[:field_type]
end
def by_star_offset(options = {})
(options[:offset] || @by_star_offset || 0).seconds
end
def by_star_start_field(options={})
field = options[:field] ||
options[:start_field] ||
@by_star_start_field ||
by_star_default_field
field.to_s
end
def by_star_end_field(options={})
field = options[:field] ||
options[:end_field] ||
@by_star_end_field ||
by_star_start_field
field.to_s
end
def by_star_field_type(options={})
field = options[:field_type] ||
@by_star_field_type
field.to_s
end
protected
# Wrapper function which extracts time and options for each by_star query.
# Note the following syntax examples are valid:
#
# Post.by_month # defaults to current time
# Post.by_month(2, year: 2004) # February, 2004
# Post.by_month(Time.now)
# Post.by_month(Time.now, field: "published_at")
# Post.by_month(field: "published_at")
#
def with_by_star_options(*args, &block)
options = args.extract_options!.symbolize_keys!
time = args.first || Time.zone.now
block.call(time, options)
end
def by_star_eval_index_scope(start_time, end_time, options)
value = options[:index_scope] || @by_star_index_scope
value = value.call(start_time, end_time, options) if value.is_a?(Proc)
case value
when nil, false then nil
when Time, DateTime, Date then value.in_time_zone
when ActiveSupport::Duration then start_time - value
when Numeric then start_time - value.seconds
when :beginning_of_day
offset = options[:offset] || 0
(start_time - offset).beginning_of_day + offset
else raise 'ByStar :index_scope option value is not a supported type.'
end
end
end
end
module ByStar
module Between
def between_times(*args)
options = args.extract_options!.symbolize_keys!
start_time, end_time = ByStar::Normalization.extract_range(args)
offset = options[:offset] || 0
field_type = by_star_field_type(options)
if start_time.is_a?(Date)
start_time = field_type == 'date' ?
start_time :
ByStar::Normalization.apply_offset_start(start_time.in_time_zone, offset)
elsif start_time
start_time += offset.seconds
end
if end_time.is_a?(Date)
end_time = field_type == 'date' ?
end_time :
ByStar::Normalization.apply_offset_end(end_time.in_time_zone, offset)
elsif end_time
end_time += offset.seconds
end
start_field = by_star_start_field(options)
end_field = by_star_end_field(options)
scope = self
scope = if !start_time && !end_time
scope # do nothing
elsif !end_time
by_star_after_query(scope, start_field, start_time)
elsif !start_time
by_star_before_query(scope, start_field, end_time)
elsif start_field == end_field
by_star_point_query(scope, start_field, start_time, end_time)
elsif options[:strict]
by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
else
by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
end
scope = by_star_order(scope, options[:order]) if options[:order]
scope
end
def between_dates(*args)
options = args.extract_options!
start_date, end_date = ByStar::Normalization.extract_range(args)
start_date = ByStar::Normalization.date(start_date)
end_date = ByStar::Normalization.date(end_date)
between_times(start_date, end_date, options)
end
def at_time(*args)
with_by_star_options(*args) do |time, options|
start_field = by_star_start_field(options)
end_field = by_star_end_field(options)
scope = self
scope = if start_field == end_field
by_star_point_overlap_query(scope, start_field, time)
else
by_star_span_overlap_query(scope, start_field, end_field, time, options)
end
scope = by_star_order(scope, options[:order]) if options[:order]
scope
end
end
def by_day(*args)
with_by_star_options(*args) do |time, options|
date = ByStar::Normalization.date(time)
between_dates(date, date, options)
end
end
def by_week(*args)
with_by_star_options(*args) do |time, options|
date = ByStar::Normalization.week(time, options)
start_day = Array(options[:start_day])
between_dates(date.beginning_of_week(*start_day), date.end_of_week(*start_day), options)
end
end
def by_cweek(*args)
with_by_star_options(*args) do |time, options|
by_week(ByStar::Normalization.cweek(time, options), options)
end
end
def by_weekend(*args)
with_by_star_options(*args) do |time, options|
date = ByStar::Normalization.week(time, options)
between_dates(date.beginning_of_weekend, date.end_of_weekend, options)
end
end
def by_fortnight(*args)
with_by_star_options(*args) do |time, options|
date = ByStar::Normalization.fortnight(time, options)
between_dates(date.beginning_of_fortnight, date.end_of_fortnight, options)
end
end
def by_month(*args)
with_by_star_options(*args) do |time, options|
date = ByStar::Normalization.month(time, options)
between_dates(date.beginning_of_month, date.end_of_month, options)
end
end
def by_calendar_month(*args)
with_by_star_options(*args) do |time, options|
date = ByStar::Normalization.month(time, options)
start_day = Array(options[:start_day])
between_dates(date.beginning_of_calendar_month(*start_day), date.end_of_calendar_month(*start_day), options)
end
end
def by_quarter(*args)
with_by_star_options(*args) do |time, options|
date = ByStar::Normalization.quarter(time, options)
between_dates(date.beginning_of_quarter, date.end_of_quarter, options)
end
end
def by_year(*args)
with_by_star_options(*args) do |time, options|
date = ByStar::Normalization.year(time, options)
between_dates(date.beginning_of_year, date.to_date.end_of_year, options)
end
end
def today(options = {})
by_day(Date.current, options)
end
def yesterday(options = {})
by_day(Date.yesterday, options)
end
def tomorrow(options = {})
by_day(Date.tomorrow, options)
end
def past_day(options = {})
between_times(Time.current - 1.day, Time.current, options)
end
def past_week(options = {})
between_times(Time.current - 1.week, Time.current, options)
end
def past_fortnight(options = {})
between_times(Time.current - 2.weeks, Time.current, options)
end
def past_month(options = {})
between_times(Time.current - 1.month, Time.current, options)
end
def past_year(options = {})
between_times(Time.current - 1.year, Time.current, options)
end
def next_day(options = {})
between_times(Time.current, Time.current + 1.day, options)
end
def next_week(options = {})
between_times(Time.current, Time.current + 1.week, options)
end
def next_fortnight(options = {})
between_times(Time.current, Time.current + 2.weeks, options)
end
def next_month(options = {})
between_times(Time.current, Time.current + 1.month, options)
end
def next_year(options = {})
between_times(Time.current, Time.current + 1.year, options)
end
end
end
module ByStar
module Directional
def before(*args)
with_by_star_options(*args) do |time, options|
field = by_star_start_field(options)
time = ByStar::Normalization.time(time)
by_star_before_query(self, field, time)
end
end
alias_method :before_now, :before
def after(*args)
with_by_star_options(*args) do |time, options|
field = by_star_start_field(options)
time = ByStar::Normalization.time(time)
by_star_after_query(self, field, time)
end
end
alias_method :after_now, :after
def oldest(*args)
with_by_star_options(*args) do |time, options|
oldest_query(options)
end
end
def newest(*args)
with_by_star_options(*args) do |time, options|
newest_query(options)
end
end
end
end
module ByStar
module Kernel
module Date
# A "Weekend" is defined as beginning of Saturday to end of Sunday.
# The weekend for a given date will be the the next weekend if the day Mon-Thurs,
# otherwise the current weekend if the day is Fri-Sun.
def beginning_of_weekend
beginning_of_week(:monday).advance(days: 5)
end
def end_of_weekend
beginning_of_weekend + 1
end
# A "Fortnight" is defined as a two week period, with the first fortnight of the
# year beginning on 1st January.
def beginning_of_fortnight
beginning_of_year + 14 * ((self - beginning_of_year) / 14).to_i
end
def end_of_fortnight
beginning_of_fortnight + 13
end
# A "Calendar Month" is defined as a month as it appears on a calendar, including days form
# previous/following months which are part of the first/last weeks of the given month.
def beginning_of_calendar_month(*args)
beginning_of_month.beginning_of_week(*args)
end
def end_of_calendar_month(*args)
end_of_month.end_of_week(*args)
end
end
end
end
::Date.__send__(:include, ByStar::Kernel::Date)
module ByStar
module Kernel
module InTimeZone
extend ActiveSupport::Concern
included do
if method_defined?(:to_time_in_current_zone) && !method_defined?(:in_time_zone)
alias_method :in_time_zone, :to_time_in_current_zone
end
end
end
end
end
::Date.__send__(:include, ByStar::Kernel::InTimeZone)
::Time.__send__(:include, ByStar::Kernel::InTimeZone)
::DateTime.__send__(:include, ByStar::Kernel::InTimeZone)
::ActiveSupport::TimeWithZone.__send__(:include, ByStar::Kernel::InTimeZone)
module ByStar
module Kernel
module Time
# A "Weekend" is defined as beginning of Saturday to end of Sunday.
# The weekend for a given date will be the the next weekend if the day Mon-Thurs,
# otherwise the current weekend if the day is Fri-Sun.
def beginning_of_weekend
beginning_of_week(:monday).advance(days: 5)
end
def end_of_weekend
(beginning_of_weekend + 47.hours).end_of_hour
end
# A "Fortnight" is defined as a two week period, with the first fortnight of the
# year beginning on 1st January.
def beginning_of_fortnight
(beginning_of_year + 1.fortnight * ((self - beginning_of_year) / 1.fortnight).to_i).beginning_of_day
end
def end_of_fortnight
(beginning_of_fortnight + 13.days).end_of_day
end
# A "Calendar Month" is defined as a month as it appears on a calendar, including days form
# previous/following months which are part of the first/last weeks of the given month.
def beginning_of_calendar_month(*args)
beginning_of_month.beginning_of_week(*args)
end
def end_of_calendar_month(*args)
end_of_month.end_of_week(*args)
end
end
end
end
::Time.__send__(:include, ByStar::Kernel::Time)
module ByStar
class ParseError < StandardError; end
module Normalization
class << self
def date(value)
value = parse_time(value) if value.is_a?(String)
value = value.try(:in_time_zone) unless value.is_a?(Date)
value.try(:to_date)
end
def time(value)
value = parse_time(value) if value.is_a?(String)
value.try(:in_time_zone)
end
def week(value, options={})
value = try_string_to_int(value)
case value
when Integer then week_integer(value, options)
else date(value)
end
end
def week_integer(value, options={})
raise ParseError, 'Week number must be between 0 and 52' unless value.in?(0..52)
time = Time.zone.local(options[:year] || Time.zone.now.year)
time.beginning_of_year + value.to_i.weeks
end
def cweek(value, options={})
_value = value
if _value.is_a?(Integer)
raise ParseError, 'cweek number must be between 1 and 53' unless value.in?(1..53)
_value -= 1
end
week(_value, options)
end
def fortnight(value, options={})
value = try_string_to_int(value)
case value
when Integer then fortnight_integer(value, options)
else date(value)
end
end
def fortnight_integer(value, options={})
raise ParseError, 'Fortnight number must be between 0 and 26' unless value.in?(0..26)
time = Time.zone.local(options[:year] || Time.zone.now.year)
time + (value * 2).weeks
end
def quarter(value, options={})
value = try_string_to_int(value)
case value
when Integer then quarter_integer(value, options)
else date(value)
end
end
def quarter_integer(value, options={})
raise ParseError, 'Quarter number must be between 1 and 4' unless value.in?(1..4)
time = Time.zone.local(options[:year] || Time.zone.now.year)
time.beginning_of_year + ((value - 1) * 3).months
end
def month(value, options={})
value = try_string_to_int(value)
case value
when Integer, String then month_integer(value, options)
else date(value)
end
end
def month_integer(value, options={})
year = options[:year] || Time.zone.now.year
Time.zone.parse "#{year}-#{value}-01"
rescue
raise ParseError, 'Month must be a number between 1 and 12 or a month name'
end
def year(value, options={})
value = try_string_to_int(value)
case value
when Integer then year_integer(value)
else date(value)
end
end
def year_integer(value)
Time.zone.local(extrapolate_year(value))
end
def extrapolate_year(value)
case value.to_i
when 0..69
2000 + value
when 70..99
1900 + value
else
value.to_i
end
end
def try_string_to_int(value)
value.is_a?(String) ? Integer(value) : value
rescue
value
end
def time_in_units(seconds)
days = seconds / 1.day
time = Time.at(seconds).utc
{ days: days, hour: time.hour, min: time.min, sec: time.sec }
end
def apply_offset_start(time, offset)
units = time_in_units(offset)
time += units.delete(:days).days
time.change(units)
end
def apply_offset_end(time, offset)
units = time_in_units(offset)
time += units.delete(:days).days
(time + 1.day).change(units) - 1.second
end
def extract_range(args)
case args[0]
when Array, Range then [args[0].first, args[0].last]
else args[0..1]
end
end
private
def parse_time(value)
defined?(Chronic) ? parse_time_chronic(value) : parse_time_fallback(value)
end
def parse_time_chronic(value)
Chronic.time_class = Time.zone
Chronic.parse(value) || raise(ByStar::ParseError, "Chronic could not parse String #{value.inspect}")
end
def parse_time_fallback(value)
Time.zone.parse(value) || raise(ByStar::ParseError, "Cannot parse String #{value.inspect}")
end
end
end
end
module ByStar
module ActiveRecord
extend ActiveSupport::Concern
module ClassMethods
include ::ByStar::Base
protected
def by_star_default_field
"#{self.table_name}.created_at"
end
def by_star_point_query(scope, field, start_time, end_time)
scope.where("#{field} >= ? AND #{field} <= ?", start_time, end_time)
end
def by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
scope.where("#{start_field} >= ? AND #{start_field} <= ? AND #{end_field} >= ? AND #{end_field} <= ?", start_time, end_time, start_time, end_time)
end
def by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
index_scope = by_star_eval_index_scope(start_time, end_time, options)
scope = scope.where("#{end_field} > ? AND #{start_field} < ?", start_time, end_time)
scope = scope.where("#{start_field} >= ?", index_scope) if index_scope
scope
end
def by_star_point_overlap_query(scope, field, time)
scope.where("#{field} = ?", time)
end
def by_star_span_overlap_query(scope, start_field, end_field, time, options)
index_scope = by_star_eval_index_scope(time, time, options)
scope = scope.where("#{end_field} > ? AND #{start_field} <= ?", time, time)
scope = scope.where("#{start_field} >= ?", index_scope) if index_scope
scope
end
def by_star_before_query(scope, field, time)
scope.where("#{field} <= ?", time)
end
def by_star_after_query(scope, field, time)
scope.where("#{field} >= ?", time)
end
def by_star_order(scope, order)
scope.order(order)
end
def oldest_query(options={})
field = by_star_start_field(options)
reorder("#{field} ASC").first
end
def newest_query(options={})
field = by_star_start_field(options)
reorder("#{field} DESC").first
end
end
def previous(options={})
field = self.class.by_star_start_field(options)
value = self.send(field.split(".").last)
self.class.where("#{field} < ?", value).reorder("#{field} DESC").first
end
def next(options={})
field = self.class.by_star_start_field(options)
value = self.send(field.split(".").last)
self.class.where("#{field} > ?", value).reorder("#{field} ASC").first
end
end
end
# In keeping with Mongoid standards, this module must be included into your model class, i.e.
#
# include Mongoid::ByStar
#
module Mongoid
module ByStar
extend ActiveSupport::Concern
module ClassMethods
include ::ByStar::Base
alias_method :original_by_star_end_field, :by_star_end_field
alias_method :original_by_star_start_field, :by_star_start_field
def by_star_end_field(options = {})
database_field_name original_by_star_end_field(options)
end
def by_star_start_field(options = {})
database_field_name original_by_star_start_field(options)
end
def by_star_default_field
:created_at
end
protected
def by_star_point_query(scope, field, start_time, end_time)
range = start_time..end_time
scope.where(field => range)
end
def by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
range = start_time..end_time
scope.where(start_field => range).where(end_field => range)
end
def by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
index_scope = by_star_eval_index_scope(start_time, end_time, options)
scope = scope.gt(end_field => start_time).lt(start_field => end_time)
scope = scope.gte(start_field => index_scope) if index_scope
scope
end
def by_star_point_overlap_query(scope, field, time)
scope.where(field => time)
end
def by_star_span_overlap_query(scope, start_field, end_field, time, options)
index_scope = by_star_eval_index_scope(time, time, options)
scope = scope.gt(end_field => time).lte(start_field => time)
scope = scope.gte(start_field => index_scope) if index_scope
scope
end
def by_star_before_query(scope, field, time)
scope.lte(field => time)
end
def by_star_after_query(scope, field, time)
scope.gte(field => time)
end
def by_star_order(scope, order)
scope.order_by(order)
end
def oldest_query(options={})
field = by_star_start_field(options)
all.reorder(field => :asc).first
end
def newest_query(options={})
field = by_star_start_field(options)
all.reorder(field => :desc).first
end
end
def previous(options={})
field = self.class.by_star_start_field(options)
self.class.lt(field => self.send(field)).reorder(field => :desc).first
end
def next(options={})
field = self.class.by_star_start_field(options)
self.class.gt(field => self.send(field)).reorder(field => :asc).first
end
end
end
# Backport of `reorder` method from Origin 2.1.0+
if defined?(Origin::Optional) && !Origin::Optional.method_defined?(:reorder)
module Origin
module Optional
# Instead of merging the order criteria, use this method to completely
# replace the existing ordering with the provided.
#
# @example Replace the ordering.
# optional.reorder(name: :asc)
#
# @param [ Array, Hash, String ] spec The sorting specification.
#
# @return [ Optional ] The cloned optional.
#
# @since 2.1.0
def reorder(*spec)
options.delete(:sort)
order_by(*spec)
end
end
end
end
module ByStar
VERSION = '4.0.0'
end
sqlite:
adapter: sqlite3
database: by_star.sqlite3
postgres:
adapter: postgresql
database: by_star_test
username: <%= ENV.fetch("USER") || "postgres" %>
min_messages: warning
mysql:
adapter: mysql2
database: by_star_test
username: root
encoding: utf8
class Post < ActiveRecord::Base
end
class Appointment < ActiveRecord::Base
by_star_field index_scope: ->(start){ start - 5.days }
end
class Event < ActiveRecord::Base
by_star_field :start_time, :end_time, offset: 3.hours
default_scope ->{ order('day_of_month ASC') }
end
ActiveRecord::Schema.define do
self.verbose = false
create_table :posts, force: true do |t|
t.timestamps
t.integer :day_of_month
end
create_table :events, force: true do |t|
t.timestamps
t.datetime :start_time, :end_time
t.integer :day_of_month
end
create_table :appointments, force: true do |t|
t.timestamps
t.integer :day_of_month
end
end
class Post
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::ByStar
field :day_of_month, type: Integer
end
class Appointment
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::ByStar
field :day_of_month, type: Integer
by_star_field index_scope: ->(start){ start - 5.days }
end
class Event
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::ByStar
field :st, as: :start_time, type: Time
field :end_time, type: Time
field :day_of_month, type: Integer
by_star_field :start_time, :end_time, offset: 3.hours
default_scope ->{ order_by(day_of_month: :asc) }
end
%w(2013-11-01
2013-11-30
2013-12-01
2013-12-05
2013-12-08
2013-12-16
2013-12-22
2013-12-25
2013-12-28
2013-12-31
2014-01-01
2014-01-01
2014-01-05
2014-01-10
2014-01-12
2014-01-20
2014-02-01
2014-02-15
2014-03-01
2014-03-15
2014-04-01
2014-04-15).map{|d| Time.zone.parse(d) + 17.hours }.each_with_index do |d, index|
Post.create!(created_at: d, updated_at: d + index.days, day_of_month: d.day)
Appointment.create!(created_at: d, day_of_month: d.day)
Event.create!(created_at: d, start_time: d - 5.days, end_time: d + 5.days, day_of_month: d.day)
end
# Sydney timezone specific records
%w(
2020-04-05
2020-10-04
).map{|d| Date.parse(d) }.each do |d|
[1, 4, 5, 10].each do |h|
Event.create!(start_time: d + h.hour, end_time: d + h.hour + 30.minutes, created_at: Date.parse('2011-01-01').in_time_zone)
end
end
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', github: 'rails', branch: "main"
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 3.2.0'
gem 'pg', '~> 0.11'
gem 'mongoid'
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 4.0.0'
gem 'pg', '~> 0.11'
gem 'mongoid'
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 4.1.0'
gem 'pg', '~> 0.11'
gem 'mongoid'
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 4.2.0'
gem 'pg', '~> 0.15'
gem 'mongoid'
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 5.0.0'
gem 'pg', '~> 0.18'
gem 'mongoid'
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 5.1.0'
gem 'pg', '~> 0.18'
gem 'mongoid'
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 5.2.0'
gem 'pg', '~> 0.18'
gem 'mongoid'
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 6.0'
gem 'pg'
gem 'mongoid'
source 'http://rubygems.org'
gemspec path: '../../'
gem 'activerecord', '~> 6.1'
gem 'pg'
gem 'mongoid'
require 'spec_helper'
Dir[File.dirname(__FILE__) + '/../shared/*.rb'].each {|file| require file }
describe ActiveRecord do
before(:all) do
ActiveRecord::Base.default_timezone = :utc
# ActiveRecord::Base.logger = Logger.new(STDOUT)
database_file = File.dirname(__FILE__) + '/../../database.yml'
parsed_config = ERB.new(File.read(database_file)).result
db_config = YAML.safe_load(parsed_config)
if db_config.has_key?('sqlite') && db_config['sqlite'].has_key?('database')
db_config['sqlite']['database'] = File.dirname(__FILE__) + '/../../tmp/' + db_config['sqlite']['database']
end
ActiveRecord::Base.configurations = db_config
ActiveRecord::Base.establish_connection(ENV['DB'].try(:to_sym) || :sqlite)
load File.dirname(__FILE__) + '/../../fixtures/active_record/schema.rb'
load File.dirname(__FILE__) + '/../../fixtures/active_record/models.rb'
load File.dirname(__FILE__) + '/../../fixtures/shared/seeds.rb'
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/../../tmp/activerecord.log')
end
it_behaves_like 'between_times'
it_behaves_like 'between_dates'
it_behaves_like 'at_time'
it_behaves_like 'offset parameter'
it_behaves_like 'order parameter'
it_behaves_like 'index_scope parameter'
it_behaves_like 'by day'
it_behaves_like 'by direction'
it_behaves_like 'by fortnight'
it_behaves_like 'by month'
it_behaves_like 'by calendar month'
it_behaves_like 'by quarter'
it_behaves_like 'by week'
it_behaves_like 'by cweek'
it_behaves_like 'by weekend'
it_behaves_like 'by year'
it_behaves_like 'relative'
end if testing_active_record?
require 'spec_helper'
Dir[File.dirname(__FILE__) + '/../shared/*.rb'].each {|file| require file }
describe 'Mongoid' do
before(:all) do
DATABASE_NAME = "mongoid_#{Process.pid}"
# Moped.logger = Logger.new(STDOUT)
Mongoid.configure do |config|
config.connect_to DATABASE_NAME
end
load File.dirname(__FILE__) + '/../../fixtures/mongoid/models.rb'
load File.dirname(__FILE__) + '/../../fixtures/shared/seeds.rb'
end
after(:all) do
Mongoid.purge!
end
it_behaves_like 'between_times'
it_behaves_like 'between_dates'
it_behaves_like 'at_time'
it_behaves_like 'offset parameter'
it_behaves_like 'order parameter'
it_behaves_like 'index_scope parameter'
it_behaves_like 'by day'
it_behaves_like 'by direction'
it_behaves_like 'by fortnight'
it_behaves_like 'by month'
it_behaves_like 'by calendar month'
it_behaves_like 'by quarter'
it_behaves_like 'by week'
it_behaves_like 'by cweek'
it_behaves_like 'by weekend'
it_behaves_like 'by year'
it_behaves_like 'relative'
end if testing_mongoid?
require 'spec_helper'
shared_examples_for 'at_time' do
describe '#at_time' do
context 'point object' do
context 'exactly equal' do
subject { Post.at_time(Time.zone.parse('2013-12-28 17:00:00')) }
it { expect(subject.count).to eql(1) }
end
context 'not exactly equal' do
subject { Post.at_time(Time.zone.parse('2013-12-28 17:00:01')) }
it { expect(subject.count).to eql(0) }
end
end
context 'timespan object' do
context 'before start time' do
subject { Event.at_time(Time.zone.parse('2013-12-23 16:59:59')) }
it { expect(subject.count).to eql(2) }
end
context 'at start time' do
subject { Event.at_time(Time.zone.parse('2013-12-23 17:00:00')) }
it { expect(subject.count).to eql(3) }
end
context 'after start time' do
subject { Event.at_time(Time.zone.parse('2013-12-23 17:00:01')) }
it { expect(subject.count).to eql(3) }
end
context 'before end time' do
subject { Event.at_time(Time.zone.parse('2013-11-06 16:59:59')) }
it { expect(subject.count).to eql(1) }
end
context 'at end time' do
subject { Event.at_time(Time.zone.parse('2013-11-06 17:00:00')) }
it { expect(subject.count).to eql(0) }
end
context 'after end time' do
subject { Event.at_time(Time.zone.parse('2013-11-06 17:00:01')) }
it { expect(subject.count).to eql(0) }
end
end
end
end
require 'spec_helper'
shared_examples_for 'between_dates' do
describe '#between_dates' do
subject { Post.between_dates(Time.zone.parse('2014-01-01'), Time.zone.parse('2014-01-06')) }
if testing_active_record?
it { is_expected.to be_a(ActiveRecord::Relation) }
else testing_mongoid?
it { is_expected.to be_a(Mongoid::Criteria) }
end
it { expect(subject.count).to eql(3) }
context 'one-sided query' do
context 'point query' do
context 'only start time' do
subject { Post.between_dates(Time.zone.parse('2014-01-01'), nil) }
it { expect(subject.count).to eql(12) }
end
context 'only end time' do
subject { Post.between_dates(nil, Time.zone.parse('2014-01-01')) }
it { expect(subject.count).to eql(12) }
context 'neither start nor end time' do
subject { Post.between_dates(nil, nil) }
it { expect(subject.count).to eql(22) }
end
end
end
context 'timespan loose query' do
context 'only start time' do
subject { Event.between_dates(Time.zone.parse('2014-01-01'), nil, strict: false) }
it { expect(subject.count).to eql(17) }
end
context 'only end time' do
subject { Event.between_dates(nil, Time.zone.parse('2014-01-01'), strict: false) }
it { expect(subject.count).to eql(13) }
context 'neither start nor end time' do
subject { Event.between_dates(nil, nil) }
it { expect(subject.count).to eql(30) }
end
end
end
context 'timespan strict query' do
context 'only start time' do
subject { Event.between_dates(Time.zone.parse('2014-01-01'), nil) }
it { expect(subject.count).to eql(17) }
end
context 'only end time' do
subject { Event.between_dates(nil, Time.zone.parse('2014-01-01')) }
it { expect(subject.count).to eql(13) }
context 'neither start nor end time' do
subject { Event.between_dates(nil, nil) }
it { expect(subject.count).to eql(30) }
end
end
end
end
context 'two-sided query' do
context 'DST starts (Sydney)', sydney: true do
context 'day before' do
subject { Event.between_dates(Date.parse('2020-04-04'), Date.parse('2020-04-04'), offset: 5.hours) }
it { expect(subject.count).to eql(3) }
end
context 'same day' do
subject { Event.between_dates(Date.parse('2020-04-05'), Date.parse('2020-04-05'), offset: 5.hours) }
it { expect(subject.count).to eql(1) }
end
end
context 'when DST ends (Sydney)', sydney: true do
context 'day before' do
subject { Event.between_dates(Date.parse('2020-10-03'), Date.parse('2020-10-03'), offset: 5.hours) }
it { expect(subject.count).to eql(1) }
end
context 'same day' do
subject { Event.between_dates(Date.parse('2020-10-04'), Date.parse('2020-10-04'), offset: 5.hours) }
it { expect(subject.count).to eql(3) }
end
end
end
end
end
require 'spec_helper'
shared_examples_for 'between_times' do
describe '#between_times' do
subject { Post.between_times(Time.zone.parse('2014-01-01'), Time.zone.parse('2014-01-06')) }
if testing_active_record?
it { is_expected.to be_a(ActiveRecord::Relation) }
else testing_mongoid?
it { is_expected.to be_a(Mongoid::Criteria) }
end
it { expect(subject.count).to eql(3) }
context 'one-sided query' do
context 'point query' do
context 'only start time' do
subject { Post.between_times(Time.zone.parse('2014-01-01'), nil) }
it { expect(subject.count).to eql(12) }
end
context 'only end time' do
subject { Post.between_times(nil, Time.zone.parse('2014-01-01')) }
it { expect(subject.count).to eql(10) }
context 'neither start nor end time' do
subject { Post.between_times(nil, nil) }
it { expect(subject.count).to eql(22) }
end
end
end
context 'timespan loose query' do
context 'only start time' do
subject { Event.between_times(Time.zone.parse('2014-01-01'), nil, strict: false) }
it { expect(subject.count).to eql(17) }
end
context 'only end time' do
subject { Event.between_times(nil, Time.zone.parse('2014-01-01'), strict: false) }
it { expect(subject.count).to eql(13) }
context 'neither start nor end time' do
subject { Event.between_times(nil, nil) }
it { expect(subject.count).to eql(30) }
end
end
end
context 'timespan strict query' do
context 'only start time' do
subject { Event.between_times(Time.zone.parse('2014-01-01'), nil) }
it { expect(subject.count).to eql(17) }
end
context 'only end time' do
subject { Event.between_times(nil, Time.zone.parse('2014-01-01')) }
it { expect(subject.count).to eql(13) }
context 'neither start nor end time' do
subject { Event.between_times(nil, nil) }
it { expect(subject.count).to eql(30) }
end
end
end
end
context 'two-sided query' do
context 'DST starts (Sydney)', sydney: true do
context 'day before' do
subject { Event.between_times(Date.parse('2020-04-04'), Date.parse('2020-04-04'), offset: 5.hours) }
it { expect(subject.count).to eql(3) }
end
context 'same day' do
subject { Event.between_times(Date.parse('2020-04-05'), Date.parse('2020-04-05'), offset: 5.hours) }
it { expect(subject.count).to eql(1) }
end
end
context 'when DST ends (Sydney)', sydney: true do
context 'day before' do
subject { Event.between_times(Date.parse('2020-10-03'), Date.parse('2020-10-03'), offset: 5.hours) }
it { expect(subject.count).to eql(1) }
end
context 'same day' do
subject { Event.between_times(Date.parse('2020-10-04'), Date.parse('2020-10-04'), offset: 5.hours) }
it { expect(subject.count).to eql(3) }
end
end
end
end
end
require 'spec_helper'
shared_examples_for 'by calendar month' do
describe '#by_calendar_month' do
context 'point-in-time' do
subject { Post.by_calendar_month('Feb') }
it { expect(subject.count).to eql(3) }
end
context 'timespan' do
subject { Event.by_calendar_month(1) }
it { expect(subject.count).to eql(10) }
end
context 'timespan strict' do
subject { Event.by_calendar_month(Date.parse('2014-02-01'), strict: true) }
it { expect(subject.count).to eql(2) }
end
context 'with :year option' do
context 'point-in-time' do
subject { Post.by_calendar_month(12, year: 2013) }
it { expect(subject.count).to eql(12) }
end
context 'timespan' do
subject { Event.by_calendar_month('December', year: 2013) }
it { expect(subject.count).to eql(13) }
end
context 'timespan strict' do
subject { Event.by_calendar_month('Dec', year: 2013, strict: true) }
it { expect(subject.count).to eql(9) }
end
end
it 'should raise an error when given an invalid argument' do
expect{ Post.by_calendar_month(0) }.to raise_error(ByStar::ParseError, 'Month must be a number between 1 and 12 or a month name')
expect{ Post.by_calendar_month(13) }.to raise_error(ByStar::ParseError, 'Month must be a number between 1 and 12 or a month name')
expect{ Post.by_calendar_month('foobar') }.to raise_error(ByStar::ParseError, 'Month must be a number between 1 and 12 or a month name')
end
it 'should be able to use an alternative field' do
expect(Event.by_calendar_month(field: 'end_time').count).to eql(9)
end
context ':start_day option' do
subject { Post.by_calendar_month(1, start_day: :wednesday) }
it{ expect(subject.count).to eql(7) }
end
end
end
require 'spec_helper'
shared_examples_for 'by cweek' do
describe '#by_cweek' do
context 'point-in-time' do
subject { Post.by_cweek('2014-01-02') }
it { expect(subject.count).to eql(4) }
end
context 'timespan' do
subject { Event.by_cweek(1) }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.by_cweek(Date.parse('2014-01-01'), strict: true) }
it { expect(subject.count).to eql(0) }
end
context 'with :year option' do
context 'point-in-time' do
subject { Post.by_cweek(53, year: 2013) }
it { expect(subject.count).to eql(4) }
end
context 'timespan' do
subject { Event.by_cweek(53, year: 2013) }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.by_cweek(53, year: 2013, strict: true) }
it { expect(subject.count).to eql(0) }
end
end
it 'should raise an error when given an invalid argument' do
expect { Post.by_cweek(0) }.to raise_error(ByStar::ParseError, 'cweek number must be between 1 and 53')
expect { Post.by_cweek(54) }.to raise_error(ByStar::ParseError, 'cweek number must be between 1 and 53')
end
it 'should be able to use an alternative field' do
expect(Event.by_cweek(field: 'end_time').count).to eq 3
end
context ':start_day option' do
subject { Post.by_cweek('2014-01-02', start_day: :thursday) }
it { expect(subject.count).to eql(1) }
end
end
end
require 'spec_helper'
shared_examples_for 'by day' do
describe '#by_day' do
context 'point-in-time' do
it { expect(Post.by_day('2014-01-01').count).to eql(2) }
end
context 'timespan' do
it { expect(Event.by_day(Time.zone.parse '2014-01-01').count).to eql(5) }
end
context 'timespan strict' do
it { expect(Event.by_day(Date.parse('2014-01-01'), strict: true).count).to eql(0) }
end
it 'should be able to use an alternative field' do
expect(Event.by_day(field: 'end_time').count).to eql(0)
end
it 'should support :offset option' do
expect(Post.by_day('2014-01-01', offset: -16.hours).count).to eq(1)
end
context 'when DST starts (Sydney)', sydney: true do
context 'day before' do
subject { Event.by_day('2020-04-04', offset: 5.hours) }
it { expect(subject.count).to eq(3) }
end
context 'same day' do
subject { Event.by_day('2020-04-05', offset: 5.hours) }
it { expect(subject.count).to eq(1) }
end
end
context 'when DST ends (Sydney)', sydney: true do
context 'day before' do
subject { Event.by_day('2020-10-03', offset: 5.hours) }
it { expect(subject.count).to eq(1) }
end
context 'same day' do
subject { Event.by_day('2020-10-04', offset: 5.hours) }
it { expect(subject.count).to eq(3) }
end
end
end
describe '#today' do # 2014-01-01
context 'point-in-time' do
it { expect(Post.today.count).to eql(2) }
end
context 'timespan' do
it { expect(Event.today.count).to eql(5) }
end
context 'timespan strict' do
it { expect(Event.today(strict: true).count).to eql(0) }
end
it 'should be able to use an alternative field' do
expect(Event.today(field: 'created_at').count).to eql(2)
end
it 'should support :offset option' do
expect(Post.today(offset: -24.hours).count).to eql(1)
end
end
describe '#yesterday' do # 2013-12-31
context 'point-in-time' do
it { expect(Post.yesterday.count).to eql(1) }
end
context 'timespan' do
it { expect(Event.yesterday.count).to eql(5) }
end
context 'timespan strict' do
it { expect(Event.yesterday(strict: true).count).to eql(0) }
end
it 'should be able to use an alternative field' do
expect(Event.yesterday(field: 'created_at').count).to eql(1)
end
it 'should support :offset option' do
expect(Post.yesterday(offset: 24.hours).count).to eql(2)
end
end
describe '#tomorrow' do # 2014-01-02
context 'point-in-time' do
it { expect(Post.tomorrow.count).to eql(0) }
end
context 'timespan' do
it { expect(Event.tomorrow.count).to eql(5) }
end
context 'timespan strict' do
it { expect(Event.tomorrow(strict: true).count).to eql(0) }
end
it 'should be able to use an alternative field' do
expect(Event.tomorrow(field: 'created_at').count).to eql(0)
end
it 'should support :offset option' do
expect(Post.tomorrow(offset: -24.hours).count).to eql(2)
end
end
end
require 'spec_helper'
shared_examples_for 'by direction' do
describe '#before' do
context 'point-in-time' do
subject { Post.before(Date.parse '2014-01-05') }
it { expect(subject.count).to eql(12) }
end
context 'timespan default' do
subject { Event.before(Time.zone.parse '2014-01-05') }
it { expect(subject.count).to eql(13) }
end
context 'timespan strict' do
subject { Event.before('2014-01-05', strict: true) }
it { expect(subject.count).to eql(13) }
end
context 'timespan not strict' do
subject { Event.before('2014-01-05', strict: false) }
it { expect(subject.count).to eql(13) }
end
context 'alternative field' do
subject { Event.before('2014-01-05', field: 'created_at') }
it { expect(subject.count).to eql(20) }
end
context 'with default scope' do
subject { Appointment.before('2014-01-05', field: 'created_at') }
it { expect(subject.count).to eql(12) }
end
end
describe '#after' do
context 'point-in-time' do
subject { Post.after('2014-01-05') }
it { expect(subject.count).to eql(10) }
end
context 'timespan default' do
subject { Event.after(Date.parse '2014-01-05') }
it { expect(subject.count).to eql(17) }
end
context 'timespan strict' do
subject { Event.after('2014-01-05', strict: true) }
it { expect(subject.count).to eql(17) }
end
context 'timespan not strict' do
subject { Event.after('2014-01-05', strict: false) }
it { expect(subject.count).to eql(17) }
end
context 'alternative field' do
subject { Event.after('2014-01-05', field: 'created_at') }
it { expect(subject.count).to eql(10) }
end
context 'with default scope' do
subject { Appointment.after('2014-01-05', field: 'created_at') }
it { expect(subject.count).to eql(10) }
end
end
describe '#oldest and #newest' do
context 'point-in-time' do
it { expect(Post.newest.created_at).to eq Time.zone.parse('2014-04-15 17:00:00') }
it { expect(Post.oldest.created_at).to eq Time.zone.parse('2013-11-01 17:00:00') }
end
context 'timespan' do
it { expect(Event.newest.created_at).to eq Time.zone.parse('2011-01-01 00:00:00') }
it { expect(Event.oldest.created_at).to eq Time.zone.parse('2013-11-01 17:00:00') }
end
context 'timespan strict' do
it { expect(Event.newest(strict: true).created_at).to eq Time.zone.parse('2011-01-01 00:00:00') }
it { expect(Event.oldest(strict: true).created_at).to eq Time.zone.parse('2013-11-01 17:00:00') }
end
context 'alternative field' do
it { expect(Event.newest(field: 'created_at').created_at).to eq Time.zone.parse('2014-04-15 17:00:00') }
it { expect(Event.oldest(field: 'created_at').created_at).to eq Time.zone.parse('2011-01-01 00:00:00') }
end
context 'with default scope' do
it { expect(Appointment.newest(field: 'created_at').created_at).to eq Time.zone.parse('2014-04-15 17:00:00') }
it { expect(Appointment.oldest(field: 'created_at').created_at).to eq Time.zone.parse('2013-11-01 17:00:00') }
end
end
describe '#previous and #next' do
context 'point-in-time' do
subject { Post.where(created_at: Time.zone.parse('2014-01-10 17:00:00')).first }
it{ expect(subject.previous.created_at).to eq Time.zone.parse('2014-01-05 17:00:00') }
it{ expect(subject.next.created_at).to eq Time.zone.parse('2014-01-12 17:00:00') }
end
context 'timespan' do
subject { Event.where(start_time: Time.zone.parse('2014-01-05 17:00:00')).first }
it{ expect(subject.previous.start_time).to eq Time.zone.parse('2013-12-31 17:00:00') }
it{ expect(subject.next.start_time).to eq Time.zone.parse('2014-01-07 17:00:00') }
end
context 'with default scope' do
subject { Appointment.where(created_at: Time.zone.parse('2014-01-05 17:00:00')).first }
it{ expect(subject.previous.created_at).to eq Time.zone.parse('2014-01-01 17:00:00') }
it{ expect(subject.next.created_at).to eq Time.zone.parse('2014-01-10 17:00:00') }
end
context 'specify a field' do
subject { Post.where(created_at: Time.zone.parse('2014-01-01 17:00:00')).first }
it{ expect(subject.previous.created_at).to eq Time.zone.parse('2013-12-31 17:00:00') }
it{ expect(subject.next.created_at).to eq Time.zone.parse('2014-01-05 17:00:00') }
it{ expect(subject.previous(field: 'updated_at').created_at).to eq Time.zone.parse('2013-12-31 17:00:00') }
it{ expect(subject.next(field: 'updated_at').created_at).to eq Time.zone.parse('2014-01-01 17:00:00') }
end
end
end
require 'spec_helper'
shared_examples_for 'by fortnight' do
describe '#by_fortnight' do
context 'point-in-time' do
subject { Post.by_fortnight('2014-01-01') }
it { expect(subject.count).to eql(5) }
end
context 'timespan' do
subject { Event.by_fortnight(0) }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.by_fortnight(Date.parse('2014-01-01'), strict: true) }
it { expect(subject.count).to eql(0) }
end
context 'with :year option' do
context 'point-in-time' do
subject { Post.by_fortnight(26, year: 2013) }
it { expect(subject.count).to eql(6) }
end
context 'timespan' do
subject { Event.by_fortnight(26, year: 2013) }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.by_fortnight(26, year: 2013, strict: true) }
it { expect(subject.count).to eql(1) }
end
end
it 'should raise an error when given an invalid argument' do
expect { Post.by_fortnight(27) }.to raise_error(ByStar::ParseError, 'Fortnight number must be between 0 and 26')
end
it 'should be able to use an alternative field' do
expect(Event.by_fortnight(field: 'end_time').count).to eq 5
end
end
end
require 'spec_helper'
shared_examples_for 'by month' do
describe '#by_month' do
context 'point-in-time' do
subject { Post.by_month('Feb') }
it { expect(subject.count).to eql(2) }
end
context 'timespan' do
subject { Event.by_month(1) }
it { expect(subject.count).to eql(9) }
end
context 'timespan strict' do
subject { Event.by_month(Date.parse('2014-02-01'), strict: true) }
it { expect(subject.count).to eql(1) }
end
context 'with :year option' do
context 'point-in-time' do
subject { Post.by_month(12, year: 2013) }
it { expect(subject.count).to eql(8) }
end
context 'timespan' do
subject { Event.by_month('December', year: 2013) }
it { expect(subject.count).to eql(12) }
end
context 'timespan strict' do
subject { Event.by_month('Dec', year: 2013, strict: true) }
it { expect(subject.count).to eql(4) }
end
end
it 'should raise an error when given an invalid argument' do
expect{ Post.by_month(0) }.to raise_error(ByStar::ParseError, 'Month must be a number between 1 and 12 or a month name')
expect{ Post.by_month(13) }.to raise_error(ByStar::ParseError, 'Month must be a number between 1 and 12 or a month name')
expect{ Post.by_month('foobar') }.to raise_error(ByStar::ParseError, 'Month must be a number between 1 and 12 or a month name')
end
it 'should be able to use an alternative field' do
expect(Event.by_month(field: 'end_time').count).to eq 8
end
end
end
require 'spec_helper'
shared_examples_for 'by quarter' do
describe '#by_quarter' do
context 'point-in-time' do
subject { Post.by_quarter(2) }
it { expect(subject.count).to eql(2) }
end
context 'timespan' do
subject { Event.by_quarter(1) }
it { expect(subject.count).to eql(13) }
end
context 'timespan strict' do
subject { Event.by_quarter(Date.parse('2014-02-01'), strict: true) }
it { expect(subject.count).to eql(7) }
end
context 'with :year option' do
context 'point-in-time' do
subject { Post.by_quarter(4, year: 2013) }
it { expect(subject.count).to eql(10) }
end
context 'timespan' do
subject { Event.by_quarter(4, year: 2013) }
it { expect(subject.count).to eql(13) }
end
context 'timespan strict' do
subject { Event.by_quarter(4, year: 2013, strict: true) }
it { expect(subject.count).to eql(8) }
end
end
it 'should raise an error when given an invalid argument' do
expect{ Post.by_quarter(0) }.to raise_error(ByStar::ParseError, 'Quarter number must be between 1 and 4')
expect{ Post.by_quarter(5) }.to raise_error(ByStar::ParseError, 'Quarter number must be between 1 and 4')
end
it 'should be able to use an alternative field' do
expect(Event.by_quarter(1, field: 'end_time').count).to eq 12
end
end
end
require 'spec_helper'
shared_examples_for 'by week' do
describe '#by_week' do
context 'point-in-time' do
subject { Post.by_week('2014-01-02') }
it { expect(subject.count).to eql(4) }
end
context 'timespan' do
subject { Event.by_week(0) }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.by_week(Date.parse('2014-01-01'), strict: true) }
it { expect(subject.count).to eql(0) }
end
context 'with :year option' do
context 'point-in-time' do
subject { Post.by_week(52, year: 2013) }
it { expect(subject.count).to eql(4) }
end
context 'timespan' do
subject { Event.by_week(52, year: 2013) }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.by_week(52, year: 2013, strict: true) }
it { expect(subject.count).to eql(0) }
end
end
it 'should raise an error when given an invalid argument' do
expect { Post.by_week(-1) }.to raise_error(ByStar::ParseError, 'Week number must be between 0 and 52')
expect { Post.by_week(53) }.to raise_error(ByStar::ParseError, 'Week number must be between 0 and 52')
end
it 'should be able to use an alternative field' do
expect(Event.by_week(field: 'end_time').count).to eq 3
end
context ':start_day option' do
subject { Post.by_week('2014-01-02', start_day: :thursday) }
it { expect(subject.count).to eql(1) }
end
end
end
require 'spec_helper'
shared_examples_for 'by weekend' do
describe '#by_weekend' do
context 'point-in-time' do
subject { Post.by_weekend('2014-01-01') }
it { expect(subject.count).to eql(1) }
end
context 'timespan' do
subject { Event.by_weekend(0) }
it { expect(subject.count).to eql(5) }
end
context 'timespan strict' do
subject { Event.by_weekend(Date.parse('2014-01-01'), strict: true) }
it { expect(subject.count).to eql(0) }
end
context 'with :year option' do
context 'point-in-time' do
subject { Post.by_weekend(52, year: 2013) }
it { expect(subject.count).to eql(1) }
end
context 'timespan' do
subject { Event.by_weekend(52, year: 2013) }
it { expect(subject.count).to eql(5) }
end
context 'timespan strict' do
subject { Event.by_weekend(52, year: 2013, strict: true) }
it { expect(subject.count).to eql(0) }
end
end
it 'should raise an error when given an invalid argument' do
expect { Post.by_weekend(-1) }.to raise_error(ByStar::ParseError, 'Week number must be between 0 and 52')
expect { Post.by_weekend(53) }.to raise_error(ByStar::ParseError, 'Week number must be between 0 and 52')
end
it 'should be able to use an alternative field' do
expect(Event.by_weekend(field: 'end_time').count).to eq 1
end
end
end
require 'spec_helper'
shared_examples_for 'by year' do
describe '#by_year' do
context 'point-in-time' do
subject { Post.by_year('2014') }
it { expect(subject.count).to eql(12) }
end
context 'timespan' do
subject { Event.by_year(13) }
it { expect(subject.count).to eql(13) }
end
context 'timespan strict' do
subject { Event.by_year(Date.parse('2014-02-01'), strict: true) }
it { expect(subject.count).to eql(9) }
end
context 'integer' do
context 'point-in-time' do
subject { Post.by_year(2013) }
it { expect(subject.count).to eql(10) }
end
context 'timespan' do
subject { Event.by_year(2014) }
it { expect(subject.count).to eql(14) }
end
context 'timespan strict' do
subject { Event.by_year(2013, strict: true) }
it { expect(subject.count).to eql(8) }
end
end
it 'should be able to use an alternative field' do
expect(Event.by_year(field: 'end_time').count).to eq 14
end
it 'can use a 2-digit year' do
expect(Post.by_year(13).count).to eq 10
end
end
end
require 'spec_helper'
shared_examples_for 'index_scope parameter' do
describe ':scope' do
it 'should memoize the scope variable' do
expect(Event.instance_variable_get(:@by_star_index_scope)).to be_nil
expect(Post.instance_variable_get(:@by_star_index_scope)).to be_nil
expect(Appointment.instance_variable_get(:@by_star_index_scope)).to be_a Proc
end
context 'between_times with index_scope' do
context 'nil' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: nil) }
it { expect(subject.count).to eql(16) }
end
context 'false' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: false) }
it { expect(subject.count).to eql(16) }
end
context 'Time' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: Time.zone.parse('2013-11-30 17:00:00')) }
it { expect(subject.count).to eql(14) }
end
context 'DateTime' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: Time.zone.parse('2013-11-30 17:00:00').to_datetime) }
it { expect(subject.count).to eql(14) }
end
context 'Date' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: Date.parse('2013-11-30')) }
it { expect(subject.count).to eql(14) }
end
context 'ActiveSupport::Duration' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: 3.hours) }
it { expect(subject.count).to eql(13) }
end
context 'Numeric' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: 3600) }
it { expect(subject.count).to eql(13) }
end
context ':beginning_of_day' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: :beginning_of_day) }
it { expect(subject.count).to eql(13) }
end
context 'unsupported type' do
subject { Event.between_times(Date.parse('2013-12-01'), Date.parse('2014-01-31'), index_scope: Integer) }
it { expect{subject.count}.to raise_error(RuntimeError) }
end
end
context 'at_time with index_scope' do
context 'nil' do
subject { Event.at_time(Time.zone.parse('2013-12-01 14:00'), index_scope: nil) }
it { expect(subject.count).to eql(3) }
end
context 'false' do
subject { Event.at_time(Time.zone.parse('2013-12-01 14:00'), index_scope: false) }
it { expect(subject.count).to eql(3) }
end
context 'Time' do
subject { Event.at_time(Time.zone.parse('2013-12-01 14:00'), index_scope: Time.zone.parse('2013-10-30 17:00:00')) }
it { expect(subject.count).to eql(3) }
end
context 'DateTime' do
subject { Event.at_time(Time.zone.parse('2013-12-01 14:00'), index_scope: Time.zone.parse('2013-11-30 17:00:00').to_datetime) }
it { expect(subject.count).to eql(1) }
end
context 'Date' do
subject { Event.at_time(Time.zone.parse('2013-12-01 14:00'), index_scope: Date.parse('2013-11-30')) }
it { expect(subject.count).to eql(1) }
end
context 'ActiveSupport::Duration' do
subject { Event.at_time(Time.zone.parse('2013-12-01 14:00'), index_scope: 100.hours) }
it { expect(subject.count).to eql(1) }
end
context 'Numeric' do
subject { Event.at_time(Time.zone.parse('2013-12-01 14:00'), index_scope: 60 * 60 * 1000) }
it { expect(subject.count).to eql(3) }
end
context ':beginning_of_day' do
let!(:custom_event){ t = Time.zone.parse('2013-12-30 17:00'); Event.create!(start_time: t - 1.hour, end_time: t + 1.hour) }
subject { Event.at_time(Time.zone.parse('2013-12-30 16:00'), index_scope: :beginning_of_day) }
it { expect(subject.count).to eql(1) }
after { custom_event.delete }
end
context 'unsupported type' do
subject { Event.at_time(Time.zone.parse('2013-12-01 14:00'), index_scope: Integer) }
it { expect{subject.count}.to raise_error(RuntimeError) }
end
end
end
end
require 'spec_helper'
shared_examples_for 'offset parameter' do
describe ':offset' do
it 'should memoize the offset variable' do
expect(Event.instance_variable_get(:@by_star_offset)).to eq 3.hours
expect(Post.instance_variable_get(:@by_star_offset)).to be_nil
end
context 'between_times with default offset' do
subject { Event.between_times(Time.zone.parse('2014-01-01'), Time.zone.parse('2014-01-10')) }
it { expect(subject.count).to eql(7) }
end
context 'between_times with offset override' do
subject { Event.between_times(Time.zone.parse('2014-01-01')..Time.zone.parse('2014-01-10'), offset: 16.hours) }
it { expect(subject.count).to eql(7) }
end
context 'by_day with default offset' do
subject { Event.by_day(Time.zone.parse('2014-01-01')) }
it { expect(subject.count).to eql(5) }
end
context 'by_day with offset override' do
subject { Event.by_day(Time.zone.parse('2014-12-26'), field: :start_time, offset: 5.hours) }
it { expect(subject.count).to eql(0) }
end
end
end
require 'spec_helper'
shared_examples_for 'order parameter' do
describe ':order' do
if testing_active_record?
it 'should be able to order the result set asc' do
scope = Post.by_year(Time.zone.now.year, order: 'created_at ASC')
expect(scope.order_values).to eq ['created_at ASC']
expect(scope.first.created_at).to eq Time.zone.parse('2014-01-01 17:00:00')
end
it 'should be able to order the result set desc' do
scope = Post.by_year(Time.zone.now.year, order: 'created_at DESC')
expect(scope.order_values).to eq ['created_at DESC']
expect(scope.first.created_at).to eq Time.zone.parse('2014-04-15 17:00:00')
end
elsif testing_mongoid?
it 'should be able to order the result set asc' do
scope = Post.by_year(Time.zone.now.year, order: {created_at: :asc})
expect(scope.options[:sort]).to eq({'created_at' => 1})
expect(scope.first.created_at).to eq Time.zone.parse('2014-01-01 17:00:00')
end
it 'should be able to order the result set desc' do
scope = Post.by_year(Time.zone.now.year, order: {created_at: :desc})
expect(scope.options[:sort]).to eq({'created_at' => -1})
expect(scope.first.created_at).to eq Time.zone.parse('2014-04-15 17:00:00')
end
end
end
end
require 'spec_helper'
shared_examples_for 'relative' do
describe '#past_day' do
context 'point-in-time' do
subject { Post.past_day }
it { expect(subject.count).to eql(1) }
end
context 'timespan' do
subject { Event.past_day }
it { expect(subject.count).to eql(5) }
end
context 'timespan strict' do
subject { Event.past_day(strict: true) }
it { expect(subject.count).to eql(0) }
end
end
describe '#past_week' do
context 'point-in-time' do
subject { Post.past_week }
it { expect(subject.count).to eql(3) }
end
context 'timespan' do
subject { Event.past_week }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.past_week(strict: true) }
it { expect(subject.count).to eql(0) }
end
end
describe '#past_fortnight' do
context 'point-in-time' do
subject { Post.past_fortnight }
it { expect(subject.count).to eql(4) }
end
context 'timespan' do
subject { Event.past_fortnight }
it { expect(subject.count).to eql(8) }
end
context 'timespan strict' do
subject { Event.past_fortnight(strict: true) }
it { expect(subject.count).to eql(1) }
end
end
describe '#past_month' do
context 'point-in-time' do
subject { Post.past_month }
it { expect(subject.count).to eql(8) }
end
context 'timespan' do
subject { Event.past_month }
it { expect(subject.count).to eql(12) }
end
context 'timespan strict' do
subject { Event.past_month(strict: true) }
it { expect(subject.count).to eql(4) }
end
end
describe '#past_year' do
context 'point-in-time' do
subject { Post.past_year }
it { expect(subject.count).to eql(10) }
end
context 'timespan' do
subject { Event.past_year }
it { expect(subject.count).to eql(13) }
end
context 'timespan strict' do
subject { Event.past_year(strict: true) }
it { expect(subject.count).to eql(8) }
end
end
describe '#next_day' do
context 'point-in-time' do
subject { Post.next_day }
it { expect(subject.count).to eql(2) }
end
context 'timespan' do
subject { Event.next_day }
it { expect(subject.count).to eql(5) }
end
context 'timespan strict' do
subject { Event.next_day(strict: true) }
it { expect(subject.count).to eql(0) }
end
end
describe '#next_week' do
context 'point-in-time' do
subject { Post.next_week }
it { expect(subject.count).to eql(3) }
end
context 'timespan' do
subject { Event.next_week }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.next_week(strict: true) }
it { expect(subject.count).to eql(0) }
end
end
describe '#next_fortnight' do
context 'point-in-time' do
subject { Post.next_fortnight }
it { expect(subject.count).to eql(5) }
end
context 'timespan' do
subject { Event.next_fortnight }
it { expect(subject.count).to eql(7) }
end
context 'timespan strict' do
subject { Event.next_fortnight(strict: true) }
it { expect(subject.count).to eql(0) }
end
end
describe '#next_month' do
context 'point-in-time' do
subject { Post.next_month }
it { expect(subject.count).to eql(6) }
end
context 'timespan' do
subject { Event.next_month }
it { expect(subject.count).to eql(9) }
end
context 'timespan strict' do
subject { Event.next_month(strict: true) }
it { expect(subject.count).to eql(3) }
end
end
describe '#next_year' do
context 'point-in-time' do
subject { Post.next_year }
it { expect(subject.count).to eql(12) }
end
context 'timespan' do
subject { Event.next_year }
it { expect(subject.count).to eql(14) }
end
context 'timespan strict' do
subject { Event.next_year(strict: true) }
it { expect(subject.count).to eql(9) }
end
end
end
require 'rubygems'
require 'bundler'
Bundler.setup
require 'fileutils'
require 'logger'
FileUtils.mkdir_p(File.dirname(__FILE__) + "/tmp")
$:.unshift(File.join(File.dirname(__FILE__), "../lib"))
require 'active_record'
require 'mongoid'
require 'chronic'
require 'timecop'
require 'by_star'
# Specs should pass regardless of timezone
Time.zone = %w(Asia/Tokyo America/New_York Australia/Sydney UTC).sample
puts "Running specs in #{Time.zone} timezone..."
# Set Rails time to 2014-01-01 00:00:00
Timecop.travel(Time.zone.local(2014))
RSpec.configure do |c|
c.filter_run_excluding sydney: true unless Time.zone.name == 'Australia/Sydney'
end
def testing_mongoid?
ENV['DB'] == 'mongodb'
end
def testing_active_record?
!testing_mongoid?
end
require 'spec_helper'
describe Date do
describe '#in_time_zone' do
subject { Date.new }
before do
stub_const('Date', Class.new)
allow_any_instance_of(Date).to receive(:to_time_in_current_zone)
end
context 'when #in_time_zone is already defined' do
before do
expect_any_instance_of(Date).to receive(:in_time_zone)
end
context 'when ByStar::Kernel::InTimeZone included' do
before do
::Date.__send__(:include, ByStar::Kernel::InTimeZone)
end
it 'should not be aliased to #to_time_in_current_zone' do
expect(subject).not_to receive(:to_time_in_current_zone)
subject.in_time_zone
end
end
context 'when ByStar::Kernel::InTimeZone not included' do
it 'should not be aliased to #to_time_in_current_zone' do
expect(subject).not_to receive(:to_time_in_current_zone)
subject.in_time_zone
end
end
end
context 'when #in_time_zone is not defined' do
context 'when ByStar::Kernel::InTimeZone included' do
before do
::Date.__send__(:include, ByStar::Kernel::InTimeZone)
end
it 'should be aliased to #to_time_in_current_zone' do
expect(subject).to receive(:to_time_in_current_zone)
subject.in_time_zone
end
end
context 'when ByStar::Kernel::InTimeZone not included' do
it 'should raise NoMethodError' do
expect{ subject.in_time_zone }.to raise_error(NoMethodError)
end
end
end
end
describe 'weekend' do
(0..6).each do |n|
context "Monday plus #{n} days" do
subject { Date.parse('2014-01-06') + n.days }
it { expect(subject.beginning_of_weekend).to eq Date.parse('2014-01-11') }
it { expect(subject.end_of_weekend).to eq Date.parse('2014-01-12') }
end
end
end
describe 'fortnight' do
context 'first day of year' do
subject { Date.parse '2014-01-01' }
it { expect(subject.beginning_of_fortnight).to eq Date.parse('2014-01-01') }
it { expect(subject.end_of_fortnight).to eq Date.parse('2014-01-14') }
end
context 'second fortnight of year' do
subject { Date.parse '2014-01-16' }
it { expect(subject.beginning_of_fortnight).to eq Date.parse('2014-01-15') }
it { expect(subject.end_of_fortnight).to eq Date.parse('2014-01-28') }
end
context 'middle of year' do
subject { Date.parse '2014-06-13' }
it { expect(subject.beginning_of_fortnight).to eq Date.parse('2014-06-04') }
it { expect(subject.end_of_fortnight).to eq Date.parse('2014-06-17') }
end
context 'last day of year' do
subject { Date.parse '2014-12-31' }
it { expect(subject.beginning_of_fortnight).to eq Date.parse('2014-12-31') }
it { expect(subject.end_of_fortnight).to eq Date.parse('2015-01-13') }
end
end
describe 'calendar_month' do
subject { Date.parse '2014-01-01' }
context 'week begins Monday' do
it { expect(subject.beginning_of_calendar_month).to eq Date.parse('2013-12-30') }
it { expect(subject.end_of_calendar_month).to eq Date.parse('2014-02-02') }
end
context 'week begins Sunday' do
it { expect(subject.beginning_of_calendar_month(:sunday)).to eq Date.parse('2013-12-29') }
it { expect(subject.end_of_calendar_month(:sunday)).to eq Date.parse('2014-02-01') }
end
end
end
require 'spec_helper'
describe Time do
describe 'weekend' do
(0..6).each do |n|
context "Monday plus #{n} days" do
subject { Time.zone.parse('2014-01-06') + n.days }
it { expect(subject.beginning_of_weekend).to eq Time.zone.parse('2014-01-11') }
it { expect(subject.end_of_weekend).to eq Time.zone.parse('2014-01-12').end_of_day }
end
end
end
describe 'fortnight' do
context 'first day of year' do
subject { Time.zone.parse '2014-01-01' }
it { expect(subject.beginning_of_fortnight).to eq Time.zone.parse('2014-01-01') }
it { expect(subject.end_of_fortnight).to eq Time.zone.parse('2014-01-14').end_of_day }
end
context 'second fortnight of year' do
subject { Time.zone.parse '2014-01-16' }
it { expect(subject.beginning_of_fortnight).to eq Time.zone.parse('2014-01-15') }
it { expect(subject.end_of_fortnight).to eq Time.zone.parse('2014-01-28').end_of_day }
end
context 'middle of year' do
subject { Time.zone.parse '2014-06-13' }
it { expect(subject.beginning_of_fortnight).to eq Time.zone.parse('2014-06-04') }
it { expect(subject.end_of_fortnight).to eq Time.zone.parse('2014-06-17').end_of_day }
end
context 'last day of year' do
subject { Time.zone.parse '2014-12-31' }
it { expect(subject.beginning_of_fortnight).to eq Time.zone.parse('2014-12-31') }
it { expect(subject.end_of_fortnight).to eq Time.zone.parse('2015-01-13').end_of_day }
end
end
describe 'calendar_month' do
subject { Time.zone.parse '2014-01-01' }
context 'week begins Monday' do
it { expect(subject.beginning_of_calendar_month).to eq Time.zone.parse('2013-12-30') }
it { expect(subject.end_of_calendar_month).to eq Time.zone.parse('2014-02-02').end_of_day }
end
context 'week begins Sunday' do
it { expect(subject.beginning_of_calendar_month(:sunday)).to eq Time.zone.parse('2013-12-29') }
it { expect(subject.end_of_calendar_month(:sunday)).to eq Time.zone.parse('2014-02-01').end_of_day }
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment