Skip to content

Commit

Permalink
Merge f6203fd into de7bdeb
Browse files Browse the repository at this point in the history
  • Loading branch information
chylauSF committed Aug 16, 2017
2 parents de7bdeb + f6203fd commit 285823f
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 183 deletions.
1 change: 0 additions & 1 deletion lib/merch_calendar.rb
@@ -1,6 +1,5 @@
require "date"

#
module MerchCalendar
DEPRECATION_DATE = Date.new(2018, 1, 1)
end
Expand Down
144 changes: 72 additions & 72 deletions lib/merch_calendar/fiscal_year_calendar.rb
@@ -1,118 +1,118 @@
require "merch_calendar/retail_calendar"

module MerchCalendar
class FiscalYearCalendar
# Stitch Fix's fiscal year starts two quarters *before* (hence the negative number) the start of the
# merch/retail calendar year.
STITCH_FIX_FY_QUARTER_OFFSET = -2

QUARTER_1 = 1
QUARTER_2 = 2
QUARTER_3 = 3
QUARTER_4 = 4

# @param fy_quarter_offset [Fixnum]
# The number of quarters before or after the start of the traditional NRF retail calendar that the year
# should begin.
# ex) Stitch Fix's fiscal year calendar starts in August of the prior gregorian calendar year.
# February 2017 = Traditional retail month 1, year 2017
# August 2016 = Offset retail month 1, year 2017 (2 quarters earlier)
def initialize(fy_quarter_offset = STITCH_FIX_FY_QUARTER_OFFSET)
@fy_quarter_offset = fy_quarter_offset

# TODO: support other fiscal year offsets
if fy_quarter_offset != STITCH_FIX_FY_QUARTER_OFFSET
raise NotImplementedError.new("FY quarter offset of #{fy_quarter_offset} not yet supported")
end

@retail_calendar = RetailCalendar.new
end

FOUR_WEEK_MONTHS = [2, 5, 8, 11]
FIVE_WEEK_MONTHS = [3, 6, 9, 12]

# The date of the first day of the year
def start_of_year(year)
start_of_quarter(year, QUARTER_1)
end_of_year(year - 1) + 1
end

# The date of the last day of the year
# The date of the last day of the year
def end_of_year(year)
end_of_quarter(year, QUARTER_4)
year_end = Date.new((year), 7, -1)
wday = (year_end.wday + 1) % 7

if wday > 3
year_end += 7 - wday
else
year_end -= wday
end
year_end
end

# Return the starting date for a particular quarter
def start_of_quarter(year, quarter)
@retail_calendar.start_of_quarter(*offset_quarter(year, quarter))
case quarter
when QUARTER_1
start_of_month(year, 1)
when QUARTER_2
start_of_month(year, 4)
when QUARTER_3
start_of_month(year, 7)
when QUARTER_4
start_of_month(year, 10)
end
end

# Return the ending date for a particular quarter
def end_of_quarter(year, quarter)
@retail_calendar.end_of_quarter(*offset_quarter(year, quarter))
case quarter
when QUARTER_1
end_of_month(year, 3)
when QUARTER_2
end_of_month(year, 6)
when QUARTER_3
end_of_month(year, 9)
when QUARTER_4
end_of_month(year, 12)
end
end

# The date of the first day of the merch month
# @param [Fixnum] year - the fiscal year
# @param [Fixnum] merch_month - the nth month of the offset calendar
# ex) for an offset of +/- 2 quarters, month 1 = August
def start_of_month(year, merch_month)
@retail_calendar.start_of_month(*offset_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
when *FOUR_WEEK_MONTHS
# 28 = 4 weeks
start = start + 28
when *FIVE_WEEK_MONTHS
# The 5 week months
# 63 = 4 weeks + 5 weeks
start = start + 63
end

start
end

# The date of the last day of the merch month
# @param [Fixnum] year - the fiscal year
# @param [Fixnum] merch_month - the nth month of the offset calendar
# ex) for an offset of +/- 2 quarters, month 1 = August
def end_of_month(year, merch_month)
@retail_calendar.end_of_month(*offset_month(year, merch_month))
if merch_month == 12
end_of_year(year)
else
start_of_month(year, merch_month + 1) - 1
end
end

# Returns the date that corresponds to the first day in the merch week
def start_of_week(year, merch_month, merch_week)
@retail_calendar.start_of_week(*offset_month(year, merch_month), merch_week)
def start_of_week(year, month, merch_week)
start_of_month(year, month) + ((merch_week - 1) * 7)
end

# Returns the date that corresponds to the last day in the merch week
def end_of_week(year, merch_month, merch_week)
@retail_calendar.end_of_week(*offset_month(year, merch_month), merch_week)
def end_of_week(year, month, merch_week)
start_of_month(year, month) + (6 + ((merch_week - 1) * 7))
end

# Returns the number of weeks in the fiscal year
def weeks_in_year(year)
@retail_calendar.weeks_in_year(offset_year(year))
((start_of_year(year + 1) - start_of_year(year)) / 7).to_i
end

private

# Offsets the quarter based on the fiscal year quarter offset
# returns: offset [year, quarter]
def offset_quarter(year, quarter)
# first quarter in fiscal calendar is Q3 of retail calendar of previous year
if quarter >= 1 + @fy_quarter_offset.abs
[year, quarter + @fy_quarter_offset]
else
[year - 1, quarter - @fy_quarter_offset]
def merch_months_in(start_date, end_date)
merch_months = []
prev_date = start_date - 2
date = start_date
while date <= end_date do
date = MerchCalendar.start_of_month(date.year, merch_month: date.month)
next if prev_date == date
merch_months.push(date)
prev_date = date
date += 14
end
merch_months
end

# Offsets the month based on the fiscal year quarter offset
# returns: offset [year, month]
def offset_month(year, month)
# 3 - number of months in a quarter
month_offset = @fy_quarter_offset * 3

if month >= (month_offset.abs + 1)
[year, month + month_offset]
else
[year - 1, month - month_offset]
end
end

# Offsets the year based on the fiscal year quarter offset
# returns: offset year
def offset_year(year)
if @fy_quarter_offset < 0
year -= 1
elsif @fy_quarter_offset > 0
year += 1
else
year
end
end
end
end
17 changes: 10 additions & 7 deletions lib/merch_calendar/retail_calendar.rb
Expand Up @@ -6,14 +6,17 @@ class RetailCalendar
QUARTER_2 = 2
QUARTER_3 = 3
QUARTER_4 = 4

FOUR_WEEK_MONTHS = [2, 5, 8, 11]
FIVE_WEEK_MONTHS = [3, 6, 9, 12]

def end_of_year(year)
year_end = Date.new((year + 1), 1, -1)
wday = (year_end.wday + 1) % 7
year_end = Date.new((year + 1), 1, -1) # Jan 31st
wday = (year_end.wday + 1) % 7

if wday > 3
if wday > 3 # this rounds up to the next saturday
year_end += 7 - wday
elsif wday > 0
else # rounding down to the next saturday
year_end -= wday
end
year_end
Expand All @@ -27,16 +30,16 @@ def start_of_year(year)
# The starting date of a given month
# THIS IS THE MERCH MONTH
# 1 = feb
#

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
when 2,5,8,11
when *FOUR_WEEK_MONTHS
# 28 = 4 weeks
start = start + 28
when 3,6,9,12
when *FIVE_WEEK_MONTHS
# The 5 week months
# 63 = 4 weeks + 5 weeks
start = start + 63
Expand Down

0 comments on commit 285823f

Please sign in to comment.