activesupport-refinements 0.0.1

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.
Files changed (120) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +6 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +32 -0
  5. data/Rakefile +1 -0
  6. data/activesupport-refinements.gemspec +21 -0
  7. data/lib/active_support/refinements/core_ext/array.rb +7 -0
  8. data/lib/active_support/refinements/core_ext/array/access.rb +56 -0
  9. data/lib/active_support/refinements/core_ext/array/conversions.rb +224 -0
  10. data/lib/active_support/refinements/core_ext/array/extract_options.rb +31 -0
  11. data/lib/active_support/refinements/core_ext/array/grouping.rb +101 -0
  12. data/lib/active_support/refinements/core_ext/array/prepend_and_append.rb +9 -0
  13. data/lib/active_support/refinements/core_ext/array/uniq_by.rb +21 -0
  14. data/lib/active_support/refinements/core_ext/array/wrap.rb +48 -0
  15. data/lib/active_support/refinements/core_ext/benchmark.rb +7 -0
  16. data/lib/active_support/refinements/core_ext/big_decimal.rb +1 -0
  17. data/lib/active_support/refinements/core_ext/big_decimal/conversions.rb +32 -0
  18. data/lib/active_support/refinements/core_ext/class.rb +4 -0
  19. data/lib/active_support/refinements/core_ext/class/attribute.rb +119 -0
  20. data/lib/active_support/refinements/core_ext/class/attribute_accessors.rb +172 -0
  21. data/lib/active_support/refinements/core_ext/class/delegating_attributes.rb +42 -0
  22. data/lib/active_support/refinements/core_ext/class/subclasses.rb +44 -0
  23. data/lib/active_support/refinements/core_ext/date.rb +5 -0
  24. data/lib/active_support/refinements/core_ext/date/acts_like.rb +10 -0
  25. data/lib/active_support/refinements/core_ext/date/calculations.rb +123 -0
  26. data/lib/active_support/refinements/core_ext/date/conversions.rb +86 -0
  27. data/lib/active_support/refinements/core_ext/date/zones.rb +17 -0
  28. data/lib/active_support/refinements/core_ext/date_and_time/calculations.rb +232 -0
  29. data/lib/active_support/refinements/core_ext/date_time.rb +4 -0
  30. data/lib/active_support/refinements/core_ext/date_time/acts_like.rb +15 -0
  31. data/lib/active_support/refinements/core_ext/date_time/calculations.rb +143 -0
  32. data/lib/active_support/refinements/core_ext/date_time/conversions.rb +93 -0
  33. data/lib/active_support/refinements/core_ext/date_time/zones.rb +26 -0
  34. data/lib/active_support/refinements/core_ext/enumerable.rb +82 -0
  35. data/lib/active_support/refinements/core_ext/exception.rb +5 -0
  36. data/lib/active_support/refinements/core_ext/file.rb +1 -0
  37. data/lib/active_support/refinements/core_ext/file/atomic.rb +60 -0
  38. data/lib/active_support/refinements/core_ext/hash.rb +8 -0
  39. data/lib/active_support/refinements/core_ext/hash/conversions.rb +161 -0
  40. data/lib/active_support/refinements/core_ext/hash/deep_merge.rb +29 -0
  41. data/lib/active_support/refinements/core_ext/hash/diff.rb +15 -0
  42. data/lib/active_support/refinements/core_ext/hash/except.rb +17 -0
  43. data/lib/active_support/refinements/core_ext/hash/indifferent_access.rb +24 -0
  44. data/lib/active_support/refinements/core_ext/hash/keys.rb +140 -0
  45. data/lib/active_support/refinements/core_ext/hash/reverse_merge.rb +24 -0
  46. data/lib/active_support/refinements/core_ext/hash/slice.rb +42 -0
  47. data/lib/active_support/refinements/core_ext/integer.rb +3 -0
  48. data/lib/active_support/refinements/core_ext/integer/inflections.rb +31 -0
  49. data/lib/active_support/refinements/core_ext/integer/multiple.rb +12 -0
  50. data/lib/active_support/refinements/core_ext/integer/time.rb +43 -0
  51. data/lib/active_support/refinements/core_ext/kernel.rb +4 -0
  52. data/lib/active_support/refinements/core_ext/kernel/agnostics.rb +13 -0
  53. data/lib/active_support/refinements/core_ext/kernel/debugger.rb +12 -0
  54. data/lib/active_support/refinements/core_ext/kernel/reporting.rb +97 -0
  55. data/lib/active_support/refinements/core_ext/kernel/singleton_class.rb +8 -0
  56. data/lib/active_support/refinements/core_ext/load_error.rb +27 -0
  57. data/lib/active_support/refinements/core_ext/logger.rb +86 -0
  58. data/lib/active_support/refinements/core_ext/module.rb +10 -0
  59. data/lib/active_support/refinements/core_ext/module/aliasing.rb +69 -0
  60. data/lib/active_support/refinements/core_ext/module/anonymous.rb +21 -0
  61. data/lib/active_support/refinements/core_ext/module/attr_internal.rb +40 -0
  62. data/lib/active_support/refinements/core_ext/module/attribute_accessors.rb +68 -0
  63. data/lib/active_support/refinements/core_ext/module/delegation.rb +172 -0
  64. data/lib/active_support/refinements/core_ext/module/deprecation.rb +27 -0
  65. data/lib/active_support/refinements/core_ext/module/introspection.rb +80 -0
  66. data/lib/active_support/refinements/core_ext/module/qualified_const.rb +54 -0
  67. data/lib/active_support/refinements/core_ext/module/reachable.rb +10 -0
  68. data/lib/active_support/refinements/core_ext/module/remove_method.rb +14 -0
  69. data/lib/active_support/refinements/core_ext/name_error.rb +20 -0
  70. data/lib/active_support/refinements/core_ext/numeric.rb +3 -0
  71. data/lib/active_support/refinements/core_ext/numeric/bytes.rb +46 -0
  72. data/lib/active_support/refinements/core_ext/numeric/conversions.rb +137 -0
  73. data/lib/active_support/refinements/core_ext/numeric/time.rb +81 -0
  74. data/lib/active_support/refinements/core_ext/object.rb +14 -0
  75. data/lib/active_support/refinements/core_ext/object/acts_like.rb +12 -0
  76. data/lib/active_support/refinements/core_ext/object/blank.rb +107 -0
  77. data/lib/active_support/refinements/core_ext/object/conversions.rb +4 -0
  78. data/lib/active_support/refinements/core_ext/object/deep_dup.rb +48 -0
  79. data/lib/active_support/refinements/core_ext/object/duplicable.rb +92 -0
  80. data/lib/active_support/refinements/core_ext/object/inclusion.rb +27 -0
  81. data/lib/active_support/refinements/core_ext/object/instance_variables.rb +30 -0
  82. data/lib/active_support/refinements/core_ext/object/to_json.rb +27 -0
  83. data/lib/active_support/refinements/core_ext/object/to_param.rb +60 -0
  84. data/lib/active_support/refinements/core_ext/object/to_query.rb +29 -0
  85. data/lib/active_support/refinements/core_ext/object/try.rb +72 -0
  86. data/lib/active_support/refinements/core_ext/object/with_options.rb +44 -0
  87. data/lib/active_support/refinements/core_ext/proc.rb +19 -0
  88. data/lib/active_support/refinements/core_ext/range.rb +3 -0
  89. data/lib/active_support/refinements/core_ext/range/conversions.rb +21 -0
  90. data/lib/active_support/refinements/core_ext/range/include_range.rb +23 -0
  91. data/lib/active_support/refinements/core_ext/range/overlaps.rb +10 -0
  92. data/lib/active_support/refinements/core_ext/regexp.rb +7 -0
  93. data/lib/active_support/refinements/core_ext/string.rb +13 -0
  94. data/lib/active_support/refinements/core_ext/string/access.rb +106 -0
  95. data/lib/active_support/refinements/core_ext/string/behavior.rb +8 -0
  96. data/lib/active_support/refinements/core_ext/string/conversions.rb +60 -0
  97. data/lib/active_support/refinements/core_ext/string/encoding.rb +10 -0
  98. data/lib/active_support/refinements/core_ext/string/exclude.rb +13 -0
  99. data/lib/active_support/refinements/core_ext/string/filters.rb +54 -0
  100. data/lib/active_support/refinements/core_ext/string/indent.rb +45 -0
  101. data/lib/active_support/refinements/core_ext/string/inflections.rb +214 -0
  102. data/lib/active_support/refinements/core_ext/string/inquiry.rb +15 -0
  103. data/lib/active_support/refinements/core_ext/string/multibyte.rb +58 -0
  104. data/lib/active_support/refinements/core_ext/string/output_safety.rb +194 -0
  105. data/lib/active_support/refinements/core_ext/string/starts_ends_with.rb +6 -0
  106. data/lib/active_support/refinements/core_ext/string/strip.rb +28 -0
  107. data/lib/active_support/refinements/core_ext/string/xchar.rb +18 -0
  108. data/lib/active_support/refinements/core_ext/time.rb +5 -0
  109. data/lib/active_support/refinements/core_ext/time/acts_like.rb +10 -0
  110. data/lib/active_support/refinements/core_ext/time/calculations.rb +251 -0
  111. data/lib/active_support/refinements/core_ext/time/conversions.rb +65 -0
  112. data/lib/active_support/refinements/core_ext/time/marshal.rb +30 -0
  113. data/lib/active_support/refinements/core_ext/time/zones.rb +98 -0
  114. data/lib/active_support/refinements/core_ext/uri.rb +28 -0
  115. data/lib/activesupport-refinements.rb +9 -0
  116. data/lib/activesupport-refinements/version.rb +5 -0
  117. data/refine_core_ext.rb +45 -0
  118. data/spec/hwia_spec.rb +15 -0
  119. data/spec/try_spec.rb +18 -0
  120. metadata +182 -0
