activesupport-refinements 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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