ta_by_star 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +59 -0
- data/Gemfile +18 -0
- data/MIT-LICENSE +20 -0
- data/README.md +616 -0
- data/Rakefile +18 -0
- data/UPGRADING +4 -0
- data/by_star.gemspec +34 -0
- data/cleaner.rb +25 -0
- data/lib/by_star/base.rb +76 -0
- data/lib/by_star/between.rb +190 -0
- data/lib/by_star/directional.rb +35 -0
- data/lib/by_star/kernel/date.rb +41 -0
- data/lib/by_star/kernel/in_time_zone.rb +20 -0
- data/lib/by_star/kernel/time.rb +41 -0
- data/lib/by_star/normalization.rb +156 -0
- data/lib/by_star/orm/active_record/by_star.rb +75 -0
- data/lib/by_star/orm/mongoid/by_star.rb +90 -0
- data/lib/by_star/orm/mongoid/reorder.rb +23 -0
- data/lib/by_star/version.rb +3 -0
- data/lib/by_star.rb +18 -0
- data/spec/database.yml +15 -0
- data/spec/fixtures/active_record/models.rb +12 -0
- data/spec/fixtures/active_record/schema.rb +19 -0
- data/spec/fixtures/mongoid/models.rb +31 -0
- data/spec/fixtures/shared/seeds.rb +36 -0
- data/spec/gemfiles/Gemfile.rails +5 -0
- data/spec/gemfiles/Gemfile.rails32 +7 -0
- data/spec/gemfiles/Gemfile.rails40 +7 -0
- data/spec/gemfiles/Gemfile.rails41 +7 -0
- data/spec/gemfiles/Gemfile.rails42 +7 -0
- data/spec/gemfiles/Gemfile.rails50 +7 -0
- data/spec/gemfiles/Gemfile.rails51 +7 -0
- data/spec/gemfiles/Gemfile.rails52 +7 -0
- data/spec/gemfiles/Gemfile.rails60 +7 -0
- data/spec/gemfiles/Gemfile.rails61 +7 -0
- data/spec/integration/active_record/active_record_spec.rb +41 -0
- data/spec/integration/mongoid/mongoid_spec.rb +39 -0
- data/spec/integration/shared/at_time.rb +53 -0
- data/spec/integration/shared/between_dates.rb +99 -0
- data/spec/integration/shared/between_times.rb +99 -0
- data/spec/integration/shared/by_calendar_month.rb +55 -0
- data/spec/integration/shared/by_cweek.rb +54 -0
- data/spec/integration/shared/by_day.rb +120 -0
- data/spec/integration/shared/by_direction.rb +126 -0
- data/spec/integration/shared/by_fortnight.rb +48 -0
- data/spec/integration/shared/by_month.rb +50 -0
- data/spec/integration/shared/by_quarter.rb +49 -0
- data/spec/integration/shared/by_week.rb +54 -0
- data/spec/integration/shared/by_weekend.rb +49 -0
- data/spec/integration/shared/by_year.rb +48 -0
- data/spec/integration/shared/index_scope_parameter.rb +111 -0
- data/spec/integration/shared/offset_parameter.rb +32 -0
- data/spec/integration/shared/order_parameter.rb +36 -0
- data/spec/integration/shared/relative.rb +174 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/unit/kernel_date_spec.rb +113 -0
- data/spec/unit/kernel_time_spec.rb +57 -0
- data/spec/unit/normalization_spec.rb +384 -0
- data/tmp/.gitignore +1 -0
- metadata +298 -0
    
        data/lib/by_star/base.rb
    ADDED
    
    | @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            module ByStar
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              module Base
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                include ByStar::Between
         | 
| 6 | 
            +
                include ByStar::Directional
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def by_star_field(*args)
         | 
| 9 | 
            +
                  options = args.extract_options!
         | 
| 10 | 
            +
                  @by_star_start_field ||= args[0]
         | 
| 11 | 
            +
                  @by_star_end_field   ||= args[1]
         | 
| 12 | 
            +
                  @by_star_offset      ||= options[:offset]
         | 
| 13 | 
            +
                  @by_star_scope       ||= options[:scope]
         | 
| 14 | 
            +
                  @by_star_index_scope ||= options[:index_scope]
         | 
| 15 | 
            +
                  @by_star_field_type  ||= options[:field_type]
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def by_star_offset(options = {})
         | 
