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.
# ByStar
[![Build Status](https://travis-ci.org/radar/by_star.svg)](https://travis-ci.org/radar/by_star)
[![Code Climate](https://codeclimate.com/github/radar/by_star.svg)](https://codeclimate.com/github/radar/by_star)
ByStar (by_*) allows you easily and reliably query ActiveRecord and Mongoid objects based on time.
### Examples
```ruby
Post.by_year(2013) # all posts in 2013
Post.before(Date.today) # all posts for before today
Post.yesterday # all posts for yesterday
Post.between_times(Time.zone.now - 3.hours, # all posts in last 3 hours
Time.zone.now)
@post.next # next post after a given post
```
## Installation
Install this gem by adding this to your Gemfile:
```ruby
gem 'by_star', git: 'https://github.com/radar/by_star'
```
Then run `bundle install`
If you are using ActiveRecord, you're done!
Mongoid users, please include the Mongoid::ByStar module for each model you wish to use the functionality.
This is the convention among Mongoid plugins.
```ruby
class MyModel
include Mongoid::Document
include Mongoid::ByStar
```
## Finder Methods
### Base Scopes
ByStar adds the following finder scopes (class methods) to your model to query time ranges.
These accept a `Date`, `Time`, or `DateTime` object as an argument, which defaults to `Time.zone.now` if not specified:
| Scope | Meaning |
| --- | --- |
| `between_times(start_time, end_time)` | Finds all records occurring between two given times. |
| `between_dates(start_date, end_date)` | Finds all records occurring between two given dates, from beginning of start_date until end of end_date. |
| `before(end_time)` | Finds all records occurring before the given time. |
| `after(start_time)` | Finds all records occurring after the given time. |
| `at_time(time)` | Finds all records occurring exactly at the given time, or which overlap the time in the case of "timespan"-type object (see below) |
`between_times` and `between_dates` supports alternate argument forms:
* `between_times(Range)`
* `between_times(Array)`
* `between_times(start_time, nil)` - same as `after(start_time)`
* `between_times(nil, end_time)` - same as `before(end_time)`
### Time Range Scopes
ByStar adds additional shortcut scopes based on commonly used time ranges.
See sections below for detailed argument usage of each:
| Scope | Meaning |
| --- | --- |
| `by_day` | Query by a given date. |
| `by_week` | Allows zero-based week value from 0 to 52. |
| `by_cweek` | Allows one-based week value from 1 to 53. |
| `by_weekend` | Saturday and Sunday only of the given week. |
| `by_fortnight` | A two-week period, with the first fortnight of the year beginning on 1st January. |
| `by_month` | Query by month. Allows integer arg, e.g. `11` for November. |
| `by_calendar_month` | Month as it appears on a calendar; days form previous/following months which are part of the first/last weeks of the given month. |
| `by_quarter` | 3-month intervals of the year. |
| `by_year` | Query by year. Allows integer arg, e.g. `2017`. |
### Relative Scopes
ByStar also adds scopes which are relative to the current time.
Note the `past_*` and `next_*` methods represent a time distance from current time (`Time.zone.now`),
and do not strictly end/begin evenly on a calendar week/month/year (unlike `by_*` methods which do.)
| Scope | Meaning |
| --- | --- |
| `today` | Finds all occurrences on today's date. |
| `yesterday` | Finds all occurrences on yesterday's date. |
| `tomorrow` | Finds all occurrences on tomorrow's date. |
| `past_day` | Prior 24-hour period from current time. |
| `past_week` | Prior 7-day period from current time. |
| `past_fortnight` | Prior 14-day period from current time. |
| `past_month` | Prior 30-day period from current time. |
| `past_year` | Prior 365-day period from current time. |
| `next_day` | Subsequent 24-hour period from current time. |
| `next_week` | Subsequent 7-day period from current time. |
| `next_fortnight` | Subsequent 14-day period from current time. |
| `next_month` | Subsequent 30-day period from current time. |
| `next_year` | Subsequent 365-day period from current time. |
### Superlative Finders
Find the oldest or newest records. Returns an object instance (not a relation):
* `newest`
* `oldest`
### Instance Methods
In addition, ByStar adds instance methods to return the next / previous record in the timewise sequence.
Returns an object instance (not a relation):
* `object.next`
* `object.previous`
### Kernel Extensions
ByStar extends the kernel `Date`, `Time`, and `DateTime` objects with the following instance methods,
which mirror the ActiveSupport methods `beginning_of_day`, `end_of_week`, etc:
* `beginning_of_weekend`
* `end_of_weekend`
* `beginning_of_fortnight`
* `end_of_fortnight`
* `beginning_of_calendar_month`
* `end_of_calendar_month`
Lastly, ByStar aliases Rails 3 `Date#to_time_in_current_zone` to the Rails 4 syntax `#in_time_zone`, if it has not already been defined.
## Usage
### Setting the Query Field
By default, ByStar assumes you will use the `created_at` field to query objects by time.
You may specify an alternate field on all query methods as follows:
```ruby
Post.by_month("January", field: :updated_at)
```
Alternatively, you may set a default in your model using the `by_star_field` macro:
```ruby
class Post < ActiveRecord::Base
by_star_field :updated_at
end
```
### Scoping the Query
All ByStar methods (except `oldest`, `newest`, `previous`, `next`) return an `ActiveRecord::Relation`
(or `Mongoid::Criteria`) which can be daisy-chained with other scopes/finder methods:
```ruby
Post.by_month.your_scope
Post.by_month(1).include(:tags).where("tags.name" => "ruby")
```
Want to count records? Simple:
```ruby
Post.by_month.count
```
### Timezone Handling
ByStar date-range finders will use value of `Time.zone` to evaluate the args.
This may cause unexpected behavior when use Time values in timezones other than `Time.zone`.
```ruby
Time.zone = 'Australia/Sydney'
Post.by_day('2020-04-05 18:00:00 EST')
#=> Returns Apr 6th, 0:00 until Apr 6th, 23:59 in Sydney timezone.
```
### `:offset` Option
All ByStar finders support an `:offset` option which is applied to time period of the query condition.
This is useful in cases where the daily cycle occurs at a time other than midnight.
For example, if you'd like to find all Posts from 9:00 on 2014-03-05 until 8:59:59.999 on 2014-03-06, you can do:
```ruby
Post.by_day('2014-03-05', offset: 9.hours)
```
**Note:** When passing `offset` in date finders, it will set the hour, minute, and second on the queried date in order to properly handle DST transitions. Example:
```ruby
Time.zone = 'Australia/Sydney'
Post.by_day('2020-04-05', offset: 9.hours)
#=> Returns Apr 5th, 09:00 until Apr 6th, 08:59
```
### Timespan Objects
If your object has both a start and end time, you may pass both params to `by_star_field`:
```ruby
by_star_field :start_time, :end_time
```
By default, ByStar queries will return all objects whose range has any overlap within the desired period (permissive):
```ruby
MultiDayEvent.by_month("January")
#=> returns MultiDayEvents that overlap in January,
# even if they start in December and/or end in February
```
### Timespan Objects: `#at_time`
To find all instances of a timespan object which contain a specific time:
```ruby
Post.at_time(time)
```
This can be useful to find all currently active instances. Note that object instances which start
exactly at the given `time` will be included in the result, but instances that end exactly at the given
`time` will not be.
### Timespan Objects: `:strict` Option
If you'd like to confine results to only those both starting and ending within the given range, use the `:strict` option:
```ruby
MultiDayEvent.by_month("January", :strict => true)
#=> returns MultiDayEvents that both start AND end in January
```
### Timespan Objects: Database Indexing and `:index_scope` Option
In order to ensure query performance on large dataset, you must add an index to the query field (e.g. "created_at") be indexed. ByStar does **not** define indexes automatically.
Database indexes require querying a range query on a single field, i.e. `start_time >= X and start_time <= Y`.
If we use a single-sided query, the database will iterate through all items either from the beginning or until the end of time.
This poses a challenge for timespan-type objects which have two fields, i.e. `start_time` and `end_time`.
There are two cases to consider:
1) Timespan with `:strict` option, e.g. `start_time >= X and end_time <= Y`.
Given that this gem requires `start_time >= end_time`, we add the converse constraint `start_time <= Y and end_time >= X`
to ensure both fields are double-sided, i.e. an index can be used on either field.
2) Timespan without `:strict` option, e.g. "start_time < Y and end_time > X".
Here we need to add a condition `start_time >= X` to ensure `start_time` is bounded on both sides.
To achieve this, we allow an `:index_scope` option which is the minimum "strict" bound on the querying range,
in other words, it is an assumption about the maximum timespan of objects.
`:index_scope` supports multiple value types:
| `:index_scope` Value | Meaning |
| --- | --- |
| `nil` or `false` | No constraint set; query will be one-sided (default, but not recommended) |
| `Date` or `Time`, etc. | A fixed point in time |
| `ActiveSupport::Duration` (e.g. `1.month`) | The duration value will be subtracted from the start of the range. In other words, a value of `1.month` would imply the longest possible object in the database is no longer than `1.month`. |
| `Numeric` | Will be converted to seconds, then handled the same as `ActiveSupport::Duration` |
| `:beginning_of_day` (`Symbol` literal) |
| `Proc<Range, Hash(options)>` | A proc which evaluates to one of the above types. Args are `(start_time, end_time, options)` |
An example settings of `:index_scope`:
```
# The maximum possible object length is 5 hours.
by_star index_scope: 5.hours
# Objects are guaranteed to start within the same month, with some offset.
by_star index_scope: ->(start_time, end_time, options){ start_time.beginning_of_month + (options[:offset] || 0) }
# The maximum possible object length half the range being queried.
by_star index_scope: ->(start_time, end_time, options){ ((start_time - end_time)*0.5).seconds }
```
### Chronic Support
If [Chronic](https://github.com/mojombo/chronic) gem is present, it will be used to parse natural-language date/time
strings in all ByStar finder methods. Otherwise, the Ruby `Time.parse` kernel method will be used as a fallback.
As of ByStar 2.2.0, you must explicitly include `gem 'chronic'` into your Gemfile in order to use Chronic.
## Advanced Usage
### between_times
To find records between two times:
```ruby
Post.between_times(time1, time2)
```
You use a Range like so:
```ruby
Post.between_times(time1..time2)
```
Also works with dates - WARNING: there are currently some caveats see [Issue #49](https://github.com/radar/by_star/issues/49):
```ruby
Post.between_times(date1, date2)
```
It will query records from `date1` (00:00:00 Hrs) until `date2` (23:59:59 Hrs).
### before and after
To find all posts before / after the current time:
```ruby
Post.before
Post.after
```
To find all posts before certain time or date:
```ruby
Post.before(Date.today + 2)
Post.after(Time.now + 5.days)
```
You can also pass a string:
```ruby
Post.before("next tuesday")
```
For Time-Range type objects, only the start time is considered for `before` and `after`.
### previous and next
To find the prior/subsequent record to a model instance, `previous`/`next` on it:
```ruby
Post.last.previous
Post.first.next
```
You can specify a field also:
```ruby
Post.last.previous(field: "published_at")
Post.first.next(field: "published_at")
```
For Time-Range type objects, only the start time is considered for `previous` and `next`.
### by_year
To find records from the current year, simply call the method without any arguments:
```ruby
Post.by_year
```
To find records based on a year you can pass it a two or four digit number:
```ruby
Post.by_year(09)
```
This will return all posts in 2009, whereas:
```ruby
Post.by_year(99)
```
will return all the posts in the year 1999.
You can also specify the full year:
```ruby
Post.by_year(2009)
Post.by_year(1999)
```
### by_month
If you know the number of the month you want:
```ruby
Post.by_month(1)
```
This will return all posts in the first month (January) of the current year.
If you like being verbose:
```ruby
Post.by_month("January")
```
This will return all posts created in January of the current year.
If you want to find all posts in January of last year just do
```ruby
Post.by_month(1, year: 2007)
```
or
```ruby
Post.by_month("January", year: 2007)
```
This will perform a find using the column you've specified.
If you have a Time object you can use it to find the posts:
```ruby
Post.by_month(Time.local(2012, 11, 24))
```
This will find all the posts in November 2012.
### by_calendar_month
Finds records for a given month as shown on a calendar. Includes all the results of `by_month`, plus any results which fall in the same week as the first and last of the month. Useful for working with UI calendars which show rows of weeks.
```ruby
Post.by_calendar_month
```
Parameter behavior is otherwise the same as `by_month`. Also, `:start_day` option is supported to specify the start day of the week (`:monday`, `:tuesday`, etc.)
### by_fortnight
Fortnight numbering starts at 0. The beginning of a fortnight is Monday, 12am.
To find records from the current fortnight:
```ruby
Post.by_fortnight
```
To find records based on a fortnight, you can pass in a number (representing the fortnight number) or a time object:
```ruby
Post.by_fortnight(18)
```
This will return all posts in the 18th fortnight of the current year.
```ruby
Post.by_fortnight(18, year: 2012)
```
This will return all posts in the 18th fortnight week of 2012.
```ruby
Post.by_fortnight(Time.local(2012,1,1))
```
This will return all posts from the first fortnight of 2012.
### by_week and by_cweek
Week numbering starts at 0, and cweek numbering starts at 1 (same as `Date#cweek`). The beginning of a week is as defined in `ActiveSupport#beginning_of_week`, which can be configured.
To find records from the current week:
```ruby
Post.by_week
Post.by_cweek # same result
```
This will return all posts in the 37th week of the current year (remember week numbering starts at 0):
```ruby
Post.by_week(36)
Post.by_cweek(37) # same result
```
This will return all posts in the 37th week of 2012:
```ruby
Post.by_week(36, year: 2012)
Post.by_cweek(37, year: 2012) # same result
```
This will return all posts in the week which contains Jan 1, 2012:
```ruby
Post.by_week(Time.local(2012,1,1))
Post.by_cweek(Time.local(2012,1,1)) # same result
```
You may pass in a `:start_day` option (`:monday`, `:tuesday`, etc.) to specify the starting day of the week. This may also be configured in Rails.
### by_weekend
If the time passed in (or the time now is a weekend) it will return posts from 0:00 Saturday to 23:59:59 Sunday. If the time is a week day, it will show all posts for the coming weekend.
```ruby
Post.by_weekend(Time.now)
```
### by_day and today
To find records for today:
```ruby
Post.by_day
Post.today
```
To find records for a certain day:
```ruby
Post.by_day(Time.local(2012, 1, 1))
```
You can also pass a string:
```ruby
Post.by_day("next tuesday")
```
This will return all posts for the given day.
### by_quarter
Finds records by 3-month quarterly period of year. Quarter numbering starts at 1. The four quarters of the year begin on Jan 1, Apr 1, Jul 1, and Oct 1 respectively.
To find records from the current quarter:
```ruby
Post.by_quarter
```
To find records based on a quarter, you can pass in a number (representing the quarter number) or a time object:
```ruby
Post.by_quarter(4)
```
This will return all posts in the 4th quarter of the current year.
```ruby
Post.by_quarter(2, year: 2012)
```
This will return all posts in the 2nd quarter of 2012.
```ruby
Post.by_week(Time.local(2012,1,1))
```
This will return all posts from the first quarter of 2012.
## Version Support
ByStar is tested against the following versions:
* Ruby 2.0.0+
* Rails/ActiveRecord 3.2+
* Mongoid 3.1+
Note that ByStar automatically adds the following version compatibility shims:
* ActiveSupport 3.x: Add `Time/Date/DateTime#in_time_zone` (as an alias to `#to_time_in_current_zone`) for compatibility with Rails 4+.
* Mongoid 3.x: Adds `Criteria#reorder` method from Mongoid 4.
## Testing
### Test Setup
Specify a database by supplying a `DB` environmental variable:
```bash
bundle exec rake spec DB=sqlite
```
You can also take an ORM-specific test task for a ride:
```bash
bundle exec rake spec:active_record
```
Have an Active Record or Mongoid version in mind? Set the environment variables
`ACTIVE_RECORD_VERSION` and `MONGOID_VERSION` to a version of your choice. A
version number provided will translate to `~> VERSION`, and the string `master`
will grab the latest from Github.
```bash
# Update your bundle appropriately...
ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle update
# ...then run the specs
ACTIVE_RECORD_VERSION=4.0.0 MONGOID_VERSION=master bundle exec rpsec spec
```
### Test Implementation
ByStar tests use TimeCop to lock the system `Time.now` at Jan 01, 2014, and seed
objects with fixed dates according to `spec/fixtures/shared/seeds.rb`.
Note that the timezone is randomized on each run to shake-out timezone related quirks.
## Collaborators
ByStar is actively maintained by Ryan Bigg (radar) and Johnny Shields (johnnyshields)
Thank you to the following people:
* Thomas Sinclair for the original bump for implementing ByStar
* [Ruby on Rails](http://rubyonrails.org/) for their support
* Mislav Marohnic
* August Lilleas (leethal)
* gte351s
* Sam Elliott (lenary)
* The creators of the [Chronic](https://github.com/mojombo/chronic) gem
* Erik Fonselius
* Johnny Shields (johnnyshields)
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
require 'spec_helper'
describe ByStar::Normalization do
let(:options){ {} }
shared_examples_for 'date normalization from string' do
context 'when Chronic is defined' do
context 'date String' do
let(:input){ '2014-01-01' }
it { should eq Date.parse('2014-01-01') }
end
context 'time String' do
let(:input){ '2014-01-01 15:00:00' }
it { should eq Date.parse('2014-01-01') }
end
context 'natural language String' do
let(:input){ 'tomorrow at 3:30 pm' }
it { should eq Date.parse('2014-01-02') }
end
end
context 'when Chronic is not defined' do
before { hide_const('Chronic') }
context 'date String' do
let(:input){ '2014-01-01' }
it { should eq Date.parse('2014-01-01') }
end
context 'time String' do
let(:input){ '2014-01-01 15:00:00' }
it { should eq Date.parse('2014-01-01') }
end
context 'natural language String' do
let(:input){ 'tomorrow at noon' }
it { expect{ subject }.to raise_error(ByStar::ParseError, "Cannot parse String #{input.inspect}") }
end
end
end
shared_examples_for 'date normalization from time value' do
context 'Date' do
let(:input){ Date.parse('2014-01-01') }
it { expect eq Date.parse('2014-01-01') }
end
context 'DateTime' do
let(:input){ Date.parse('2014-01-01').to_datetime }
it { expect eq Date.parse('2014-01-01') }
end
context 'Time' do
let(:input){ Date.parse('2014-01-01') }
it { expect eq Date.parse('2014-01-01') }
end
end
describe '#time' do
subject { ByStar::Normalization.time(input) }
context 'when Chronic is defined' do
context 'date String' do
let(:input){ '2014-01-01' }
it { should eq Time.zone.parse('2014-01-01 12:00:00') }
end
context 'time String' do
let(:input){ '2014-01-01 15:00:00' }
it { should eq Time.zone.parse('2014-01-01 15:00:00') }
end
context 'natural language String' do
let(:input){ 'tomorrow at 3:30 pm' }
it { should eq Time.zone.parse('2014-01-02 15:30:00') }
end
end
context 'when Chronic is not defined' do
before { hide_const('Chronic') }
context 'date String' do
let(:input){ '2014-01-01' }
it { should eq Time.zone.parse('2014-01-01 00:00:00') }
end
context 'time String' do
let(:input){ '2014-01-01 15:00:00' }
it { should eq Time.zone.parse('2014-01-01 15:00:00') }
end
context 'natural language String' do
let(:input){ 'tomorrow at noon' }
it { expect{ subject }.to raise_error(ByStar::ParseError, "Cannot parse String #{input.inspect}") }
end
end
context 'Date' do
let(:input){ Date.parse('2014-01-01') }
it { expect eq Time.zone.parse('2014-01-01 00:00:00') }
end
context 'DateTime' do
let(:input){ Time.zone.parse('2014-01-01 15:00:00').to_datetime }
it { expect eq Time.zone.parse('2014-01-01 15:00:00') }
end
context 'Time' do
let(:input){ Time.zone.parse('2014-01-01 15:00:00') }
it { expect eq Time.zone.parse('2014-01-01 15:00:00') }
end
end
describe '#week' do
subject { ByStar::Normalization.week(input, options) }
it_behaves_like 'date normalization from string'
it_behaves_like 'date normalization from time value'
context 'Integer -1' do
let(:input){ -1 }
it { expect{subject}.to raise_error(ByStar::ParseError, 'Week number must be between 0 and 52') }
end
context 'Integer 0' do
let(:input){ 0 }
it { expect eq Date.parse('2014-01-01') }
end
context 'Integer 20' do
let(:input){ 20 }
it { expect eq Date.parse('2014-05-21') }
end
context 'Integer 53' do
let(:input){ 53 }
it { expect{subject}.to raise_error(ByStar::ParseError, 'Week number must be between 0 and 52') }
end
context 'with year option' do
let(:options){ { year: 2011 } }
context 'Integer 0' do
let(:input){ 0 }
it { expect eq Date.parse('2011-01-01') }
end
context 'Integer 20' do
let(:input){ 20 }
it { expect eq Date.parse('2011-05-21') }
end
end
end
describe '#cweek' do
subject { ByStar::Normalization.cweek(input, options) }
it_behaves_like 'date normalization from string'
it_behaves_like 'date normalization from time value'
context 'Integer 9' do
let(:input){ 0 }
it { expect{subject}.to raise_error(ByStar::ParseError, 'cweek number must be between 1 and 53') }
end
context 'Integer 1' do
let(:input){ 1 }
it { expect eq Date.parse('2014-01-01') }
end
context 'Integer 21' do
let(:input){ 21 }
it { expect eq Date.parse('2014-05-21') }
end
context 'Integer 54' do
let(:input){ 54 }
it { expect{subject}.to raise_error(ByStar::ParseError, 'cweek number must be between 1 and 53') }
end
context 'with year option' do
let(:options){ { year: 2011 } }
context 'Integer 1' do
let(:input){ 1 }
it { expect eq Date.parse('2011-01-01') }
end
context 'Integer 21' do
let(:input){ 21 }
it { expect eq Date.parse('2011-05-21') }
end
end
end
describe '#fortnight' do
subject { ByStar::Normalization.fortnight(input, options) }
it_behaves_like 'date normalization from string'
it_behaves_like 'date normalization from time value'
context 'Integer 0' do
let(:input){ 0 }
it { expect eq Date.parse('2014-01-01') }
end
context 'Integer 26' do
let(:input){ 26 }
it { expect eq Date.parse('2014-12-31') }
end
context 'out of range' do
specify { expect{ ByStar::Normalization.fortnight(-1) }.to raise_error(ByStar::ParseError, 'Fortnight number must be between 0 and 26') }
specify { expect{ ByStar::Normalization.fortnight(27) }.to raise_error(ByStar::ParseError, 'Fortnight number must be between 0 and 26') }
end
context 'with year option' do
let(:options){ { year: 2011 } }
context 'Integer 0' do
let(:input){ 0 }
it { expect eq Date.parse('2011-01-01') }
end
context 'Integer 26' do
let(:input){ 26 }
it { expect eq Date.parse('2011-12-31') }
end
end
end
describe '#month' do
subject { ByStar::Normalization.month(input, options) }
it_behaves_like 'date normalization from time value'
context 'month abbr String' do
let(:input){ 'Feb' }
it { expect eq Date.parse('2014-02-01') }
end
context 'month full String' do
let(:input){ 'February' }
it { expect eq Date.parse('2014-02-01') }
end
context 'number String' do
let(:input){ '2' }
it { expect eq Date.parse('2014-02-01') }
end
context 'Integer' do
let(:input){ 2 }
it { expect eq Date.parse('2014-02-01') }
end
context 'out of range' do
specify { expect{ ByStar::Normalization.month(0) }.to raise_error(ByStar::ParseError, 'Month must be a number between 1 and 12 or a month name') }
specify { expect{ ByStar::Normalization.month(13) }.to raise_error(ByStar::ParseError, 'Month must be a number between 1 and 12 or a month name') }
end
context 'with year option' do
let(:options){ { year: 2011 } }
context 'month abbr String' do
let(:input){ 'Dec' }
it { expect eq Date.parse('2011-12-01') }
end
context 'Integer 12' do
let(:input){ 10 }
it { expect eq Date.parse('2011-10-01') }
end
end
end
describe '#quarter' do
subject { ByStar::Normalization.quarter(input, options) }
it_behaves_like 'date normalization from string'
it_behaves_like 'date normalization from time value'
context 'Integer 1' do
let(:input){ 1 }
it { expect eq Date.parse('2014-01-01') }
end
context 'Integer 2' do
let(:input){ 2 }
it { expect eq Date.parse('2014-04-01') }
end
context 'Integer 3' do
let(:input){ 3 }
it { expect eq Date.parse('2014-07-01') }
end
context 'Integer 4' do
let(:input){ 4 }
it { expect eq Date.parse('2014-10-01') }
end
context 'with year option' do
let(:options){ { year: 2011 } }
context 'Integer 3' do
let(:input){ 3 }
it { expect eq Date.parse('2011-07-01') }
end
end
context 'out of range' do
specify { expect{ ByStar::Normalization.quarter(0) }.to raise_error(ByStar::ParseError, 'Quarter number must be between 1 and 4') }
specify { expect{ ByStar::Normalization.quarter(5) }.to raise_error(ByStar::ParseError, 'Quarter number must be between 1 and 4') }
end
end
describe '#year' do
subject { ByStar::Normalization.year(input, options) }
it_behaves_like 'date normalization from string'
it_behaves_like 'date normalization from time value'
context 'Integer 69' do
let(:input){ 69 }
it { expect eq Date.parse('2069-01-01') }
end
context 'Integer 99' do
let(:input){ 99 }
it { expect eq Date.parse('1999-01-01') }
end
context 'Integer 2001' do
let(:input){ 1 }
it { expect eq Date.parse('2001-01-01') }
end
context 'String 01' do
let(:input){ '01' }
it { expect eq Date.parse('2001-01-01') }
end
context 'String 70' do
let(:input){ '70' }
it { expect eq Date.parse('1970-01-01') }
end
context 'String 2001' do
let(:input){ '2001' }
it { expect eq Date.parse('2001-01-01') }
end
end
describe '#time_in_units' do
subject { ByStar::Normalization.time_in_units(input) }
context 'when less than a day' do
let(:input) { 34876 }
it { is_expected.to eq(days: 0, hour: 9, min: 41, sec: 16) }
end
context 'when more than a day' do
let(:input) { 97532 }
it { is_expected.to eq(days: 1, hour: 3, min: 5, sec: 32) }
end
end
describe '#apply_offset_start' do
subject { ByStar::Normalization.apply_offset_start(input, offset) }
let(:input) { Time.zone.parse('2020-04-05 00:00:00') }
let(:offset) { 5.hours }
it { is_expected.to eq Time.zone.parse('2020-04-05 05:00:00') }
end
describe '#apply_offset_end' do
subject { ByStar::Normalization.apply_offset_end(input, offset) }
let(:input) { Time.zone.parse('2020-10-04 00:00:00') }
let(:offset) { 5.hours }
it { is_expected.to eq Time.zone.parse('2020-10-05 04:59:59') }
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