@@ -0,0 +1,44 @@
1
+ module ClassExt; end; module ClassExt::Subclasses
2
+ require 'active_support/refinements/core_ext/module/anonymous'
3
+ require 'active_support/refinements/core_ext/module/reachable'
4
+
5
+ refine Class do
6
+ begin
7
+ ObjectSpace.each_object(Class.new) {}
8
+
9
+ def descendants # :nodoc:
10
+ descendants = []
11
+ ObjectSpace.each_object(singleton_class) do |k|
12
+ descendants.unshift k unless k == self
13
+ end
14
+ descendants
15
+ end
16
+ rescue StandardError # JRuby
17
+ def descendants # :nodoc:
18
+ descendants = []
19
+ ObjectSpace.each_object(Class) do |k|
20
+ descendants.unshift k if k < self
21
+ end
22
+ descendants.uniq!
23
+ descendants
24
+ end
25
+ end
26
+
27
+ # Returns an array with the direct children of +self+.
28
+ #
29
+ # Integer.subclasses # => [Fixnum, Bignum]
30
+ #
31
+ # class Foo; end
32
+ # class Bar < Foo; end
33
+ # class Baz < Foo; end
34
+ #
35
+ # Foo.subclasses # => [Baz, Bar]
36
+ def subclasses
37
+ subclasses, chain = [], descendants
38
+ chain.each do |k|
39
+ subclasses << k unless chain.any? { |c| c > k }
40
+ end
41
+ subclasses
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_support/refinements/core_ext/date/acts_like'
2
+ require 'active_support/refinements/core_ext/date/calculations'
3
+ require 'active_support/refinements/core_ext/date/conversions'
4
+ require 'active_support/refinements/core_ext/date/zones'
5
+
@@ -0,0 +1,10 @@
1
+ module DateExt; end; module DateExt::ActsLike
2
+ require 'active_support/refinements/core_ext/object/acts_like'
3
+
4
+ refine Date do
5
+ # Duck-types as a Date-like class. See Object#acts_like?.
6
+ def acts_like_date?
7
+ true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,123 @@
1
+ module DateExt; end; module DateExt::Calculations
2
+ require 'date'
3
+ require 'active_support/duration'
4
+ require 'active_support/refinements/core_ext/object/acts_like'
5
+ require 'active_support/refinements/core_ext/date/zones'
6
+ require 'active_support/refinements/core_ext/time/zones'
7
+ require 'active_support/refinements/core_ext/date_and_time/calculations'
8
+
9
+ refine Date do
10
+ include DateAndTime::Calculations
11
+
12
+ class << self
13
+ attr_accessor :beginning_of_week_default
14
+
15
+ # Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=).
16
+ # If <tt>Date.beginning_of_week</tt> has not been set for the current request, returns the week start specified in <tt>config.beginning_of_week</tt>.
17
+ # If no config.beginning_of_week was specified, returns :monday.
18
+ def beginning_of_week
19
+ Thread.current[:beginning_of_week] || beginning_of_week_default || :monday
20
+ end
21
+
22
+ # Sets <tt>Date.beginning_of_week</tt> to a week start (e.g. :monday) for current request/thread.
23
+ #
24
+ # This method accepts any of the following day symbols:
25
+ # :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
26
+ def beginning_of_week=(week_start)
27
+ Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start)
28
+ end
29
+
30
+ # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol.
31
+ def find_beginning_of_week!(week_start)
32
+ raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start)
33
+ week_start
34
+ end
35
+
36
+ # Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
37
+ def yesterday
38
+ ::Date.current.yesterday
39
+ end
40
+
41
+ # Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
42
+ def tomorrow
43
+ ::Date.current.tomorrow
44
+ end
45
+
46
+ # Returns Time.zone.today when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns Date.today.
47
+ def current
48
+ ::Time.zone ? ::Time.zone.today : ::Date.today
49
+ end
50
+ end
51
+
52
+ # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
53
+ # and then subtracts the specified number of seconds.
54
+ def ago(seconds)
55
+ to_time_in_current_zone.since(-seconds)
56
+ end
57
+
58
+ # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
59
+ # and then adds the specified number of seconds
60
+ def since(seconds)
61
+ to_time_in_current_zone.since(seconds)
62
+ end
63
+ # alias :in :since
64
+
65
+ # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
66
+ def beginning_of_day
67
+ to_time_in_current_zone
68
+ end
69
+ # alias :midnight :beginning_of_day
70
+ # alias :at_midnight :beginning_of_day
71
+ # alias :at_beginning_of_day :beginning_of_day
72
+
73
+ # Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
74
+ def end_of_day
75
+ to_time_in_current_zone.end_of_day
76
+ end
77
+
78
+ def plus_with_duration(other) #:nodoc:
79
+ if ActiveSupport::Duration === other
80
+ other.since(self)
81
+ else
82
+ plus_without_duration(other)
83
+ end
84
+ end
85
+ # alias_method :plus_without_duration, :+
86
+ # alias_method :+, :plus_with_duration
87
+
88
+ def minus_with_duration(other) #:nodoc:
89
+ if ActiveSupport::Duration === other
90
+ plus_with_duration(-other)
91
+ else
92
+ minus_without_duration(other)
93
+ end
94
+ end
95
+ # alias_method :minus_without_duration, :-
96
+ # alias_method :-, :minus_with_duration
97
+
98
+ # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
99
+ # any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
100
+ def advance(options)
101
+ options = options.dup
102
+ d = self
103
+ d = d >> options.delete(:years) * 12 if options[:years]
104
+ d = d >> options.delete(:months) if options[:months]
105
+ d = d + options.delete(:weeks) * 7 if options[:weeks]
106
+ d = d + options.delete(:days) if options[:days]
107
+ d
108
+ end
109
+
110
+ # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
111
+ # The +options+ parameter is a hash with a combination of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>.
112
+ #
113
+ # Date.new(2007, 5, 12).change(day: 1) # => Date.new(2007, 5, 1)
114
+ # Date.new(2007, 5, 12).change(year: 2005, month: 1) # => Date.new(2005, 1, 12)
115
+ def change(options)
116
+ ::Date.new(
117
+ options.fetch(:year, year),
118
+ options.fetch(:month, month),
119
+ options.fetch(:day, day)
120
+ )
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,86 @@
1
+ module DateExt; end; module DateExt::Conversions
2
+ require 'date'
3
+ require 'active_support/inflector/methods'
4
+ require 'active_support/refinements/core_ext/date/zones'
5
+ require 'active_support/refinements/core_ext/module/remove_method'
6
+
7
+ refine Date do
8
+ DATE_FORMATS = {
9
+ :short => '%e %b',
10
+ :long => '%B %e, %Y',
11
+ :db => '%Y-%m-%d',
12
+ :number => '%Y%m%d',
13
+ :long_ordinal => lambda { |date|
14
+ day_format = ActiveSupport::Inflector.ordinalize(date.day)
15
+ date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
16
+ },
17
+ :rfc822 => '%e %b %Y'
18
+ }
19
+
20
+ # Ruby 1.9 has Date#to_time which converts to localtime only.
21
+ remove_possible_method :to_time
22
+
23
+ # Ruby 1.9 has Date#xmlschema which converts to a string without the time component.
24
+ remove_possible_method :xmlschema
25
+
26
+ # Convert to a formatted string. See DATE_FORMATS for predefined formats.
27
+ #
28
+ # This method is aliased to <tt>to_s</tt>.
29
+ #
30
+ # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
31
+ #
32
+ # date.to_formatted_s(:db) # => "2007-11-10"
33
+ # date.to_s(:db) # => "2007-11-10"
34
+ #
35
+ # date.to_formatted_s(:short) # => "10 Nov"
36
+ # date.to_formatted_s(:long) # => "November 10, 2007"
37
+ # date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
38
+ # date.to_formatted_s(:rfc822) # => "10 Nov 2007"
39
+ #
40
+ # == Adding your own time formats to to_formatted_s
41
+ # You can add your own formats to the Date::DATE_FORMATS hash.
42
+ # Use the format name as the hash key and either a strftime string
43
+ # or Proc instance that takes a date argument as the value.
44
+ #
45
+ # # config/initializers/time_formats.rb
46
+ # Date::DATE_FORMATS[:month_and_year] = '%B %Y'
47
+ # Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") }
48
+ def to_formatted_s(format = :default)
49
+ if formatter = DATE_FORMATS[format]
50
+ if formatter.respond_to?(:call)
51
+ formatter.call(self).to_s
52
+ else
53
+ strftime(formatter)
54
+ end
55
+ else
56
+ to_default_s
57
+ end
58
+ end
59
+ # alias_method :to_default_s, :to_s
60
+ # alias_method :to_s, :to_formatted_s
61
+
62
+ # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
63
+ def readable_inspect
64
+ strftime('%a, %d %b %Y')
65
+ end
66
+ # alias_method :default_inspect, :inspect
67
+ # alias_method :inspect, :readable_inspect
68
+
69
+ # Converts a Date instance to a Time, where the time is set to the beginning of the day.
70
+ # The timezone can be either :local or :utc (default :local).
71
+ #
72
+ # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
73
+ #
74
+ # date.to_time # => Sat Nov 10 00:00:00 0800 2007
75
+ # date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007
76
+ #
77
+ # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
78
+ def to_time(form = :local)
79
+ ::Time.send("#{form}_time", year, month, day)
80
+ end
81
+
82
+ def xmlschema
83
+ to_time_in_current_zone.xmlschema
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,17 @@
1
+ module DateExt; end; module DateExt::Zones
2
+ require 'date'
3
+ require 'active_support/refinements/core_ext/time/zones'
4
+
5
+ refine Date do
6
+ # Converts Date to a TimeWithZone in the current zone if <tt>Time.zone</tt> or
7
+ # <tt>Time.zone_default</tt> is set, otherwise converts Date to a Time via
8
+ # Date#to_time.
9
+ def to_time_in_current_zone
10
+ if ::Time.zone
11
+ ::Time.zone.local(year, month, day)
12
+ else
13
+ to_time
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,232 @@
1
+ module DateAndTime
2
+ module Calculations
3
+ DAYS_INTO_WEEK = {
4
+ :monday => 0,
5
+ :tuesday => 1,
6
+ :wednesday => 2,
7
+ :thursday => 3,
8
+ :friday => 4,
9
+ :saturday => 5,
10
+ :sunday => 6
11
+ }
12
+
13
+ # Returns a new date/time representing yesterday.
14
+ def yesterday
15
+ advance(:days => -1)
16
+ end
17
+
18
+ # Returns a new date/time representing tomorrow.
19
+ def tomorrow
20
+ advance(:days => 1)
21
+ end
22
+
23
+ # Returns true if the date/time is today.
24
+ def today?
25
+ to_date == ::Date.current
26
+ end
27
+
28
+ # Returns true if the date/time is in the past.
29
+ def past?
30
+ self < self.class.current
31
+ end
32
+
33
+ # Returns true if the date/time is in the future.
34
+ def future?
35
+ self > self.class.current
36
+ end
37
+
38
+ # Returns a new date/time the specified number of days ago.
39
+ def days_ago(days)
40
+ advance(:days => -days)
41
+ end
42
+
43
+ # Returns a new date/time the specified number of days in the future.
44
+ def days_since(days)
45
+ advance(:days => days)
46
+ end
47
+
48
+ # Returns a new date/time the specified number of weeks ago.
49
+ def weeks_ago(weeks)
50
+ advance(:weeks => -weeks)
51
+ end
52
+
53
+ # Returns a new date/time the specified number of weeks in the future.
54
+ def weeks_since(weeks)
55
+ advance(:weeks => weeks)
56
+ end
57
+
58
+ # Returns a new date/time the specified number of months ago.
59
+ def months_ago(months)
60
+ advance(:months => -months)
61
+ end
62
+
63
+ # Returns a new date/time the specified number of months in the future.
64
+ def months_since(months)
65
+ advance(:months => months)
66
+ end
67
+
68
+ # Returns a new date/time the specified number of years ago.
69
+ def years_ago(years)
70
+ advance(:years => -years)
71
+ end
72
+
73
+ # Returns a new date/time the specified number of years in the future.
74
+ def years_since(years)
75
+ advance(:years => years)
76
+ end
77
+
78
+ # Returns a new date/time at the start of the month.
79
+ # DateTime objects will have a time set to 0:00.
80
+ def beginning_of_month
81
+ first_hour{ change(:day => 1) }
82
+ end
83
+ alias :at_beginning_of_month :beginning_of_month
84
+
85
+ # Returns a new date/time at the start of the quarter.
86
+ # Example: 1st January, 1st July, 1st October.
87
+ # DateTime objects will have a time set to 0:00.
88
+ def beginning_of_quarter
89
+ first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
90
+ beginning_of_month.change(:month => first_quarter_month)
91
+ end
92
+ alias :at_beginning_of_quarter :beginning_of_quarter
93
+
94
+ # Returns a new date/time at the end of the quarter.
95
+ # Example: 31st March, 30th June, 30th September.
96
+ # DateTIme objects will have a time set to 23:59:59.
97
+ def end_of_quarter
98
+ last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
99
+ beginning_of_month.change(:month => last_quarter_month).end_of_month
100
+ end
101
+ alias :at_end_of_quarter :end_of_quarter
102
+
103
+ # Return a new date/time at the beginning of the year.
104
+ # Example: 1st January.
105
+ # DateTime objects will have a time set to 0:00.
106
+ def beginning_of_year
107
+ change(:month => 1).beginning_of_month
108
+ end
109
+ alias :at_beginning_of_year :beginning_of_year
110
+
111
+ # Returns a new date/time representing the given day in the next week.
112
+ # Week is assumed to start on +start_day+, default is
113
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
114
+ # DateTime objects have their time set to 0:00.
115
+ def next_week(start_day = Date.beginning_of_week)
116
+ first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(start_day)) }
117
+ end
118
+
119
+ # Short-hand for months_since(1).
120
+ def next_month
121
+ months_since(1)
122
+ end
123
+
124
+ # Short-hand for months_since(3)
125
+ def next_quarter
126
+ months_since(3)
127
+ end
128
+
129
+ # Short-hand for years_since(1).
130
+ def next_year
131
+ years_since(1)
132
+ end
133
+
134
+ # Returns a new date/time representing the given day in the previous week.
135
+ # Week is assumed to start on +start_day+, default is
136
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
137
+ # DateTime objects have their time set to 0:00.
138
+ def prev_week(start_day = Date.beginning_of_week)
139
+ first_hour{ weeks_ago(1).beginning_of_week.days_since(days_span(start_day)) }
140
+ end
141
+ alias_method :last_week, :prev_week
142
+
143
+ # Short-hand for months_ago(1).
144
+ def prev_month
145
+ months_ago(1)
146
+ end
147
+ alias_method :last_month, :prev_month
148
+
149
+ # Short-hand for months_ago(3).
150
+ def prev_quarter
151
+ months_ago(3)
152
+ end
153
+ alias_method :last_quarter, :prev_quarter
154
+
155
+ # Short-hand for years_ago(1).
156
+ def prev_year
157
+ years_ago(1)
158
+ end
159
+ alias_method :last_year, :prev_year
160
+
161
+ # Returns the number of days to the start of the week on the given day.
162
+ # Week is assumed to start on +start_day+, default is
163
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
164
+ def days_to_week_start(start_day = Date.beginning_of_week)
165
+ start_day_number = DAYS_INTO_WEEK[start_day]
166
+ current_day_number = wday != 0 ? wday - 1 : 6
167
+ (current_day_number - start_day_number) % 7
168
+ end
169
+
170
+ # Returns a new date/time representing the start of this week on the given day.
171
+ # Week is assumed to start on +start_day+, default is
172
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
173
+ # +DateTime+ objects have their time set to 0:00.
174
+ def beginning_of_week(start_day = Date.beginning_of_week)
175
+ result = days_ago(days_to_week_start(start_day))
176
+ acts_like?(:time) ? result.midnight : result
177
+ end
178
+ alias :at_beginning_of_week :beginning_of_week
179
+
180
+ # Returns Monday of this week assuming that week starts on Monday.
181
+ # +DateTime+ objects have their time set to 0:00.
182
+ def monday
183
+ beginning_of_week(:monday)
184
+ end
185
+
186
+ # Returns a new date/time representing the end of this week on the given day.
187
+ # Week is assumed to start on +start_day+, default is
188
+ # +Date.beginning_of_week+ or +config.beginning_of_week+ when set.
189
+ # DateTime objects have their time set to 23:59:59.
190
+ def end_of_week(start_day = Date.beginning_of_week)
191
+ last_hour{ days_since(6 - days_to_week_start(start_day)) }
192
+ end
193
+ alias :at_end_of_week :end_of_week
194
+
195
+ # Returns Sunday of this week assuming that week starts on Monday.
196
+ # +DateTime+ objects have their time set to 23:59:59.
197
+ def sunday
198
+ end_of_week(:monday)
199
+ end
200
+
201
+ # Returns a new date/time representing the end of the month.
202
+ # DateTime objects will have a time set to 23:59:59.
203
+ def end_of_month
204
+ last_day = ::Time.days_in_month(month, year)
205
+ last_hour{ days_since(last_day - day) }
206
+ end
207
+ alias :at_end_of_month :end_of_month
208
+
209
+ # Returns a new date/time representing the end of the year.
210
+ # DateTime objects will have a time set to 23:59:59.
211
+ def end_of_year
212
+ change(:month => 12).end_of_month
213
+ end
214
+ alias :at_end_of_year :end_of_year
215
+
216
+ private
217
+
218
+ def first_hour
219
+ result = yield
220
+ acts_like?(:time) ? result.change(:hour => 0) : result
221
+ end
222
+
223
+ def last_hour
224
+ result = yield
225
+ acts_like?(:time) ? result.end_of_day : result
226
+ end
227
+
228
+ def days_span(day)
229
+ (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7
230
+ end
231
+ end
232
+ end