| 19 | 
            +
                  (options[:offset] || @by_star_offset || 0).seconds
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def by_star_start_field(options={})
         | 
| 23 | 
            +
                  field = options[:field] ||
         | 
| 24 | 
            +
                      options[:start_field] ||
         | 
| 25 | 
            +
                      @by_star_start_field ||
         | 
| 26 | 
            +
                      by_star_default_field
         | 
| 27 | 
            +
                  field.to_s
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def by_star_end_field(options={})
         | 
| 31 | 
            +
                  field = options[:field] ||
         | 
| 32 | 
            +
                      options[:end_field] ||
         | 
| 33 | 
            +
                      @by_star_end_field ||
         | 
| 34 | 
            +
                      by_star_start_field
         | 
| 35 | 
            +
                  field.to_s
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def by_star_field_type(options={})
         | 
| 39 | 
            +
                  field = options[:field_type] ||
         | 
| 40 | 
            +
                      @by_star_field_type
         | 
| 41 | 
            +
                  field.to_s
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                protected
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # Wrapper function which extracts time and options for each by_star query.
         | 
| 47 | 
            +
                # Note the following syntax examples are valid:
         | 
| 48 | 
            +
                #
         | 
| 49 | 
            +
                #   Post.by_month                   # defaults to current time
         | 
| 50 | 
            +
                #   Post.by_month(2, year: 2004)    # February, 2004
         | 
| 51 | 
            +
                #   Post.by_month(Time.now)
         | 
| 52 | 
            +
                #   Post.by_month(Time.now, field: "published_at")
         | 
| 53 | 
            +
                #   Post.by_month(field: "published_at")
         | 
| 54 | 
            +
                #
         | 
| 55 | 
            +
                def with_by_star_options(*args, &block)
         | 
| 56 | 
            +
                  options = args.extract_options!.symbolize_keys!
         | 
| 57 | 
            +
                  time = args.first || Time.zone.now
         | 
| 58 | 
            +
                  block.call(time, options)
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def by_star_eval_index_scope(start_time, end_time, options)
         | 
| 62 | 
            +
                  value = options[:index_scope] || @by_star_index_scope
         | 
| 63 | 
            +
                  value = value.call(start_time, end_time, options) if value.is_a?(Proc)
         | 
| 64 | 
            +
                  case value
         | 
| 65 | 
            +
                    when nil, false then nil
         | 
| 66 | 
            +
                    when Time, DateTime, Date then value.in_time_zone
         | 
| 67 | 
            +
                    when ActiveSupport::Duration then start_time - value
         | 
| 68 | 
            +
                    when Numeric then start_time - value.seconds
         | 
| 69 | 
            +
                    when :beginning_of_day
         | 
| 70 | 
            +
                      offset = options[:offset] || 0
         | 
| 71 | 
            +
                      (start_time - offset).beginning_of_day + offset
         | 
| 72 | 
            +
                    else raise 'ByStar :index_scope option value is not a supported type.'
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
            end
         | 
| @@ -0,0 +1,190 @@ | |
| 1 | 
            +
            module ByStar
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              module Between
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def between_times(*args)
         | 
| 6 | 
            +
                  options = args.extract_options!.symbolize_keys!
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  start_time, end_time = ByStar::Normalization.extract_range(args)
         | 
| 9 | 
            +
                  offset = options[:offset] || 0
         | 
| 10 | 
            +
                  field_type = by_star_field_type(options)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  if start_time.is_a?(Date)
         | 
| 13 | 
            +
                    start_time = field_type == 'date' ?
         | 
| 14 | 
            +
                      start_time :
         | 
| 15 | 
            +
                      ByStar::Normalization.apply_offset_start(start_time.in_time_zone, offset)
         | 
| 16 | 
            +
                  elsif start_time
         | 
| 17 | 
            +
                    start_time += offset.seconds
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  if end_time.is_a?(Date)
         | 
| 21 | 
            +
                    end_time = field_type == 'date' ?
         | 
| 22 | 
            +
                      end_time :
         | 
| 23 | 
            +
                      ByStar::Normalization.apply_offset_end(end_time.in_time_zone, offset)
         | 
| 24 | 
            +
                  elsif end_time
         | 
| 25 | 
            +
                    end_time += offset.seconds
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  start_field = by_star_start_field(options)
         | 
