motion-support 0.1.0 → 0.2.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.
Files changed (171) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +1 -1
  3. data/README.md +231 -2
  4. data/Rakefile +3 -3
  5. data/app/app_delegate.rb +0 -2
  6. data/examples/Inflector/.gitignore +16 -0
  7. data/examples/Inflector/Gemfile +5 -0
  8. data/examples/Inflector/Rakefile +13 -0
  9. data/examples/Inflector/app/app_delegate.rb +8 -0
  10. data/examples/Inflector/app/inflections.rb +5 -0
  11. data/examples/Inflector/app/inflector_view_controller.rb +120 -0
  12. data/examples/Inflector/app/words_view_controller.rb +101 -0
  13. data/examples/Inflector/resources/Default-568h@2x.png +0 -0
  14. data/examples/Inflector/spec/main_spec.rb +9 -0
  15. data/lib/motion-support/core_ext/array.rb +13 -0
  16. data/lib/motion-support/core_ext/class.rb +8 -0
  17. data/lib/motion-support/core_ext/hash.rb +16 -0
  18. data/lib/motion-support/core_ext/integer.rb +9 -0
  19. data/lib/motion-support/core_ext/module.rb +14 -0
  20. data/lib/motion-support/core_ext/numeric.rb +8 -0
  21. data/lib/motion-support/core_ext/object.rb +14 -0
  22. data/lib/motion-support/core_ext/range.rb +8 -0
  23. data/lib/motion-support/core_ext/string.rb +14 -0
  24. data/lib/motion-support/core_ext/time.rb +19 -0
  25. data/lib/motion-support/core_ext.rb +3 -0
  26. data/lib/motion-support/inflector.rb +12 -156
  27. data/lib/motion-support.rb +5 -5
  28. data/motion/_stdlib/cgi.rb +22 -0
  29. data/motion/_stdlib/date.rb +77 -0
  30. data/motion/_stdlib/time.rb +19 -0
  31. data/motion/core_ext/array/access.rb +28 -0
  32. data/motion/core_ext/array/conversions.rb +86 -0
  33. data/motion/core_ext/array/extract_options.rb +11 -0
  34. data/motion/core_ext/array/grouping.rb +99 -0
  35. data/motion/core_ext/array/prepend_and_append.rb +7 -0
  36. data/motion/core_ext/array/wrap.rb +45 -0
  37. data/{lib/motion-support → motion/core_ext}/array.rb +0 -4
  38. data/motion/core_ext/class/attribute.rb +119 -0
  39. data/motion/core_ext/class/attribute_accessors.rb +168 -0
  40. data/motion/core_ext/date/acts_like.rb +8 -0
  41. data/motion/core_ext/date/calculations.rb +117 -0
  42. data/motion/core_ext/date/conversions.rb +56 -0
  43. data/motion/core_ext/date_and_time/calculations.rb +232 -0
  44. data/motion/core_ext/enumerable.rb +90 -0
  45. data/motion/core_ext/hash/deep_merge.rb +27 -0
  46. data/motion/core_ext/hash/except.rb +15 -0
  47. data/motion/core_ext/hash/indifferent_access.rb +19 -0
  48. data/motion/core_ext/hash/keys.rb +138 -0
  49. data/motion/core_ext/hash/reverse_merge.rb +22 -0
  50. data/motion/core_ext/hash/slice.rb +40 -0
  51. data/motion/core_ext/integer/inflections.rb +27 -0
  52. data/motion/core_ext/integer/multiple.rb +10 -0
  53. data/motion/core_ext/integer/time.rb +41 -0
  54. data/{lib/motion-support → motion/core_ext}/metaclass.rb +0 -0
  55. data/motion/core_ext/module/aliasing.rb +69 -0
  56. data/motion/core_ext/module/anonymous.rb +19 -0
  57. data/motion/core_ext/module/attr_internal.rb +38 -0
  58. data/motion/core_ext/module/attribute_accessors.rb +64 -0
  59. data/motion/core_ext/module/delegation.rb +175 -0
  60. data/motion/core_ext/module/introspection.rb +60 -0
  61. data/motion/core_ext/module/reachable.rb +5 -0
  62. data/motion/core_ext/module/remove_method.rb +12 -0
  63. data/motion/core_ext/ns_dictionary.rb +11 -0
  64. data/motion/core_ext/numeric/bytes.rb +44 -0
  65. data/motion/core_ext/numeric/conversions.rb +7 -0
  66. data/motion/core_ext/numeric/time.rb +75 -0
  67. data/motion/core_ext/object/acts_like.rb +10 -0
  68. data/motion/core_ext/object/blank.rb +105 -0
  69. data/motion/core_ext/object/deep_dup.rb +44 -0
  70. data/motion/core_ext/object/duplicable.rb +83 -0
  71. data/motion/core_ext/object/instance_variables.rb +28 -0
  72. data/motion/core_ext/object/to_param.rb +58 -0
  73. data/motion/core_ext/object/to_query.rb +26 -0
  74. data/motion/core_ext/object/try.rb +78 -0
  75. data/motion/core_ext/range/include_range.rb +23 -0
  76. data/motion/core_ext/range/overlaps.rb +8 -0
  77. data/motion/core_ext/regexp.rb +5 -0
  78. data/motion/core_ext/string/access.rb +104 -0
  79. data/motion/core_ext/string/behavior.rb +6 -0
  80. data/motion/core_ext/string/exclude.rb +11 -0
  81. data/motion/core_ext/string/filters.rb +55 -0
  82. data/motion/core_ext/string/indent.rb +43 -0
  83. data/motion/core_ext/string/inflections.rb +195 -0
  84. data/motion/core_ext/string/starts_ends_with.rb +4 -0
  85. data/motion/core_ext/string/strip.rb +24 -0
  86. data/motion/core_ext/time/acts_like.rb +8 -0
  87. data/motion/core_ext/time/calculations.rb +215 -0
  88. data/motion/core_ext/time/conversions.rb +52 -0
  89. data/motion/duration.rb +104 -0
  90. data/motion/hash_with_indifferent_access.rb +251 -0
  91. data/motion/inflections.rb +67 -0
  92. data/motion/inflector/inflections.rb +203 -0
  93. data/motion/inflector/methods.rb +321 -0
  94. data/{lib/motion-support → motion}/logger.rb +0 -0
  95. data/{lib/motion-support → motion}/version.rb +1 -1
  96. data/motion-support.gemspec +2 -2
  97. data/spec/motion-support/_helpers/constantize_test_cases.rb +75 -0
  98. data/spec/motion-support/_helpers/inflector_test_cases.rb +313 -0
  99. data/spec/motion-support/core_ext/array/access_spec.rb +29 -0
  100. data/spec/motion-support/core_ext/array/conversion_spec.rb +60 -0
  101. data/spec/motion-support/core_ext/array/extract_options_spec.rb +15 -0
  102. data/spec/motion-support/core_ext/array/grouping_spec.rb +85 -0
  103. data/spec/motion-support/core_ext/array/prepend_and_append_spec.rb +25 -0
  104. data/spec/motion-support/core_ext/array/wrap_spec.rb +19 -0
  105. data/spec/motion-support/{array_spec.rb → core_ext/array_spec.rb} +0 -5
  106. data/spec/motion-support/core_ext/class/attribute_accessor_spec.rb +127 -0
  107. data/spec/motion-support/core_ext/class/attribute_spec.rb +92 -0
  108. data/spec/motion-support/core_ext/date/acts_like_spec.rb +11 -0
  109. data/spec/motion-support/core_ext/date/calculation_spec.rb +186 -0
  110. data/spec/motion-support/core_ext/date/conversion_spec.rb +18 -0
  111. data/spec/motion-support/core_ext/date_and_time/calculation_spec.rb +336 -0
  112. data/spec/motion-support/core_ext/enumerable_spec.rb +130 -0
  113. data/spec/motion-support/core_ext/hash/deep_merge_spec.rb +32 -0
  114. data/spec/motion-support/core_ext/hash/except_spec.rb +43 -0
  115. data/spec/motion-support/core_ext/hash/key_spec.rb +230 -0
  116. data/spec/motion-support/core_ext/hash/reverse_merge_spec.rb +26 -0
  117. data/spec/motion-support/core_ext/hash/slice_spec.rb +61 -0
  118. data/spec/motion-support/core_ext/integer/inflection_spec.rb +23 -0
  119. data/spec/motion-support/core_ext/integer/multiple_spec.rb +19 -0
  120. data/spec/motion-support/{metaclass_spec.rb → core_ext/metaclass_spec.rb} +0 -0
  121. data/spec/motion-support/core_ext/module/aliasing_spec.rb +143 -0
  122. data/spec/motion-support/core_ext/module/anonymous_spec.rb +29 -0
  123. data/spec/motion-support/core_ext/module/attr_internal_spec.rb +104 -0
  124. data/spec/motion-support/core_ext/module/attribute_accessor_spec.rb +86 -0
  125. data/spec/motion-support/core_ext/module/delegation_spec.rb +136 -0
  126. data/spec/motion-support/core_ext/module/introspection_spec.rb +70 -0
  127. data/spec/motion-support/core_ext/module/reachable_spec.rb +61 -0
  128. data/spec/motion-support/core_ext/module/remove_method_spec.rb +25 -0
  129. data/spec/motion-support/core_ext/numeric/bytes_spec.rb +43 -0
  130. data/spec/motion-support/core_ext/object/acts_like_spec.rb +21 -0
  131. data/spec/motion-support/core_ext/object/blank_spec.rb +54 -0
  132. data/spec/motion-support/core_ext/object/deep_dup_spec.rb +54 -0
  133. data/spec/motion-support/core_ext/object/duplicable_spec.rb +31 -0
  134. data/spec/motion-support/core_ext/object/instance_variable_spec.rb +19 -0
  135. data/spec/motion-support/core_ext/object/to_param_spec.rb +75 -0
  136. data/spec/motion-support/core_ext/object/to_query_spec.rb +37 -0
  137. data/spec/motion-support/core_ext/object/try_spec.rb +92 -0
  138. data/spec/motion-support/core_ext/range/include_range_spec.rb +31 -0
  139. data/spec/motion-support/core_ext/range/overlap_spec.rb +43 -0
  140. data/spec/motion-support/core_ext/regexp_spec.rb +7 -0
  141. data/spec/motion-support/core_ext/string/access_spec.rb +53 -0
  142. data/spec/motion-support/core_ext/string/behavior_spec.rb +7 -0
  143. data/spec/motion-support/core_ext/string/exclude_spec.rb +8 -0
  144. data/spec/motion-support/core_ext/string/filter_spec.rb +48 -0
  145. data/spec/motion-support/core_ext/string/indent_spec.rb +56 -0
  146. data/spec/motion-support/core_ext/string/inflection_spec.rb +142 -0
  147. data/spec/motion-support/core_ext/string/starts_end_with_spec.rb +14 -0
  148. data/spec/motion-support/core_ext/string/strip_spec.rb +34 -0
  149. data/spec/motion-support/core_ext/string_spec.rb +88 -0
  150. data/spec/motion-support/core_ext/time/acts_like_spec.rb +11 -0
  151. data/spec/motion-support/core_ext/time/calculation_spec.rb +201 -0
  152. data/spec/motion-support/core_ext/time/conversion_spec.rb +54 -0
  153. data/spec/motion-support/duration_spec.rb +107 -0
  154. data/spec/motion-support/hash_with_indifferent_access_spec.rb +605 -0
  155. data/spec/motion-support/inflector_spec.rb +474 -35
  156. data/spec/motion-support/ns_dictionary_spec.rb +29 -0
  157. metadata +212 -35
  158. data/lib/motion-support/cattr_accessor.rb +0 -19
  159. data/lib/motion-support/class_inheritable_accessor.rb +0 -23
  160. data/lib/motion-support/class_inheritable_array.rb +0 -29
  161. data/lib/motion-support/hash.rb +0 -31
  162. data/lib/motion-support/nilclass.rb +0 -5
  163. data/lib/motion-support/object.rb +0 -17
  164. data/lib/motion-support/string.rb +0 -71
  165. data/spec/motion-support/cattr_accessor_spec.rb +0 -49
  166. data/spec/motion-support/class_inheritable_accessor_spec.rb +0 -49
  167. data/spec/motion-support/class_inheritable_array_spec.rb +0 -61
  168. data/spec/motion-support/hash_spec.rb +0 -31
  169. data/spec/motion-support/nilclass_spec.rb +0 -5
  170. data/spec/motion-support/object_spec.rb +0 -43
  171. data/spec/motion-support/string_spec.rb +0 -145
