weekling 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Weekling.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ require 'rake'
21
+ require 'pathname'
22
+ require 'bundler/gem_tasks'
23
+ require 'yard'
24
+ require 'rspec/core/rake_task'
25
+
26
+ RSpec::Core::RakeTask.new
27
+
28
+ YARD::Rake::YardocTask.new('doc')
29
+
30
+ desc "Removes temporary project files"
31
+ task :clean do
32
+ %w{doc coverage pkg .yardoc .rbx}.map{|name| Pathname.new(name) }.each do |path|
33
+ path.rmtree if path.exist?
34
+ end
35
+
36
+ Pathname.glob('*.gem').each &:delete
37
+ Pathname.glob('**/*.rbc').each &:delete
38
+ end
39
+
40
+ desc "Opens an interactive console with the library loaded"
41
+ task :console do
42
+ require 'pry'
43
+ require 'weekling'
44
+ Pry.start
45
+ end
46
+
47
+ task :default => :spec
@@ -0,0 +1,45 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Weekling.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ # Namespace for projects of Alexander E. Fischer <aef@raxys.net>.
21
+ #
22
+ # If you want to be able to simply type Example instead of Aef::Example to
23
+ # address classes in this namespace simply write the following before using the
24
+ # classes.
25
+ #
26
+ # @example Including the namespace
27
+ # include Aef
28
+ # @author Alexander E. Fischer
29
+ module Aef
30
+
31
+ # Namespace for components of the weekling gem
32
+ module Weekling
33
+
34
+ # The currently loaded library version
35
+ VERSION = '1.0.0'.freeze
36
+
37
+ end
38
+ end
39
+
40
+ require 'time'
41
+ require 'aef/weekling/year'
42
+ require 'aef/weekling/week'
43
+ require 'aef/weekling/week_day'
44
+ require 'aef/weekling/core_extensions/to_year'
45
+ require 'aef/weekling/core_extensions/to_week_and_week_day'
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Weekling.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ module Aef
21
+ module Weekling
22
+
23
+ # Namespace for extension modules for Ruby's core classes.
24
+ module CoreExtensions
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Weekling.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ module Aef
21
+ module Weekling
22
+ module CoreExtensions
23
+
24
+ # This module allows date-like objects to be extended to support
25
+ # to_year, to_week and to_week_day.
26
+ module ToWeekAndWeekDay
27
+
28
+ # Constructs an Aef::Weekling::Week from the object.
29
+ def to_week
30
+ Week.new(self)
31
+ end
32
+
33
+ # Constructs an Aef::Weekling::Week::Day from the object.
34
+ def to_week_day
35
+ WeekDay.new(self)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Weekling.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ module Aef
21
+ module Weekling
22
+ module CoreExtensions
23
+
24
+ # This module allows integer-like objects to be extended to support
25
+ # to_year.
26
+ module ToYear
27
+
28
+ # Constructs an Aef::Weekling::Year from the object.
29
+ def to_year
30
+ Year.new(self)
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,373 @@
1
+ # encoding: UTF-8
2
+ =begin
3
+ Copyright Alexander E. Fischer <aef@raxys.net>, 2012
4
+
5
+ This file is part of Weekling.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
12
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
13
+ FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
14
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
15
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
+ PERFORMANCE OF THIS SOFTWARE.
18
+ =end
19
+
20
+ require 'aef/weekling'
21
+
22
+ module Aef
23
+ module Weekling
24
+
25
+ # Immutable object representing a calendar week (according to ISO 8601).
26
+ class Week
27
+ include Comparable
28
+
29
+ # Regular expression for week extraction from strings.
30
+ # @private
31
+ PARSE_PATTERN = /(0|-?\d+)-W(0[1-9]|(?:1|2|3|4)\d|5(?:0|1|2|3))/
32
+
33
+ class << self
34
+ # Initializes the current week.
35
+ #
36
+ # @return [Aef::Weekling::Week] the current week
37
+ def today
38
+ today = Date.today
39
+
40
+ new(today.year, today.cweek)
41
+ end
42
+
43
+ alias now today
44
+
45
+ # Parses the first week out of a string.
46
+ #
47
+ # @note Looks for patterns like this:
48
+ # 2011-W03
49
+ # @param [String] string a string containing a week representation
50
+ # @return [Aef::Weekling::Week] the week parsed from input
51
+ # @raise [ArgumentError] if pattern cannot be found
52
+ def parse(string)
53
+ if sub_matches = PARSE_PATTERN.match(string.to_s)
54
+ original, year, index = *sub_matches
55
+ new(year.to_i, index.to_i)
56
+ else
57
+ raise ArgumentError, 'No week found for parsing'
58
+ end
59
+ end
60
+ end
61
+
62
+ # @return [Aef::Weekling::Year] the year the week is part of
63
+ attr_reader :year
64
+
65
+ # @return [Integer] the number of the week in its year
66
+ attr_reader :index
67
+
68
+ # @overload initialize(week)
69
+ # Initialize by a week-like object.
70
+ # @param [Aef::Weekling::Week] week a week-like object
71
+ #
72
+ # @overload initialize(date)
73
+ # Initialize by a date-like object.
74
+ # @param [Date, DateTime, Time] date a date-like object
75
+ #
76
+ # @overload initialize(year, index)
77
+ # Initialize by year and week number.
78
+ # @param [Integer, Aef::Weekling::Year] year a year
79
+ # @param [Integer] index a week index
80
+ def initialize(*arguments)
81
+ case arguments.count
82
+ when 1
83
+ object = arguments.first
84
+ if [:year, :index].all?{|method_name| object.respond_to?(method_name) }
85
+ @year = object.year.to_year
86
+ @index = object.index.to_i
87
+ elsif object.respond_to?(:to_date)
88
+ date = object.to_date
89
+ @year = Year.new(date.year)
90
+ @index = date.cweek
91
+ else
92
+ raise ArgumentError, 'A single argument must either respond to #year and #index or to #to_date'
93
+ end
94
+ when 2
95
+ year, index = *arguments
96
+ @year = Year.new(year)
97
+ @index = index.to_i
98
+ else
99
+ raise ArgumentError, "wrong number of arguments (#{arguments.count} for 1..2)"
100
+ end
101
+
102
+ if not (1..52).include?(@index)
103
+ if @index == 53
104
+ if @year.week_count == 52
105
+ raise ArgumentError, "Index #{@index} is invalid. Year #{@year} has only 52 weeks"
106
+ end
107
+ else
108
+ raise ArgumentError, "Index #{@index} is invalid. Index can never be lower than 1 or higher than 53"
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ # Represents a week as String in ISO 8601 format.
115
+ #
116
+ # @example Output of the 13th week in 2012
117
+ # Aef::Weekling::Week.new(2012, 13).to_s
118
+ # # => "2012-W13"
119
+ #
120
+ # @return [String] a character representation of the week
121
+ def to_s
122
+ "#{'%04i' % year}-W#{'%02i' % index}"
123
+ end
124
+
125
+ # Represents a week as String for debugging.
126
+ #
127
+ # @example Output of the 13th week in 2012
128
+ # Aef::Weekling::Week.new(2012, 13).inspect
129
+ # # => "#<Aef::Weekling::Week: 2012-W13>"
130
+ #
131
+ # @return [String] a character representation for debugging
132
+ def inspect
133
+ "#<#{self.class.name}: #{to_s}>"
134
+ end
135
+
136
+ # @return [Aef::Weekling::Week] self reference
137
+ def to_week
138
+ self
139
+ end
140
+
141
+ # @param [Aef::Weekling::Week] other a week-like object to be compared
142
+ # @return [true, false] true if other lies in the same year and has the
143
+ # same index
144
+ def ==(other)
145
+ other_week = self.class.new(other)
146
+
147
+ year == other_week.year and index == other_week.index
148
+ end
149
+
150
+ # @param [Aef::Weekling::Week] other a week object to be compared
151
+ # @return [true, false] true if other lies in the same year, has the same
152
+ # index and is of the same or a descending class
153
+ def eql?(other)
154
+ other.is_a?(self.class) and self == other
155
+ end
156
+
157
+ # @return [see Array#hash] identity hash for hash table usage
158
+ def hash
159
+ [year, index].hash
160
+ end
161
+
162
+ # Compares the week with another to determine its relative position.
163
+ #
164
+ # @param [Aef::Weekling::Week] other a week-like object to be compared
165
+ # @return [-1, 0, 1] -1 if other is greater, 0 if other is equal and 1 if
166
+ # other is lesser than self
167
+ def <=>(other)
168
+ other_week = self.class.new(other)
169
+
170
+ year_comparison = year <=> other_week.year
171
+
172
+ return index <=> other_week.index if year_comparison == 0
173
+ return year_comparison
174
+ end
175
+
176
+ # Finds the following week.
177
+ #
178
+ # @example The week after some other week
179
+ # some_week = Aef::Weekling::Week.new(2012, 5)
180
+ # some_week.next
181
+ # # => #<Aef::Weekling::Week: 2012-W06>
182
+ #
183
+ # @example The week after the last week of a year
184
+ # last_week_in_year = Aef::Weekling::Week.new(2012, 52)
185
+ # last_week_in_year.next
186
+ # # => #<Aef::Weekling::Week: 2013-W01>
187
+ #
188
+ # @return [Aef::Weekling::Week] the following week
189
+ def next
190
+ if index < 52
191
+ self.class.new(year, index + 1)
192
+ elsif year.week_count == 53 and index == 52
193
+ self.class.new(year, index + 1)
194
+ else
195
+ self.class.new(year.next, 1)
196
+ end
197
+ end
198
+
199
+ alias succ next
200
+
201
+ # Find the previous week.
202
+ #
203
+ # @example The week before some other week
204
+ # some_week = Aef::Weekling::Week.new(2012, 5)
205
+ # some_week.previous
206
+ # # => #<Aef::Weekling::Week: 2012-W04>
207
+ #
208
+ # @example The week before the first week of a year
209
+ # first_week_in_year = Aef::Weekling::Week.new(2016, 1)
210
+ # first_week_in_year.previous
211
+ # # => #<Aef::Weekling::Week: 2015-W53>
212
+ #
213
+ # @return [Aef::Weekling::Week] the previous week
214
+ def previous
215
+ if index > 1
216
+ self.class.new(year, index - 1)
217
+ elsif year.previous.week_count == 53
218
+ self.class.new(year.previous, 53)
219
+ else
220
+ self.class.new(year.previous, 52)
221
+ end
222
+ end
223
+
224
+ alias pred previous
225
+
226
+ # Adds weeks to the week.
227
+ #
228
+ # @example 28 weeks after 2007-W01
229
+ # Aef::Weekling::Week.new(2007, 1) + 28
230
+ # # => #<Aef::Weekling::Week: 2007-W29>
231
+ #
232
+ # @param [Integer] other number of weeks to add
233
+ # @return [Aef::Weekling::Week] the resulting week
234
+ def +(other)
235
+ result = self
236
+ number = other.to_i
237
+
238
+ number.abs.times do
239
+ if number < 0
240
+ result = result.previous
241
+ else
242
+ result = result.next
243
+ end
244
+ end
245
+
246
+ result
247
+ end
248
+
249
+ # Subtracts weeks from the week.
250
+ #
251
+ # @example 3 weeks before 2000-W03
252
+ # Aef::Weekling::Week.new(2000, 3) - 3
253
+ # # => #<Aef::Weekling::Week: 1999-W52>
254
+ #
255
+ # @param [Integer] other number of weeks to subtract
256
+ # @return [Aef::Weekling::Week] the resulting week
257
+ def -(other)
258
+ self + -other.to_i
259
+ end
260
+
261
+ # Returns a range of weeks beginning with self and ending with the first
262
+ # following week with the given index.
263
+ #
264
+ # @example End index higher than start index
265
+ # Aef::Weekling::Week.new(2012, 35).until_index(50)
266
+ # # => #<Aef::Weekling::Week: 2012-W35>..#<Aef::Weekling::Week: 2012-W50>
267
+ #
268
+ # @example End index lower or equal than start index
269
+ # Aef::Weekling::Week.new(2012, 35).until_index(11)
270
+ # # => #<Aef::Weekling::Week: 2012-W35>..#<Aef::Weekling::Week:2013-W11>
271
+ #
272
+ # @param [Integer] end_index the number of the last week in the result
273
+ # @return [Range<Aef::Weekling::Week>] range from self to the first
274
+ # following week with the given index
275
+ def until_index(end_index)
276
+ if end_index <= index
277
+ self .. self.class.new(year.next, end_index)
278
+ else
279
+ self .. self.class.new(year, end_index)
280
+ end
281
+ end
282
+
283
+ # States if the week's index is odd.
284
+ #
285
+ # @return [true, false] true if the week is odd
286
+ def odd?
287
+ @index.odd?
288
+ end
289
+
290
+ # States if the week's index is even.
291
+ #
292
+ # @return [true, false] true if the week is even
293
+ def even?
294
+ @index.even?
295
+ end
296
+
297
+ # @overload day(index)
298
+ # Returns a week-day by given index.
299
+ # @param [Integer] the index between 1 for monday and 7 for sunday
300
+ # @overload day(symbol)
301
+ # Returns a week-day by given symbol.
302
+ # @param [Symbol] the English name of the day in lowercase
303
+ # @return [Aef::Weekling::WeekDay] the specified day of the week
304
+ def day(index_or_symbol)
305
+ WeekDay.new(self, index_or_symbol)
306
+ end
307
+
308
+ # Returns the week's monday.
309
+ #
310
+ # @return [Aef::Weekling::WeekDay] monday of the week
311
+ def monday
312
+ day(:monday)
313
+ end
314
+
315
+ # Returns the week's tuesday.
316
+ #
317
+ # @return [Aef::Weekling::WeekDay] tuesday of the week
318
+ def tuesday
319
+ day(:tuesday)
320
+ end
321
+
322
+ # Returns the week's wednesday.
323
+ #
324
+ # @return [Aef::Weekling::WeekDay] wednesday of the week
325
+ def wednesday
326
+ day(:wednesday)
327
+ end
328
+
329
+ # Returns the week's thursday.
330
+ #
331
+ # @return [Aef::Weekling::WeekDay] thursday of the week
332
+ def thursday
333
+ day(:thursday)
334
+ end
335
+
336
+ # Returns the week's friday.
337
+ #
338
+ # @return [Aef::Weekling::WeekDay] friday of the week
339
+ def friday
340
+ day(:friday)
341
+ end
342
+
343
+ # Returns the week's saturday.
344
+ #
345
+ # @return [Aef::Weekling::WeekDay] saturday of the week
346
+ def saturday
347
+ day(:saturday)
348
+ end
349
+
350
+ # Returns the week's sunday.
351
+ #
352
+ # @return [Aef::Weekling::WeekDay] sunday of the week
353
+ def sunday
354
+ day(:sunday)
355
+ end
356
+
357
+ # Returns the week's saturday and sunday in an Array.
358
+ #
359
+ # @return [Array<Aef:Weekling::WeekDay>] the saturday and sunday of a week
360
+ def weekend
361
+ [saturday, sunday]
362
+ end
363
+
364
+ # Returns a range from monday to sunday.
365
+ #
366
+ # @return [Range<Aef::Weekling::WeekDay>] the days of the week
367
+ def days
368
+ monday..sunday
369
+ end
370
+
371
+ end
372
+ end
373
+ end