| 29 | 
            +
                  end_field = by_star_end_field(options)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  scope = self
         | 
| 32 | 
            +
                  scope = if !start_time && !end_time
         | 
| 33 | 
            +
                            scope # do nothing
         | 
| 34 | 
            +
                          elsif !end_time
         | 
| 35 | 
            +
                            by_star_after_query(scope, start_field, start_time)
         | 
| 36 | 
            +
                          elsif !start_time
         | 
| 37 | 
            +
                            by_star_before_query(scope, start_field, end_time)
         | 
| 38 | 
            +
                          elsif start_field == end_field
         | 
| 39 | 
            +
                            by_star_point_query(scope, start_field, start_time, end_time)
         | 
| 40 | 
            +
                          elsif options[:strict]
         | 
| 41 | 
            +
                            by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
         | 
| 42 | 
            +
                          else
         | 
| 43 | 
            +
                            by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
         | 
| 44 | 
            +
                          end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  scope = by_star_order(scope, options[:order]) if options[:order]
         | 
| 47 | 
            +
                  scope
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def between_dates(*args)
         | 
| 51 | 
            +
                  options = args.extract_options!
         | 
| 52 | 
            +
                  start_date, end_date = ByStar::Normalization.extract_range(args)
         | 
| 53 | 
            +
                  start_date = ByStar::Normalization.date(start_date)
         | 
| 54 | 
            +
                  end_date   = ByStar::Normalization.date(end_date)
         | 
| 55 | 
            +
                  between_times(start_date, end_date, options)
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                def at_time(*args)
         | 
| 59 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 60 | 
            +
                    start_field = by_star_start_field(options)
         | 
| 61 | 
            +
                    end_field = by_star_end_field(options)
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    scope = self
         | 
| 64 | 
            +
                    scope = if start_field == end_field
         | 
| 65 | 
            +
                              by_star_point_overlap_query(scope, start_field, time)
         | 
| 66 | 
            +
                            else
         | 
| 67 | 
            +
                              by_star_span_overlap_query(scope, start_field, end_field, time, options)
         | 
| 68 | 
            +
                            end
         | 
| 69 | 
            +
                    scope = by_star_order(scope, options[:order]) if options[:order]
         | 
| 70 | 
            +
                    scope
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def by_day(*args)
         | 
| 75 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 76 | 
            +
                    date = ByStar::Normalization.date(time)
         | 
| 77 | 
            +
                    between_dates(date, date, options)
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def by_week(*args)
         | 
| 82 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 83 | 
            +
                    date = ByStar::Normalization.week(time, options)
         | 
| 84 | 
            +
                    start_day = Array(options[:start_day])
         | 
| 85 | 
            +
                    between_dates(date.beginning_of_week(*start_day), date.end_of_week(*start_day), options)
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def by_cweek(*args)
         | 
| 90 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 91 | 
            +
                    by_week(ByStar::Normalization.cweek(time, options), options)
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def by_weekend(*args)
         | 
| 96 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 97 | 
            +
                    date = ByStar::Normalization.week(time, options)
         | 
| 98 | 
            +
                    between_dates(date.beginning_of_weekend, date.end_of_weekend, options)
         | 
| 99 | 
            +
                  end
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                def by_fortnight(*args)
         | 
| 103 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 104 | 
            +
                    date = ByStar::Normalization.fortnight(time, options)
         | 
| 105 | 
            +
                    between_dates(date.beginning_of_fortnight, date.end_of_fortnight, options)
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                def by_month(*args)
         | 
| 110 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 111 | 
            +
                    date = ByStar::Normalization.month(time, options)
         | 
| 112 | 
            +
                    between_dates(date.beginning_of_month, date.end_of_month, options)
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def by_calendar_month(*args)
         | 
| 117 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 118 | 
            +
                    date = ByStar::Normalization.month(time, options)
         | 
| 119 | 
            +
                    start_day = Array(options[:start_day])
         | 
| 120 | 
            +
                    between_dates(date.beginning_of_calendar_month(*start_day), date.end_of_calendar_month(*start_day), options)
         | 
| 121 | 
            +
                  end
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                def by_quarter(*args)
         | 
| 125 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 126 | 
            +
                    date = ByStar::Normalization.quarter(time, options)
         | 
