merch_calendar 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 94429998c8a78882cafaba7e479b5a8af2e75b08
4
+ data.tar.gz: d1ece80b55e67aa6dd9236888095770f13a7aedb
5
+ SHA512:
6
+ metadata.gz: 505cbc69d351c12bb201d6d720c2504542a2d4f3998983bf006580a89fb9c72b4cd63e5b18ae301d69c7cb17a38938b8e9ccd819bf1ca9e15f49915eb05cc458
7
+ data.tar.gz: caad2d4f70d937baaf692eca58f46829644c318a9436b9bb14029f62182c520f3ad2a8c009b261fedec3861122221c5da46108d215abc7c2de10060d091951f1
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ coverage/
2
+ doc
3
+ .yardoc/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.0
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://www.rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,71 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ merch_calendar (0.0.1)
5
+
6
+ GEM
7
+ remote: https://www.rubygems.org/
8
+ specs:
9
+ coderay (1.1.0)
10
+ coveralls (0.8.1)
11
+ json (~> 1.8)
12
+ rest-client (>= 1.6.8, < 2)
13
+ simplecov (~> 0.10.0)
14
+ term-ansicolor (~> 1.3)
15
+ thor (~> 0.19.1)
16
+ diff-lcs (1.2.5)
17
+ docile (1.1.5)
18
+ domain_name (0.5.24)
19
+ unf (>= 0.0.5, < 1.0.0)
20
+ http-cookie (1.0.2)
21
+ domain_name (~> 0.5)
22
+ json (1.8.2)
23
+ method_source (0.8.2)
24
+ mime-types (2.5)
25
+ netrc (0.10.3)
26
+ pry (0.10.1)
27
+ coderay (~> 1.1.0)
28
+ method_source (~> 0.8.1)
29
+ slop (~> 3.4)
30
+ rake (10.4.2)
31
+ rest-client (1.8.0)
32
+ http-cookie (>= 1.0.2, < 2.0)
33
+ mime-types (>= 1.16, < 3.0)
34
+ netrc (~> 0.7)
35
+ rspec (3.2.0)
36
+ rspec-core (~> 3.2.0)
37
+ rspec-expectations (~> 3.2.0)
38
+ rspec-mocks (~> 3.2.0)
39
+ rspec-core (3.2.2)
40
+ rspec-support (~> 3.2.0)
41
+ rspec-expectations (3.2.0)
42
+ diff-lcs (>= 1.2.0, < 2.0)
43
+ rspec-support (~> 3.2.0)
44
+ rspec-mocks (3.2.1)
45
+ diff-lcs (>= 1.2.0, < 2.0)
46
+ rspec-support (~> 3.2.0)
47
+ rspec-support (3.2.2)
48
+ simplecov (0.10.0)
49
+ docile (~> 1.1.0)
50
+ json (~> 1.8)
51
+ simplecov-html (~> 0.10.0)
52
+ simplecov-html (0.10.0)
53
+ slop (3.6.0)
54
+ term-ansicolor (1.3.0)
55
+ tins (~> 1.0)
56
+ thor (0.19.1)
57
+ tins (1.5.1)
58
+ unf (0.1.4)
59
+ unf_ext
60
+ unf_ext (0.0.7.1)
61
+
62
+ PLATFORMS
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ coveralls
67
+ merch_calendar!
68
+ pry
69
+ rake
70
+ rspec (>= 3.0.0)
71
+ simplecov
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015 Stitch Fix
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # Merch Calendar
2
+
3
+ [![Build Status](https://travis-ci.org/stitchfix/merch_calendar.svg)](https://travis-ci.org/stitchfix/merch_calendar)
4
+ [![Code Climate](https://codeclimate.com/github/stitchfix/merch_calendar/badges/gpa.svg)](https://codeclimate.com/github/stitchfix/merch_calendar)
5
+ [![Coverage Status](https://coveralls.io/repos/stitchfix/merch_calendar/badge.svg)](https://coveralls.io/r/stitchfix/merch_calendar)
6
+
7
+ This gem allows for finding retail/merchandising weeks for a given date, along with manipulating the retail calendar. This gem is used at [StitchFix](http://www.stitchfix.com/).
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ $ gem install merch_calendar
13
+ ```
14
+
15
+ Add the following line to your `Gemfile`:
16
+ ```ruby
17
+ gem "merch_calendar"
18
+ ```
19
+
20
+
21
+ ## Configuration
22
+ ```ruby
23
+ # NOTE: Configuration will be added soon, but is currently NOT available.
24
+ MerchCalendar.configure do |config|
25
+ # The month that Q1 begins. The default is 8 (August)
26
+ config.quarter_start_month = 8
27
+ end
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ For converting a date into a `MerchWeek` object.
33
+
34
+ ```ruby
35
+ merch_week = MerchCalendar::MerchWeek.from_date("2014-01-01")
36
+ puts merch_week.year # 2013 (the merch year associated with this date)
37
+ puts merch_week.month # 12 (the julian month that the date falls in)
38
+ puts merch_week.week # 5 (the week number within the month)
39
+ puts merch_week.year_week # 48 (the week number within the year)
40
+ puts merch_week.quarter # 2
41
+
42
+ puts merch_week.start_of_week # <Date: 2013-12-29>
43
+ puts merch_week.end_of_week # <Date>
44
+
45
+ puts merch_week.start_of_month # <Date>
46
+ puts merch_week.end_of_month # <Date>
47
+
48
+ puts merch_week.start_of_quarter # <Date>
49
+ puts merch_week.end_of_quarter # <Date>
50
+
51
+ puts merch_week.start_of_year # <Date>
52
+ puts merch_week.end_of_year # <Date>
53
+
54
+ # Formatting
55
+ puts merch_week.to_s # "Dec W5"
56
+ puts merch_week.to_s(:short) # "Dec W5"
57
+ puts merch_week.to_s(:long) # "2013:48 Dec W5"
58
+ puts merch_week.to_s(:elasticsearch) # "2013-12w05"
59
+ ```
60
+
61
+ This can also be used on the `MerchCalendar` module. All `start_` and `end_` methods can be called, along with a few additional ones.
62
+
63
+ ```ruby
64
+ # All examples below return a Date object for the start of May within the 2014 merch year
65
+ MerchCalendar.start_of_month(2014, 5)
66
+ MerchCalendar.start_of_month(2014, month: 5)
67
+ MerchCalendar.start_of_month(2014, julian_month: 5)
68
+
69
+ # This is the same as May, because "Merch" months are shifted by 1.
70
+ # i.e. month 1 is actually February
71
+ # You probably will never use this, but it is available.
72
+ MerchCalendar.start_of_month(2014, merch_month: 4)
73
+ ```
74
+
75
+ Other useful methods:
76
+
77
+ ```ruby
78
+ # 52 or 53 (depending on leap year)
79
+ MerchCalendar.weeks_in_year(2015)
80
+
81
+ # returns an array of MerchWeek objects for each week within the provided month
82
+ MerchCalendar.weeks_for_month(2014, 1)
83
+ ```
84
+
85
+ ## Documentation
86
+ You can view the documentation for this gem on [RubyDoc.info](http://www.rubydoc.info/github/stitchfix/merch_calendar/master).
87
+
88
+
89
+ ## Roadmap
90
+ * Support for 4-4-5 calendars
91
+
92
+ ## License
93
+ MerchCalendar is released under the [MIT License](http://www.opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems/package_task'
2
+ require 'rspec/core/rake_task'
3
+ require "bundler/gem_tasks"
4
+
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task default: :spec
@@ -0,0 +1,28 @@
1
+ module MerchCalendar
2
+ class << self
3
+
4
+ attr_writer :configuration
5
+
6
+ # Returns the global configuration object
7
+ #
8
+ # @return [Configuration]
9
+ def configuration
10
+ @configuration ||= Configuration.new
11
+ end
12
+
13
+ # Used in initializers to set the global configuration
14
+ #
15
+ # @return [void]
16
+ def configure
17
+ yield(configuration)
18
+ end
19
+
20
+ # Resets the configuration to default values
21
+ #
22
+ # @return [void]
23
+ def reset_config!
24
+ @configuration = Configuration.new
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,13 @@
1
+ module MerchCalendar
2
+ # @note WARNING: THIS IS NOT BEING USED YET
3
+ class Configuration
4
+
5
+ # The JULIAN month of the quarter start
6
+ attr_accessor :quarter_start_month
7
+
8
+ def initialize
9
+ @quarter_start_month = 8
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,103 @@
1
+ module MerchCalendar
2
+
3
+ # @api private
4
+ class DateCalculator
5
+
6
+ def end_of_year(year)
7
+ year_end = Date.new (year + 1), 1, -1
8
+ wday = (year_end.wday + 1) % 7
9
+
10
+ if wday > 3
11
+ year_end += 7 - wday
12
+ elsif wday > 0
13
+ year_end -= wday
14
+ end
15
+ year_end
16
+ end
17
+
18
+ # The day after last years' end date
19
+ def start_of_year(year)
20
+ end_of_year(year - 1) + 1
21
+ end
22
+
23
+ # The starting date of a given month
24
+ # THIS IS THE MERCH MONTH
25
+ # 1 = feb
26
+ #
27
+ def start_of_month(year, merch_month)
28
+ # 91 = number of days in a single 4-5-4 set
29
+ start = start_of_year(year) + ((merch_month - 1) / 3).to_i * 91
30
+
31
+ case merch_month
32
+ when 2,5,8,11
33
+ # 28 = 4 weeks
34
+ start = start + 28
35
+ when 3,6,9,12
36
+ # The 5 week months
37
+ # 63 = 4 weeks + 5 weeks
38
+ start = start + 63
39
+ end
40
+
41
+ start
42
+ end
43
+
44
+ def end_of_month(year, merch_month)
45
+ if merch_month == 12
46
+ end_of_year(year)
47
+ else
48
+ start_of_month(year, merch_month + 1) - 1
49
+ end
50
+ end
51
+
52
+ # Return the starting date for a particular quarter
53
+ def start_of_quarter(year, quarter)
54
+ case quarter
55
+ when 1
56
+ start_of_month(year, 7)
57
+ when 2
58
+ start_of_month(year, 10)
59
+ when 3
60
+ start_of_month(year, 1)
61
+ when 4
62
+ start_of_month(year, 4)
63
+ end
64
+ end
65
+
66
+ # Return the ending date for a particular quarter
67
+ def end_of_quarter(year, quarter)
68
+ case quarter
69
+ when 1
70
+ end_of_month(year, 9)
71
+ when 2
72
+ end_of_month(year, 12)
73
+ when 3
74
+ end_of_month(year, 3)
75
+ when 4
76
+ end_of_month(year, 6)
77
+ end
78
+ end
79
+
80
+ # Return the number of weeks in a particular year
81
+ def weeks_in_year(year)
82
+ ((start_of_year(year + 1) - start_of_year(year)) / 7).to_i
83
+ end
84
+
85
+ def merch_to_julian(merch_month)
86
+ if merch_month == 12
87
+ 1
88
+ else
89
+ merch_month + 1
90
+ end
91
+ end
92
+
93
+ def julian_to_merch(julian_month)
94
+ if julian_month == 1
95
+ 12
96
+ else
97
+ julian_month - 1
98
+ end
99
+ end
100
+
101
+
102
+ end
103
+ end
@@ -0,0 +1,224 @@
1
+ module MerchCalendar
2
+
3
+ # Represents the Merch Week for a specified date.
4
+ class MerchWeek
5
+
6
+ MONTHS = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec).freeze
7
+
8
+ # The Julian date that is being represented
9
+ #
10
+ # @!attribute [r] date
11
+ # @return [Date] the date for this merch week
12
+ attr_reader :date
13
+
14
+ class << self
15
+
16
+ # Locates the +MerchWeek+ for a given Julian date.
17
+ #
18
+ # @overload from_date(String)
19
+ # @param [String] julian_date a julian date in the format of +YYYY-MM-DD+
20
+ # @overload from_date(Date)
21
+ # @param [Date] julian_date a +Date+ object
22
+ #
23
+ # @return [MerchWeek]
24
+ def from_date(julian_date)
25
+ MerchWeek.new Date.parse("#{julian_date}")
26
+ end
27
+
28
+ # Returns an array of merch weeks for a month, or a specific week.
29
+ #
30
+ # @overload find(year, julian_month)
31
+ # Returns an array of +MerchWeek+s for a given month
32
+ # @param year [Fixnum] the merch year to locate
33
+ # @param julian_month [Fixnum] the month to find merch months for
34
+ # @return [Array<MerchWeek>]
35
+ # @overload find(year, julian_month, week_number)
36
+ # @param year [Fixnum] the merch year to locate
37
+ # @param julian_month [Fixnum] the month to find merch months for
38
+ # @param week_number [Fixnum] the specific week number.
39
+ # @return [MerchWeek] the specific merch week
40
+ def find(year, julian_month, week_number=nil)
41
+ if week_number.nil?
42
+ MerchCalendar.weeks_for_month(year, julian_month)
43
+ else
44
+ MerchCalendar.weeks_for_month(year, julian_month)[week_number-1]
45
+ end
46
+ end
47
+
48
+ # Returns the +MerchWeek+ for today's date
49
+ #
50
+ # @return [MerchWeek]
51
+ def today
52
+ MerchWeek.from_date Date.today
53
+ end
54
+ end
55
+
56
+
57
+
58
+ # Pass in a date, make sure it is a valid date object
59
+ # @private
60
+ def initialize(date, options = {})
61
+ @date = date
62
+
63
+ # Placeholders. These should be populated by functions if nil
64
+ # week_start: nil, week_end: nil, week_number: nil
65
+ @start_of_year = options[:start_of_year]
66
+ @end_of_year = options[:end_of_year]
67
+
68
+ @start_of_week = options[:start_of_week]
69
+ @end_of_week = options[:end_of_week]
70
+ @week = options[:week]
71
+
72
+ @start_of_month = options[:start_of_month]
73
+ @end_of_month = options[:end_of_month]
74
+ end
75
+
76
+ # What week it is within the year from 1-53
77
+ #
78
+ # @return [Fixnum] The week number of the year, from 1-53
79
+ def year_week
80
+ @year_week ||= (((date-start_of_year)+1)/7.0).ceil
81
+ end
82
+
83
+ # This returns the "merch month" number for a date
84
+ # Merch months are shifted by one. Month 1 is Feb
85
+ #
86
+ # @return [Fixnum]
87
+ def merch_month
88
+ # TODO: This is very inefficient, but less complex than strategic guessing
89
+ # maybe switch to a binary search or something
90
+ @merch_month ||= (1..12).detect do |num|
91
+ date_calc.end_of_month(start_of_year.year, num) >= date && date >= date_calc.start_of_month(start_of_year.year, num)
92
+ end
93
+ end
94
+
95
+ # The merch year
96
+ #
97
+ # @return [Fixnum]
98
+ def year
99
+ start_of_year.year
100
+ end
101
+
102
+ # The julian month that this merch week falls in
103
+ #
104
+ # @return [Fixnum]
105
+ def month
106
+ @month ||= date_calc.merch_to_julian(merch_month)
107
+ end
108
+
109
+ # The specific quarter this week falls in
110
+ #
111
+ # @return [Fixnum]
112
+ def quarter
113
+ case merch_month
114
+ when 7,8,9
115
+ return 1
116
+ when 10,11,12
117
+ return 2
118
+ when 1,2,3
119
+ return 3
120
+ else
121
+ return 4
122
+ end
123
+ end
124
+
125
+ # Returns the date of the start of this week
126
+ #
127
+ # @return [Date]
128
+ def start_of_week
129
+ @start_of_week ||= (start_of_month + (7 * (week - 1)))
130
+ end
131
+
132
+ # Returns the date of the end of this week
133
+ #
134
+ # @return [Date]
135
+ def end_of_week
136
+ @end_of_week ||= (start_of_week + 6)
137
+ end
138
+
139
+ # the number of the week within the given month
140
+ # will be between 1 and 5
141
+ #
142
+ # @return [Fixnum]
143
+ def week
144
+ @week ||= (((date-start_of_month)+1)/7.0).ceil
145
+ end
146
+
147
+ # The date of the start of the corresponding merch year
148
+ #
149
+ # @return [Date]
150
+ def start_of_year
151
+ @start_of_year ||= year_start_date
152
+ end
153
+
154
+ # The end date of the corresponding merch year
155
+ #
156
+ # @return [Date]
157
+ def end_of_year
158
+ @end_of_year ||= date_calc.end_of_year(year)
159
+ end
160
+
161
+ # The start date of the merch month
162
+ #
163
+ # @return [Date]
164
+ def start_of_month
165
+ @start_of_month ||= date_calc.start_of_month(year, merch_month)
166
+ end
167
+
168
+ # The end date of the merch month
169
+ #
170
+ # @return [Date]
171
+ def end_of_month
172
+ @end_of_month ||= date_calc.end_of_month(year, merch_month)
173
+ end
174
+
175
+ # The merch season this date falls under.
176
+ # Returns a string of +Fall/Winter+ or +Spring/Summer+
177
+ #
178
+ # @return [String]
179
+ def season
180
+ case merch_month
181
+ when 1,2,3,4,5,6
182
+ "Spring/Summer"
183
+ when 7,8,9,10,11,12
184
+ "Fall/Winter"
185
+ end
186
+ end
187
+
188
+ # Outputs a text representation of this merch week
189
+ #
190
+ # Format keys:
191
+ # * +:short+ (default) "Dec W5"
192
+ # * +:long+ "2012:48 Dec W5"
193
+ # * +:elasticsearch+ (default) "2012-12w05"
194
+ #
195
+ # @param format [Symbol] the format identifier to return. Default is +:short+
196
+ #
197
+ # @return [Date]
198
+ def to_s(format = :short)
199
+ case format
200
+ when :elasticsearch
201
+ sprintf("%04d-%02dw%02d", year, month, week)
202
+ when :long
203
+ "#{year}:#{year_week} #{self.to_s(:short)}"
204
+ else
205
+ "#{MONTHS[month - 1]} W#{week}"
206
+ end
207
+ end
208
+
209
+ private
210
+
211
+ def year_start_date
212
+ start_date = date_calc.start_of_year(date.year)
213
+ if start_date > date
214
+ start_date = date_calc.start_of_year(date.year - 1)
215
+ end
216
+ start_date
217
+ end
218
+
219
+ def date_calc
220
+ @date_calc ||= DateCalculator.new
221
+ end
222
+
223
+ end
224
+ end