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.
- data/MIT-LICENSE +21 -0
- data/README.md +89 -0
- data/lib/week_sauce.rb +277 -0
- 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: []
|