| 127 | 
            +
                    between_dates(date.beginning_of_quarter, date.end_of_quarter, options)
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
                end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                def by_year(*args)
         | 
| 132 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 133 | 
            +
                    date = ByStar::Normalization.year(time, options)
         | 
| 134 | 
            +
                    between_dates(date.beginning_of_year, date.to_date.end_of_year, options)
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                def today(options = {})
         | 
| 139 | 
            +
                  by_day(Date.current, options)
         | 
| 140 | 
            +
                end
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                def yesterday(options = {})
         | 
| 143 | 
            +
                  by_day(Date.yesterday, options)
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                def tomorrow(options = {})
         | 
| 147 | 
            +
                  by_day(Date.tomorrow, options)
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                def past_day(options = {})
         | 
| 151 | 
            +
                  between_times(Time.current - 1.day, Time.current, options)
         | 
| 152 | 
            +
                end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                def past_week(options = {})
         | 
| 155 | 
            +
                  between_times(Time.current - 1.week, Time.current, options)
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                def past_fortnight(options = {})
         | 
| 159 | 
            +
                  between_times(Time.current - 2.weeks, Time.current, options)
         | 
| 160 | 
            +
                end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                def past_month(options = {})
         | 
| 163 | 
            +
                  between_times(Time.current - 1.month, Time.current, options)
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                def past_year(options = {})
         | 
| 167 | 
            +
                  between_times(Time.current - 1.year, Time.current, options)
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                def next_day(options = {})
         | 
| 171 | 
            +
                  between_times(Time.current, Time.current + 1.day, options)
         | 
| 172 | 
            +
                end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                def next_week(options = {})
         | 
| 175 | 
            +
                  between_times(Time.current, Time.current  + 1.week, options)
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                def next_fortnight(options = {})
         | 
| 179 | 
            +
                  between_times(Time.current, Time.current + 2.weeks, options)
         | 
| 180 | 
            +
                end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                def next_month(options = {})
         | 
| 183 | 
            +
                  between_times(Time.current, Time.current + 1.month, options)
         | 
| 184 | 
            +
                end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                def next_year(options = {})
         | 
| 187 | 
            +
                  between_times(Time.current, Time.current + 1.year, options)
         | 
| 188 | 
            +
                end
         | 
| 189 | 
            +
              end
         | 
| 190 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module ByStar
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              module Directional
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def before(*args)
         | 
| 6 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 7 | 
            +
                    field = by_star_start_field(options)
         | 
| 8 | 
            +
                    time = ByStar::Normalization.time(time)
         | 
| 9 | 
            +
                    by_star_before_query(self, field, time)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
                alias_method :before_now, :before
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def after(*args)
         | 
| 15 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 16 | 
            +
                    field = by_star_start_field(options)
         | 
| 17 | 
            +
                    time = ByStar::Normalization.time(time)
         | 
| 18 | 
            +
                    by_star_after_query(self, field, time)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                alias_method :after_now, :after
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def oldest(*args)
         | 
| 24 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 25 | 
            +
                    oldest_query(options)
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def newest(*args)
         | 
| 30 | 
            +
                  with_by_star_options(*args) do |time, options|
         | 
| 31 | 
            +
                    newest_query(options)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            module ByStar
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              module Kernel
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                module Date
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  # A "Weekend" is defined as beginning of Saturday to end of Sunday.
         | 
| 8 | 
            +
                  # The weekend for a given date will be the the next weekend if the day Mon-Thurs,
         | 
| 9 | 
            +
                  # otherwise the current weekend if the day is Fri-Sun.
         | 
| 10 | 
            +
                  def beginning_of_weekend
         | 
| 11 | 
            +
                    beginning_of_week(:monday).advance(days: 5)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def end_of_weekend
         | 
| 15 | 
            +
                    beginning_of_weekend + 1
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # A "Fortnight" is defined as a two week period, with the first fortnight of the
         | 
| 19 | 
            +
                  # year beginning on 1st January.
         | 
| 20 | 
            +
                  def beginning_of_fortnight
         | 
| 21 | 
            +
                    beginning_of_year + 14 * ((self - beginning_of_year) / 14).to_i
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def end_of_fortnight
         | 
| 25 | 
            +
                    beginning_of_fortnight + 13
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # A "Calendar Month" is defined as a month as it appears on a calendar, including days form
         | 
