week_sauce 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 (4) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README.md +89 -0
  3. data/lib/week_sauce.rb +277 -0
  4. metadata +80 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Daniel Høier Øhrgaard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # WeekSauce
2
+
3
+ WeekSauce is a simple class that functions as a days-of-the-week bitmask.
4
+ Useful for things that repeat weekly, and/or can occur or one or more days
5
+ of the week.
6
+
7
+ Extracted from a Rails app, it's intended to be used an ActiveRecord
8
+ attribute serializer, but it should work fine outside of Rails.
9
+
10
+ ## Basic usage
11
+
12
+ week = WeekSauce.new(16) # set a specific bitmask
13
+ week.blank? #=> false
14
+ week.one? #=> true
15
+ week.thursday? #=> true
16
+
17
+ week = WeekSauce.new # defaults to a zero-bitmask
18
+ week.blank? #=> true
19
+
20
+ # Mark off the weekend
21
+ week.set(:saturday, :sunday)
22
+
23
+ from = Time.parse("2013-04-01") # A Monday
24
+ week.next_date(from) #=> "2013-04-06" (a Saturday)
25
+
26
+ week.dates_in(from..from + 1.week) => ["2013-04-06", "2013-04-07"]
27
+
28
+ ## Rails usage
29
+
30
+ class Workout < ActiveRecord::Base
31
+ serialize :days, WeekSauce
32
+ end
33
+
34
+ workout = Workout.find_by_kind("Weights")
35
+ workout.days.to_s #=> "Monday, Wednesday"
36
+ workout.days.set!(:tuesday, :thursday) # sets only those days
37
+ workout.save
38
+
39
+ ## API
40
+
41
+ Individual days can be set and read using Fixnums, symbols or Date/Time
42
+ objects. Additionally, there are named methods for getting and setting
43
+ each of the week's days:
44
+
45
+ # These are all equivalent
46
+ week.monday = true
47
+ week[:monday] = true
48
+ week[1] = true
49
+
50
+ time = Time.parse("2013-04-01") # A Monday
51
+ week[time] = true
52
+ week[time.to_date] = true
53
+
54
+ # And these are equivalent too
55
+ week.monday #=> true
56
+ week.monday? #=> true
57
+ week[:monday] #=> true
58
+ week[1] #=> true
59
+ week[time] #=> true
60
+
61
+ _Note that similar to `Date#wday`, Sunday is `0`, Monday is `1` and so forth._
62
+
63
+ There are also a few value-checking methods:
64
+
65
+ week.blank? # true if no days are set
66
+ week.one? # true if exactly 1 day is set
67
+ week.any? # true if at least 1 day is set
68
+ week.many? # true if at least 2 days are set
69
+ week.all? # true if all days are set
70
+
71
+ Several days can be set or unset using the so-named methods:
72
+
73
+ # arguments can also be Fixnums or Date/Time objects
74
+ week.set(:monday, :wednesday) # set those days to true
75
+ week.unset(:monday, :wednesday) # set those days to false
76
+
77
+ These also have "exclusive" versions
78
+
79
+ week.set!(:monday, :wednesday) # sets *only* those days to true, all others to false
80
+ week.unset!(:monday, :wednesday) # set *only* those days to false, all others to true
81
+
82
+ Lastly, dates matching the week's bitmask can be calculated
83
+
84
+ week.dates_in(from..to) # find matching dates in a range
85
+
86
+ week.next_date # finds next matching date from today
87
+ week.next_date(some_date) # finds next matching date from (and including) some_date
88
+
89
+ If ActiveSupport's time zone support is available, `next_date` with no argument will default to the time zone-aware `Date.current` instead of `Date.today`.
data/lib/week_sauce.rb ADDED
@@ -0,0 +1,277 @@
1
+ require 'date'
2
+
3
+ # WeekSauce is a simple class that functions as a days-of-the-week bitmask.
4
+ # Useful for things that repeat weekly, and/or can occur or one or more days
5
+ # of the week.
6
+ #
7
+ # Extracted from a Rails app, it's intended to be used an ActiveRecord
8
+ # attribute serializer, but it should work fine outside of Rails.
9
+ #
10
+ # == Basic usage
11
+ #
12
+ # week = WeekSauce.new(16) # set a specific bitmask
13
+ # week.blank? #=> false
14
+ # week.one? #=> true
15
+ # week.thursday? #=> true
16
+ #
17
+ # week = WeekSauce.new # defaults to a zero-bitmask
18
+ # week.blank? #=> true
19
+ #
20
+ # # Mark off the weekend
21
+ # week.set(:saturday, :sunday)
22
+ #
23
+ # from = Time.parse("2013-04-01") # A Monday
24
+ # week.next_date(from) #=> "2013-04-06" (a Saturday)
25
+ #
26
+ # week.dates_in(from..from + 1.week) => ["2013-04-06", "2013-04-07"]
27
+ #
28
+ # == Rails usage
29
+ #
30
+ # class Workout < ActiveRecord::Base
31
+ # serialize :days, WeekSauce
32
+ # end
33
+ #
34
+ # workout = Workout.find_by_kind("Weights")
35
+ # workout.days.to_s #=> "Monday, Wednesday"
36
+ # workout.days.set!(:tuesday, :thursday) # sets only those days
37
+ # workout.save
38
+ #
39
+ class WeekSauce
40
+ MAX_VALUE = 2**7 - 1
41
+ DAY_NAMES = %w(sunday monday tuesday wednesday thursday friday saturday).map(&:to_sym).freeze
42
+ DAY_BITS = Hash[ DAY_NAMES.zip(Array.new(7) { |i| 2**i }) ].freeze
43
+
44
+ class << self
45
+ # ActiveRecord attribute serialization support
46
+ #
47
+ # Create a WeekSauce instance from a stringified integer
48
+ # bitmask. The value will be clamped (see #new)
49
+ def load(string)
50
+ self.new(string.to_i)
51
+ rescue NoMethodError => err
52
+ self.new
53
+ end
54
+
55
+ # ActiveRecord attribute serialization support
56
+ #
57
+ # Dump a WeekSauce instance to a stringified instance bitmask
58
+ def dump(instance)
59
+ if instance.is_a?(self)
60
+ instance.to_i.to_s
61
+ else
62
+ "0"
63
+ end
64
+ end
65
+ end
66
+
67
+ # Init a new WeekSauce instance. If +value+ is omitted, the new
68
+ # instance will default to a bitmask of zero, i.e. no days set.
69
+ #
70
+ # If a +value+ argument is given, +to_i+ will be called on it,
71
+ # and the resulting integer will be clamped to 0..127
72
+ def initialize(value = nil)
73
+ @value = [[0, value.to_i].max, MAX_VALUE].min
74
+ end
75
+
76
+ # Compare this instance against another instance, or a Fixnum
77
+ def ==(arg)
78
+ case arg
79
+ when self.class, Fixnum
80
+ to_i == arg.to_i
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ # Returns +true+ if no days are set, +false+ otherwise.
87
+ # Opposite of #any?
88
+ def blank?
89
+ @value == 0
90
+ end
91
+
92
+ # Returns +true+ if all days are set, +false+ otherwise
93
+ def all?
94
+ @value == MAX_VALUE
95
+ end
96
+
97
+ # Returns +true+ if any of the week's 7 days are set, +false+ otherwise.
98
+ # Opposite of #blank?
99
+ def any?
100
+ !blank?
101
+ end
102
+
103
+ # Returns +true+ if exactly one day is set, +false+ otherwise
104
+ def one?
105
+ any? && @value & (@value - 1) == 0
106
+ end
107
+
108
+ # Returns +true+ if two or days are set, +false+ otherwise
109
+ def many?
110
+ any? && !one?
111
+ end
112
+
113
+ DAY_BITS.each do |day, bit|
114
+ define_method(day) do
115
+ get_bit bit
116
+ end
117
+ alias_method :"#{day.to_s}?", day
118
+
119
+ define_method("#{day.to_s}=") do |bool|
120
+ set_bit bit, bool
121
+ end
122
+ end
123
+
124
+ # Returns +true+ if the given weekday is set, +false+ if it isn't,
125
+ # and +nil+ if the argument was invalid.
126
+ #
127
+ # The +wday+ argument can be a Fixnum from 0 (Sunday) to 6 (Saturday),
128
+ # a symbol specifying the day's name (e.g. +:tuesday+, +:friday+), or
129
+ # a Date or Time instance
130
+ def [](wday)
131
+ get_bit coerce_to_bit(wday)
132
+ end
133
+
134
+ # Set or unset the given day. See #[] for possible +wday+ values.
135
+ # Invalid +wday+ values are ignored.
136
+ def []=(wday, bool)
137
+ set_bit coerce_to_bit(wday), bool
138
+ end
139
+
140
+ # Set the given days. Like #[], arguments can be symbols, Fixnums,
141
+ # or Date/Time objects
142
+ def set(*days)
143
+ days.each do |day|
144
+ self[day] = true
145
+ end
146
+ end
147
+
148
+ # Exclusive version of #set - clears the week, and sets only
149
+ # the days passed. Returns +self+
150
+ def set!(*days)
151
+ blank!
152
+ set(*days)
153
+ self
154
+ end
155
+
156
+ # Unset the given days. Like #[], arguments can be symbols,
157
+ # Fixnums, or Date/Time objects
158
+ def unset(*days)
159
+ days.each do |day|
160
+ self[day] = false
161
+ end
162
+ end
163
+
164
+ # Exclusive version of #unset - sets all days to true and
165
+ # then unsets days passed. Returns +self+
166
+ def unset!(*days)
167
+ all!
168
+ unset(*days)
169
+ self
170
+ end
171
+
172
+ # Set all days to +false+. Returns +self+
173
+ def blank!
174
+ @value = 0
175
+ self
176
+ end
177
+
178
+ # Set all days to +true+. Returns +self+
179
+ def all!
180
+ @value = MAX_VALUE
181
+ self
182
+ end
183
+
184
+ # Returns the raw bitmask integer
185
+ def to_i
186
+ @value
187
+ end
188
+
189
+ # Returns an array of "set" day names as symbols
190
+ def to_a
191
+ DAY_NAMES.select { |day| self[day] }
192
+ end
193
+
194
+ # Returns a string with the bitmask value and a list of
195
+ # "set" days, or a simple message if all/no days are set
196
+ def inspect
197
+ if blank?
198
+ "0: No days set"
199
+ elsif all?
200
+ "#{MAX_VALUE}: All days set"
201
+ else
202
+ list = to_a.map { |day| day.to_s.sub(/./, &:upcase) }.join(", ")
203
+ "#{@value}: #{list}"
204
+ end
205
+ end
206
+
207
+ # Returns a hash where the keys are the week's 7 days
208
+ # as symbols, and the values are booleans
209
+ def to_hash
210
+ Hash[ DAY_NAMES.map { |day| [day, self[day]] } ]
211
+ end
212
+
213
+ # Return the next date matching the bitmask, or +nil+ if the
214
+ # week is blank.
215
+ #
216
+ # If no +from_date+ argument is given, it'll default to
217
+ # <tt>Date.current</tt> if ActiveSupport is available,
218
+ # or <tt>Date.today</tt> otherwise.
219
+ #
220
+ # If +from_date+ is given, #next_date will return the first
221
+ # matching date from - and including - +from_date+
222
+ def next_date(from_date = nil)
223
+ return nil if blank?
224
+ from_date ||= if Date.respond_to?(:current)
225
+ Date.current
226
+ else
227
+ Date.today
228
+ end
229
+ from_date = from_date.to_date
230
+ until self[from_date.wday]
231
+ from_date = from_date.succ
232
+ end
233
+ from_date.dup
234
+ end
235
+
236
+ # Return all dates in the given +date_range+ that match the
237
+ # bitmask. If the week's blank, an empty array will be
238
+ # returned.
239
+ #
240
+ # Note that the range is converted to an array, which
241
+ # is then filtered, so if the range is "backwards" (high to
242
+ # low) an empty array will be returned
243
+ def dates_in(date_range)
244
+ return [] if blank?
245
+ date_range.to_a.select { |date| self[date.wday] }
246
+ end
247
+
248
+ protected
249
+
250
+ def get_bit(bit) #:nodoc:
251
+ if DAY_BITS.values.include?(bit)
252
+ @value & bit > 0
253
+ end
254
+ end
255
+
256
+ def set_bit(bit, set) #:nodoc:
257
+ if DAY_BITS.values.include?(bit)
258
+ if set
259
+ @value = @value | bit
260
+ else
261
+ @value = @value & (MAX_VALUE ^ bit)
262
+ end
263
+ end
264
+ end
265
+
266
+ def coerce_to_bit(wday) #:nodoc:
267
+ case wday
268
+ when Symbol
269
+ DAY_BITS[wday]
270
+ when Fixnum
271
+ (0..6).include?(wday) ? 2**wday : nil
272
+ when Date, Time
273
+ 2**wday.wday
274
+ end
275
+ end
276
+
277
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: week_sauce
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Daniel Høier Øhrgaard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: tzinfo
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.3.29
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.3.29
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 3.2.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 3.2.0
46
+ description: A simple gem to serialize selected days of the week as a bitmask
47
+ email: daniel@stimulacrum.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - lib/week_sauce.rb
53
+ - README.md
54
+ - MIT-LICENSE
55
+ homepage: https://github.com/Flambino/week_sauce
56
+ licenses:
57
+ - MIT
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: 1.9.3
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 1.8.23
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Day-of-week bitmask
80
+ test_files: []