Since Stitch Fix is a retail company at heart, we operate on the merchandising calendar. The merchandising calendar is used by the retail industry for accounting of sales, inventory and payroll. It originated in the 1930s and became widely adopted by the 1940s. The primary reason for its creation was to guarantee the same number of weekends in comparable months since a large percentage of retail sales occur on weekends. Also the calendar ensures that any end date of a period falls on the same day of the week.
Fiscal Year
An example you may be more familiar with is the fiscal year calendar. A specific month is chosen for the fiscal year end and in that month a day is chosen. If the last Saturday of the month is chosen in some arbitrary month, it will be comparable to all other fiscal year ends. Between fiscal years, day in week and week in month are both comparable.
4-5-4 Calendar
The “4-5-4” calendar is the most widely adopted merchandising calendar. Each quarter of the calendar is composed of 13 weeks. The weeks within a quarter are grouped into two 4-week months and one 5-week month. The order is: one 4-week month at the beginning, one 5-week month in the middle and one 4-week month at the end. Take a look at the layout in this 3 year “4-5-4” calendar. The first quarter of 2016 shown below demonstrates this pattern.
First Quarter of 2016
Here are the months in the first quarter of 2016. The “4-5-4” pattern is demonstrated below. Also note that the month of January is the end month of the previous merchandising year. Therefore January 2016 in the Gregorian calendar is January 2015 in the merchandising calendar.
February 2016:
- W1
- W2
- W3
- W4
March 2016:
- W1
- W2
- W3
- W4
- W5
April 2016:
- W1
- W2
- W3
- W4
The merch_calendar
gem
Since most of us mortals are far more comfortable with the Gregorian calendar, sometimes we need to convert between the the merchandising calendar and the Gregorian calendar. Doing this manually doesn’t lend itself well in the world of software. We ended up wrapping up the logic into a gem called the merch_calendar.
Original Implementation
Our first implementation used a simple database. An entry existed for each day in a year. These entries stored the Gregorian date, as well as the merchandising date. In addition to storing the dates, it also stored information about the quarter and season. Here is an example:
Field | Value |
---|---|
id |
20171230 |
merch_date |
Sat, 30 Dec 2017 |
merch_week_id |
48 |
merch_week_name |
“Dec W5” |
merch_year |
2017 |
last_merch_year |
2017 |
last_merch_week_id |
47 |
merch_week_description |
“2017:48 Dec W5” |
merch_month |
“Dec” |
last_merch_month |
“Nov” |
merch_quarter |
“Q2” |
last_merch_quarter |
“Q1” |
merch_season |
“Fall / Winter” |
This was less than ideal, but it worked well enough for some time. However, as we began to grow, the overhead of constantly querying that table began to show. Having the database also made testing more difficult - developers needed to have a copy of the merch_dates
table locally, otherwise they would run into problems.
We needed a solution that did not depend on pre-generated values, and that performed much faster than a database.
Current Implementation
The problems explained above led us to create the merch_calendar gem. This library can be easily shared across projects, and did not have any database dependencies. The library is also much more performant.
This library now allows us to replicate most of the functionality that the database provided. We are quickly able to convert Gregorian dates to merchandising dates, as well as gather information about the number of weeks in a given year.
The heart of the implementation lies in the start_of_month
method:
def start_of_month(year, merch_month)
# 91 = number of days in a single 4-5-4 set
start = start_of_year(year) + ((merch_month - 1) / 3).to_i * 91
case merch_month
# Months 1,4,7,10 are at the beginning of a 4-5-4 set
# thus, they are not shifted at all
# The second month in a 4-5-4 set.
# They need to be shifted 4 weeks
when 2,5,8,11
start = start + 28
# These are the 3rd months in a 4-5-4 set.
# They need to be shifted (4 weeks + 5 weeks)
when 3,6,9,12
start = start + 63
end
return start
end
This method calculates the 4-5-4 group that a month lies within, and then based on the month calculates the correct offset. In the library, nearly every other calculation depends upon this method.
By using the merch_calendar gem, we have significantly reduced both our database overhead and the amount of maintenance required to maintain a list of individual date mappings.
A special thanks should be given to Brian K for his excellent StackOverflow post that explained various implementations of merchandising calendars.