| 29 | 
            +
                  # previous/following months which are part of the first/last weeks of the given month.
         | 
| 30 | 
            +
                  def beginning_of_calendar_month(*args)
         | 
| 31 | 
            +
                    beginning_of_month.beginning_of_week(*args)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def end_of_calendar_month(*args)
         | 
| 35 | 
            +
                    end_of_month.end_of_week(*args)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            ::Date.__send__(:include, ByStar::Kernel::Date)
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            module ByStar
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              module Kernel
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                module InTimeZone
         | 
| 6 | 
            +
                  extend ActiveSupport::Concern
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  included do
         | 
| 9 | 
            +
                    if method_defined?(:to_time_in_current_zone) && !method_defined?(:in_time_zone)
         | 
| 10 | 
            +
                      alias_method :in_time_zone, :to_time_in_current_zone
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ::Date.__send__(:include, ByStar::Kernel::InTimeZone)
         | 
| 18 | 
            +
            ::Time.__send__(:include, ByStar::Kernel::InTimeZone)
         | 
| 19 | 
            +
            ::DateTime.__send__(:include, ByStar::Kernel::InTimeZone)
         | 
| 20 | 
            +
            ::ActiveSupport::TimeWithZone.__send__(:include, ByStar::Kernel::InTimeZone)
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            module ByStar
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              module Kernel
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                module Time
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  # A "Weekend" is defined as beginning of Saturday to end of Sunday.
         | 
| 8 | 
            +
                  # The weekend for a given date will be the the next weekend if the day Mon-Thurs,
         | 
| 9 | 
            +
                  # otherwise the current weekend if the day is Fri-Sun.
         | 
| 10 | 
            +
                  def beginning_of_weekend
         | 
| 11 | 
            +
                    beginning_of_week(:monday).advance(days: 5)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def end_of_weekend
         | 
| 15 | 
            +
                    (beginning_of_weekend + 47.hours).end_of_hour
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  # A "Fortnight" is defined as a two week period, with the first fortnight of the
         | 
| 19 | 
            +
                  # year beginning on 1st January.
         | 
| 20 | 
            +
                  def beginning_of_fortnight
         | 
| 21 | 
            +
                    (beginning_of_year + 1.fortnight * ((self - beginning_of_year) / 1.fortnight).to_i).beginning_of_day
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  def end_of_fortnight
         | 
| 25 | 
            +
                    (beginning_of_fortnight + 13.days).end_of_day
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  # A "Calendar Month" is defined as a month as it appears on a calendar, including days form
         | 
| 29 | 
            +
                  # previous/following months which are part of the first/last weeks of the given month.
         | 
| 30 | 
            +
                  def beginning_of_calendar_month(*args)
         | 
| 31 | 
            +
                    beginning_of_month.beginning_of_week(*args)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def end_of_calendar_month(*args)
         | 
| 35 | 
            +
                    end_of_month.end_of_week(*args)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            ::Time.__send__(:include, ByStar::Kernel::Time)
         | 
| @@ -0,0 +1,156 @@ | |
| 1 | 
            +
            module ByStar
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              class ParseError < StandardError; end
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              module Normalization
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                class << self
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  def date(value)
         | 
| 10 | 
            +
                    value = parse_time(value) if value.is_a?(String)
         | 
| 11 | 
            +
                    value = value.try(:in_time_zone) unless value.is_a?(Date)
         | 
| 12 | 
            +
                    value.try(:to_date)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def time(value)
         | 
| 16 | 
            +
                    value = parse_time(value) if value.is_a?(String)
         | 
| 17 | 
            +
                    value.try(:in_time_zone)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def week(value, options={})
         | 
| 21 | 
            +
                    value = try_string_to_int(value)
         | 
| 22 | 
            +
                    case value
         | 
| 23 | 
            +
                      when Integer then week_integer(value, options)
         | 
| 24 | 
            +
                      else date(value)
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def week_integer(value, options={})
         | 
| 29 | 
            +
                    raise ParseError, 'Week number must be between 0 and 52' unless value.in?(0..52)
         | 
| 30 | 
            +
                    time = Time.zone.local(options[:year] || Time.zone.now.year)
         | 
| 31 | 
            +
                    time.beginning_of_year + value.to_i.weeks
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def cweek(value, options={})
         | 