@@ -0,0 +1,119 @@
1
+ class Class
2
+ # Declare a class-level attribute whose value is inheritable by subclasses.
3
+ # Subclasses can change their own value and it will not impact parent class.
4
+ #
5
+ # class Base
6
+ # class_attribute :setting
7
+ # end
8
+ #
9
+ # class Subclass < Base
10
+ # end
11
+ #
12
+ # Base.setting = true
13
+ # Subclass.setting # => true
14
+ # Subclass.setting = false
15
+ # Subclass.setting # => false
16
+ # Base.setting # => true
17
+ #
18
+ # In the above case as long as Subclass does not assign a value to setting
19
+ # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
20
+ # would read value assigned to parent class. Once Subclass assigns a value then
21
+ # the value assigned by Subclass would be returned.
22
+ #
23
+ # This matches normal Ruby method inheritance: think of writing an attribute
24
+ # on a subclass as overriding the reader method. However, you need to be aware
25
+ # when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
26
+ # In such cases, you don't want to do changes in places but use setters:
27
+ #
28
+ # Base.setting = []
29
+ # Base.setting # => []
30
+ # Subclass.setting # => []
31
+ #
32
+ # # Appending in child changes both parent and child because it is the same object:
33
+ # Subclass.setting << :foo
34
+ # Base.setting # => [:foo]
35
+ # Subclass.setting # => [:foo]
36
+ #
37
+ # # Use setters to not propagate changes:
38
+ # Base.setting = []
39
+ # Subclass.setting += [:foo]
40
+ # Base.setting # => []
41
+ # Subclass.setting # => [:foo]
42
+ #
43
+ # For convenience, a query method is defined as well:
44
+ #
45
+ # Subclass.setting? # => false
46
+ #
47
+ # Instances may overwrite the class value in the same way:
48
+ #
49
+ # Base.setting = true
50
+ # object = Base.new
51
+ # object.setting # => true
52
+ # object.setting = false
53
+ # object.setting # => false
54
+ # Base.setting # => true
55
+ #
56
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
57
+ #
58
+ # object.setting # => NoMethodError
59
+ # object.setting? # => NoMethodError
60
+ #
61
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
62
+ #
63
+ # object.setting = false # => NoMethodError
64
+ #
65
+ # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
66
+ def class_attribute(*attrs)
67
+ options = attrs.extract_options!
68
+ # double assignment is used to avoid "assigned but unused variable" warning
69
+ instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
70
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
71
+
72
+ attrs.each do |name|
73
+ define_singleton_method(name) { nil }
74
+ define_singleton_method("#{name}?") { !!public_send(name) }
75
+
76
+ ivar = "@#{name}"
77
+
78
+ define_singleton_method("#{name}=") do |val|
79
+ singleton_class.class_eval do
80
+ remove_possible_method(name)
81
+ define_method(name) { val }
82
+ end
83
+
84
+ if singleton_class?
85
+ class_eval do
86
+ remove_possible_method(name)
87
+ define_method(name) do
88
+ if instance_variable_defined? ivar
89
+ instance_variable_get ivar
90
+ else
91
+ singleton_class.send name
92
+ end
93
+ end
94
+ end
95
+ end
96
+ val
97
+ end
98
+
99
+ if instance_reader
100
+ remove_possible_method name
101
+ define_method(name) do
102
+ if instance_variable_defined?(ivar)
103
+ instance_variable_get ivar
104
+ else
105
+ self.class.public_send name
106
+ end
107
+ end
108
+ define_method("#{name}?") { !!public_send(name) }
109
+ end
110
+
111
+ attr_writer name if instance_writer
112
+ end
113
+ end
114
+
115
+ private
116
+ def singleton_class?
117
+ ancestors.first != self
118
+ end
119
+ end
@@ -0,0 +1,168 @@
1
+ # Extends the class object with class and instance accessors for class attributes,
2
+ # just like the native attr* accessors for instance attributes.
3
+ class Class
4
+ # Defines a class attribute if it's not defined and creates a reader method that
5
+ # returns the attribute value.
6
+ #
7
+ # class Person
8
+ # cattr_reader :hair_colors
9
+ # end
10
+ #
11
+ # Person.class_variable_set("@@hair_colors", [:brown, :black])
12
+ # Person.hair_colors # => [:brown, :black]
13
+ # Person.new.hair_colors # => [:brown, :black]
14
+ #
15
+ # The attribute name must be a valid method name in Ruby.
16
+ #
17
+ # class Person
18
+ # cattr_reader :"1_Badname "
19
+ # end
20
+ # # => NameError: invalid attribute name
21
+ #
22
+ # If you want to opt out the instance reader method, you can pass <tt>instance_reader: false</tt>
23
+ # or <tt>instance_accessor: false</tt>.
24
+ #
25
+ # class Person
26
+ # cattr_reader :hair_colors, instance_reader: false
27
+ # end
28
+ #
29
+ # Person.new.hair_colors # => NoMethodError
30
+ def cattr_reader(*syms)
31
+ options = syms.extract_options!
32
+ syms.each do |sym|
33
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
34
+ class_exec do
35
+ unless class_variable_defined?("@@#{sym}")
36
+ class_variable_set("@@#{sym}", nil)
37
+ end
38
+
39
+ define_singleton_method sym do
40
+ class_variable_get("@@#{sym}")
41
+ end
42
+ end
43
+
44
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
45
+ class_exec do
46
+ define_method sym do
47
+ self.class.class_variable_get("@@#{sym}")
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # Defines a class attribute if it's not defined and creates a writer method to allow
55
+ # assignment to the attribute.
56
+ #
57
+ # class Person
58
+ # cattr_writer :hair_colors
59
+ # end
60
+ #
61
+ # Person.hair_colors = [:brown, :black]
62
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black]
63
+ # Person.new.hair_colors = [:blonde, :red]
64
+ # Person.class_variable_get("@@hair_colors") # => [:blonde, :red]
65
+ #
66
+ # The attribute name must be a valid method name in Ruby.
67
+ #
68
+ # class Person
69
+ # cattr_writer :"1_Badname "
70
+ # end
71
+ # # => NameError: invalid attribute name
72
+ #
73
+ # If you want to opt out the instance writer method, pass <tt>instance_writer: false</tt>
74
+ # or <tt>instance_accessor: false</tt>.
75
+ #
76
+ # class Person
77
+ # cattr_writer :hair_colors, instance_writer: false
78
+ # end
79
+ #
80
+ # Person.new.hair_colors = [:blonde, :red] # => NoMethodError
81
+ #
82
+ # Also, you can pass a block to set up the attribute with a default value.
83
+ #
84
+ # class Person
85
+ # cattr_writer :hair_colors do
86
+ # [:brown, :black, :blonde, :red]
87
+ # end
88
+ # end
89
+ #
90
+ # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
91
+ def cattr_writer(*syms)
92
+ options = syms.extract_options!
93
+ syms.each do |sym|
94
+ raise NameError.new('invalid attribute name') unless sym =~ /^[_A-Za-z]\w*$/
95
+ class_exec do
96
+ unless class_variable_defined?("@@#{sym}")
97
+ class_variable_set("@@#{sym}", nil)
98
+ end
99
+
100
+ define_singleton_method "#{sym}=" do |obj|
101
+ class_variable_set("@@#{sym}", obj)
102
+ end
103
+ end
104
+
105
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
106
+ class_exec do
107
+ define_method "#{sym}=" do |obj|
108
+ self.class.class_variable_set("@@#{sym}", obj)
109
+ end
110
+ end
111
+ end
112
+ send("#{sym}=", yield) if block_given?
113
+ end
114
+ end
115
+
116
+ # Defines both class and instance accessors for class attributes.
117
+ #
118
+ # class Person
119
+ # cattr_accessor :hair_colors
120
+ # end
121
+ #
122
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
123
+ # Person.hair_colors # => [:brown, :black, :blonde, :red]
124
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
125
+ #
126
+ # If a subclass changes the value then that would also change the value for
127
+ # parent class. Similarly if parent class changes the value then that would
128
+ # change the value of subclasses too.
129
+ #
130
+ # class Male < Person
131
+ # end
132
+ #
133
+ # Male.hair_colors << :blue
134
+ # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
135
+ #
136
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
137
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
138
+ #
139
+ # class Person
140
+ # cattr_accessor :hair_colors, instance_writer: false, instance_reader: false
141
+ # end
142
+ #
143
+ # Person.new.hair_colors = [:brown] # => NoMethodError
144
+ # Person.new.hair_colors # => NoMethodError
145
+ #
146
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
147
+ #
148
+ # class Person
149
+ # cattr_accessor :hair_colors, instance_accessor: false
150
+ # end
151
+ #
152
+ # Person.new.hair_colors = [:brown] # => NoMethodError
153
+ # Person.new.hair_colors # => NoMethodError
154
+ #
155
+ # Also you can pass a block to set up the attribute with a default value.
156
+ #
157
+ # class Person
158
+ # cattr_accessor :hair_colors do
159
+ # [:brown, :black, :blonde, :red]
160
+ # end
161
+ # end
162
+ #
163
+ # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red]
164
+ def cattr_accessor(*syms, &blk)
165
+ cattr_reader(*syms)
166
+ cattr_writer(*syms, &blk)
167
+ end
168
+ end
@@ -0,0 +1,8 @@
1
+ motion_require '../object/acts_like'
2
+
3
+ class Date
4
+ # Duck-types as a Date-like class. See Object#acts_like?.
5
+ def acts_like_date?
6
+ true
7
+ end
8
+ end
@@ -0,0 +1,117 @@
1
+ motion_require "../date_and_time/calculations"
2
+
3
+ class Date
4
+ include DateAndTime::Calculations
5
+
6
+ class << self
7
+ attr_accessor :beginning_of_week_default
8
+
9
+ # Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=).
10
+ # 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>.
11
+ # If no config.beginning_of_week was specified, returns :monday.
12
+ def beginning_of_week
13
+ Thread.current[:beginning_of_week] || beginning_of_week_default || :monday
14
+ end
15
+
16
+ # Sets <tt>Date.beginning_of_week</tt> to a week start (e.g. :monday) for current request/thread.
17
+ #
18
+ # This method accepts any of the following day symbols:
19
+ # :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday
20
+ def beginning_of_week=(week_start)
21
+ Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start)
22
+ end
23
+
24
+ # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol.
25
+ def find_beginning_of_week!(week_start)
26
+ raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start)
27
+ week_start
28
+ end
29
+
30
+ # Returns a new Date representing the date 1 day ago (i.e. yesterday's date).
31
+ def yesterday
32
+ ::Date.current.yesterday
33
+ end
34
+
35
+ # Returns a new Date representing the date 1 day after today (i.e. tomorrow's date).
36
+ def tomorrow
37
+ ::Date.current.tomorrow
38
+ end
39
+
40
+ # Alias for Date.today.
41
+ def current
42
+ ::Date.today
43
+ end
44
+ end
45
+
46
+ # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
47
+ # and then subtracts the specified number of seconds.
48
+ def ago(seconds)
49
+ to_time.since(-seconds)
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 adds the specified number of seconds
54
+ def since(seconds)
55
+ to_time.since(seconds)
56
+ end
57
+ alias :in :since
58
+
59
+ # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00)
60
+ def beginning_of_day
61
+ to_time
62
+ end
63
+ alias :midnight :beginning_of_day
64
+ alias :at_midnight :beginning_of_day
65
+ alias :at_beginning_of_day :beginning_of_day
66
+
67
+ # Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59)
68
+ def end_of_day
69
+ to_time.end_of_day
70
+ end
71
+ alias :at_end_of_day :end_of_day
72
+
73
+ def plus_with_duration(other) #:nodoc:
74
+ if MotionSupport::Duration === other
75
+ other.since(self)
76
+ else
77
+ plus_without_duration(other)
78
+ end
79
+ end
80
+ alias_method :plus_without_duration, :+
81
+ alias_method :+, :plus_with_duration
82
+
83
+ def minus_with_duration(other) #:nodoc:
84
+ if MotionSupport::Duration === other
85
+ plus_with_duration(-other)
86
+ else
87
+ minus_without_duration(other)
88
+ end
89
+ end
90
+ alias_method :minus_without_duration, :-
91
+ alias_method :-, :minus_with_duration
92
+
93
+ # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
94
+ # any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
95
+ def advance(options)
96
+ options = options.dup
97
+ d = self
98
+ d = d >> options.delete(:years) * 12 if options[:years]
99
+ d = d >> options.delete(:months) if options[:months]
100
+ d = d + options.delete(:weeks) * 7 if options[:weeks]
101
+ d = d + options.delete(:days) if options[:days]
102
+ d
103
+ end
104
+
105
+ # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter.
106
+ # The +options+ parameter is a hash with a combination of these keys: <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>.
107
+ #
108
+ # Date.new(2007, 5, 12).change(day: 1) # => Date.new(2007, 5, 1)
109
+ # Date.new(2007, 5, 12).change(year: 2005, month: 1) # => Date.new(2005, 1, 12)
110
+ def change(options)
111
+ ::Date.new(
112
+ options.fetch(:year, year),
113
+ options.fetch(:month, month),
114
+ options.fetch(:day, day)
115
+ )
116
+ end
117
+ end
@@ -0,0 +1,56 @@
1
+ class Date
2
+ DATE_FORMATS = {
3
+ :short => '%e %b',
4
+ :long => '%B %e, %Y',
5
+ :db => '%Y-%m-%d',
6
+ :number => '%Y%m%d',
7
+ :long_ordinal => lambda { |date|
8
+ day_format = MotionSupport::Inflector.ordinalize(date.day)
9
+ date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007"
10
+ },
11
+ :rfc822 => '%e %b %Y'
12
+ }
13
+
14
+ # Convert to a formatted string. See DATE_FORMATS for predefined formats.
15
+ #
16
+ # This method is aliased to <tt>to_s</tt>.
17
+ #
18
+ # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
19
+ #
20
+ # date.to_formatted_s(:db) # => "2007-11-10"
21
+ # date.to_s(:db) # => "2007-11-10"
22
+ #
23
+ # date.to_formatted_s(:short) # => "10 Nov"
24
+ # date.to_formatted_s(:long) # => "November 10, 2007"
25
+ # date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
26
+ # date.to_formatted_s(:rfc822) # => "10 Nov 2007"
27
+ #
28
+ # == Adding your own time formats to to_formatted_s
29
+ # You can add your own formats to the Date::DATE_FORMATS hash.
30
+ # Use the format name as the hash key and either a strftime string
31
+ # or Proc instance that takes a date argument as the value.
32
+ #
33
+ # # config/initializers/time_formats.rb
34
+ # Date::DATE_FORMATS[:month_and_year] = '%B %Y'
35
+ # Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") }
36
+ def to_formatted_s(format = :default)
37
+ if formatter = DATE_FORMATS[format]
38
+ if formatter.respond_to?(:call)
39
+ formatter.call(self).to_s
40
+ else
41
+ strftime(formatter)
42
+ end
43
+ else
44
+ to_default_s
45
+ end
46
+ end
47
+ alias_method :to_default_s, :to_s
48
+ alias_method :to_s, :to_formatted_s
49
+
50
+ # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005"
51
+ def readable_inspect
52
+ strftime('%a, %d %b %Y')
53
+ end
54
+ alias_method :default_inspect, :inspect
55
+ alias_method :inspect, :readable_inspect
56
+ 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