| 35 | 
            +
                    _value = value
         | 
| 36 | 
            +
                    if _value.is_a?(Integer)
         | 
| 37 | 
            +
                      raise ParseError, 'cweek number must be between 1 and 53' unless value.in?(1..53)
         | 
| 38 | 
            +
                      _value -= 1
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                    week(_value, options)
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def fortnight(value, options={})
         | 
| 44 | 
            +
                    value = try_string_to_int(value)
         | 
| 45 | 
            +
                    case value
         | 
| 46 | 
            +
                      when Integer then fortnight_integer(value, options)
         | 
| 47 | 
            +
                      else date(value)
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def fortnight_integer(value, options={})
         | 
| 52 | 
            +
                    raise ParseError, 'Fortnight number must be between 0 and 26' unless value.in?(0..26)
         | 
| 53 | 
            +
                    time = Time.zone.local(options[:year] || Time.zone.now.year)
         | 
| 54 | 
            +
                    time + (value * 2).weeks
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def quarter(value, options={})
         | 
| 58 | 
            +
                    value = try_string_to_int(value)
         | 
| 59 | 
            +
                    case value
         | 
| 60 | 
            +
                      when Integer then quarter_integer(value, options)
         | 
| 61 | 
            +
                      else date(value)
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def quarter_integer(value, options={})
         | 
| 66 | 
            +
                    raise ParseError, 'Quarter number must be between 1 and 4' unless value.in?(1..4)
         | 
| 67 | 
            +
                    time = Time.zone.local(options[:year] || Time.zone.now.year)
         | 
| 68 | 
            +
                    time.beginning_of_year + ((value - 1) * 3).months
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                  def month(value, options={})
         | 
| 72 | 
            +
                    value = try_string_to_int(value)
         | 
| 73 | 
            +
                    case value
         | 
| 74 | 
            +
                      when Integer, String then month_integer(value, options)
         | 
| 75 | 
            +
                      else date(value)
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  def month_integer(value, options={})
         | 
| 80 | 
            +
                    year = options[:year] || Time.zone.now.year
         | 
| 81 | 
            +
                    Time.zone.parse "#{year}-#{value}-01"
         | 
| 82 | 
            +
                  rescue
         | 
| 83 | 
            +
                    raise ParseError, 'Month must be a number between 1 and 12 or a month name'
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  def year(value, options={})
         | 
| 87 | 
            +
                    value = try_string_to_int(value)
         | 
| 88 | 
            +
                    case value
         | 
| 89 | 
            +
                      when Integer then year_integer(value)
         | 
| 90 | 
            +
                      else date(value)
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  def year_integer(value)
         | 
| 95 | 
            +
                    Time.zone.local(extrapolate_year(value))
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                  def extrapolate_year(value)
         | 
| 99 | 
            +
                    case value.to_i
         | 
| 100 | 
            +
                      when 0..69
         | 
| 101 | 
            +
                        2000 + value
         | 
| 102 | 
            +
                      when 70..99
         | 
| 103 | 
            +
                        1900 + value
         | 
| 104 | 
            +
                      else
         | 
| 105 | 
            +
                        value.to_i
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  def try_string_to_int(value)
         | 
| 110 | 
            +
                    value.is_a?(String) ? Integer(value) : value
         | 
| 111 | 
            +
                  rescue
         | 
| 112 | 
            +
                    value
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  def time_in_units(seconds)
         | 
| 116 | 
            +
                    days = seconds / 1.day
         | 
| 117 | 
            +
                    time = Time.at(seconds).utc
         | 
| 118 | 
            +
                    { days: days, hour: time.hour, min: time.min, sec: time.sec }
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  def apply_offset_start(time, offset)
         | 
| 122 | 
            +
                    units = time_in_units(offset)
         | 
| 123 | 
            +
                    time += units.delete(:days).days
         | 
| 124 | 
            +
                    time.change(units)
         | 
| 125 | 
            +
                  end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  def apply_offset_end(time, offset)
         | 
| 128 | 
            +
                    units = time_in_units(offset)
         | 
| 129 | 
            +
                    time += units.delete(:days).days
         | 
| 130 | 
            +
                    (time + 1.day).change(units) - 1.second
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  def extract_range(args)
         | 
| 134 | 
            +
                    case args[0]
         | 
| 135 | 
            +
                    when Array, Range then [args[0].first, args[0].last]
         | 
| 136 | 
            +
                    else args[0..1]
         | 
| 137 | 
            +
                    end
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  private
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  def parse_time(value)
         | 
| 143 | 
            +
                    defined?(Chronic) ? parse_time_chronic(value) : parse_time_fallback(value)
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  def parse_time_chronic(value)
         | 
| 147 | 
            +
                    Chronic.time_class = Time.zone
         | 
| 148 | 
            +
                    Chronic.parse(value) || raise(ByStar::ParseError, "Chronic could not parse String #{value.inspect}")
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  def parse_time_fallback(value)
         | 
| 152 | 
            +
                    Time.zone.parse(value) || raise(ByStar::ParseError, "Cannot parse String #{value.inspect}")
         | 
| 153 | 
            +
                  end
         | 
| 154 | 
            +
                end
         | 
| 155 | 
            +
              end
         | 
| 156 | 
            +
            end
         | 
| @@ -0,0 +1,75 @@ | |
| 1 | 
            +
            module ByStar
         | 
| 2 | 
            +
              module ActiveRecord
         | 
| 3 | 
            +
                extend ActiveSupport::Concern
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                module ClassMethods
         | 
| 6 | 
            +
                  include ::ByStar::Base
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  protected
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def by_star_default_field
         | 
| 11 | 
            +
                    "#{self.table_name}.created_at"
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def by_star_point_query(scope, field, start_time, end_time)
         | 
| 15 | 
            +
                    scope.where("#{field} >= ? AND #{field} <= ?", start_time, end_time)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def by_star_span_strict_query(scope, start_field, end_field, start_time, end_time)
         | 
| 19 | 
            +
                    scope.where("#{start_field} >= ? AND #{start_field} <= ? AND #{end_field} >= ? AND #{end_field} <= ?", start_time, end_time, start_time, end_time)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def by_star_span_loose_query(scope, start_field, end_field, start_time, end_time, options)
         | 
| 23 | 
            +
                    index_scope = by_star_eval_index_scope(start_time, end_time, options)
         | 
| 24 | 
            +
                    scope = scope.where("#{end_field} > ? AND #{start_field} < ?", start_time, end_time)
         | 
| 25 | 
            +
                    scope = scope.where("#{start_field} >= ?", index_scope) if index_scope
         | 
| 26 | 
            +
                    scope
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def by_star_point_overlap_query(scope, field, time)
         | 
| 30 | 
            +
                    scope.where("#{field} = ?", time)
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def by_star_span_overlap_query(scope, start_field, end_field, time, options)
         | 
| 34 | 
            +
                    index_scope = by_star_eval_index_scope(time, time, options)
         | 
| 35 | 
            +
                    scope = scope.where("#{end_field} > ? AND #{start_field} <= ?", time, time)
         | 
| 36 | 
            +
                    scope = scope.where("#{start_field} >= ?", index_scope) if index_scope
         | 
| 37 | 
            +
                    scope
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def by_star_before_query(scope, field, time)
         | 
| 41 | 
            +
                    scope.where("#{field} <= ?", time)
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def by_star_after_query(scope, field, time)
         | 
| 45 | 
            +
                    scope.where("#{field} >= ?", time)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def by_star_order(scope, order)
         | 
| 49 | 
            +
                    scope.order(order)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def oldest_query(options={})
         | 
| 53 | 
            +
                    field = by_star_start_field(options)
         | 
| 54 | 
            +
                    reorder("#{field} ASC").first
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def newest_query(options={})
         | 
| 58 | 
            +
                    field = by_star_start_field(options)
         | 
| 59 | 
            +
                    reorder("#{field} DESC").first
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def previous(options={})
         | 
| 64 | 
            +
                  field = self.class.by_star_start_field(options)
         | 
| 65 | 
            +
                  value = self.send(field.split(".").last)
         | 
| 66 | 
            +
                  self.class.where("#{field} < ?", value).reorder("#{field} DESC").first
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def next(options={})
         | 
| 70 | 
            +
                  field = self.class.by_star_start_field(options)
         | 
| 71 | 
            +
                  value = self.send(field.split(".").last)
         | 
| 72 | 
            +
                  self.class.where("#{field} > ?", value).reorder("#{field} ASC").first
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
            end
         |