diff --git a/Gemfile.lock b/Gemfile.lock index 040e6ca..e4e04f5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - merch_calendar (0.1.0) + merch_calendar (0.2.0) GEM remote: https://www.rubygems.org/ @@ -58,4 +58,4 @@ DEPENDENCIES simplecov BUNDLED WITH - 1.15.1 + 1.15.3 diff --git a/lib/merch_calendar/merch_week.rb b/lib/merch_calendar/merch_week.rb index 44d2df1..03b5d2b 100644 --- a/lib/merch_calendar/merch_week.rb +++ b/lib/merch_calendar/merch_week.rb @@ -1,6 +1,6 @@ module MerchCalendar - # Represents the Merch Week for a specified date. + # Represents the Merch Week for a specified date and calendar class MerchWeek MONTHS = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec).freeze @@ -10,56 +10,68 @@ class MerchWeek # @!attribute [r] date # @return [Date] the date for this merch week attr_reader :date - - class << self - - # Locates the +MerchWeek+ for a given Julian date. - # - # @overload from_date(String) - # @param [String] julian_date a julian date in the format of +YYYY-MM-DD+ - # @overload from_date(Date) - # @param [Date] julian_date a +Date+ object - # - # @return [MerchWeek] - def from_date(julian_date) - MerchWeek.new Date.parse("#{julian_date}") - end - - # Returns an array of merch weeks for a month, or a specific week. - # - # @overload find(year, julian_month) - # Returns an array of +MerchWeek+s for a given month - # @param year [Fixnum] the merch year to locate - # @param julian_month [Fixnum] the month to find merch months for - # @return [Array] - # @overload find(year, julian_month, week_number) - # @param year [Fixnum] the merch year to locate - # @param julian_month [Fixnum] the month to find merch months for - # @param week_number [Fixnum] the specific week number. - # @return [MerchWeek] the specific merch week - def find(year, julian_month, week_number=nil) - if week_number.nil? - MerchCalendar.weeks_for_month(year, julian_month) - else - MerchCalendar.weeks_for_month(year, julian_month)[week_number-1] - end - end - - # Returns the +MerchWeek+ for today's date - # - # @return [MerchWeek] - def today - MerchWeek.from_date Date.today + + # The Merch Calendar that is being represented, either Fiscal or Retail + # + # @!attribute [r] calendar + # @return [Class] the calendar that determines the merch week + attr_reader :calendar + + # Locates the +MerchWeek+ for a given Julian date. + # + # @overload from_date(String) + # @param [String] julian_date a julian date in the format of +YYYY-MM-DD+ + # @param [Hash] opts the options to set your calendar, if none it will default to RetailCalendar + # # @option opts [Class] :calendar The Calendar Class + # @overload from_date(Date) + # @param [Date] julian_date a +Date+ object + # @param [Hash] opts the options to set your calendar, if none it will default to RetailCalendar + # # @option opts [Class] :calendar The Calendar Class + # @return [MerchWeek] + def self.from_date(julian_date, options = {}) + MerchWeek.new(Date.parse("#{julian_date}"), options) + end + + # Returns an array of merch weeks for a month, or a specific week. + # + # @overload find(year, julian_month, week_number=nil, options) + # Returns an array of +MerchWeek+s for a given month + # @param year [Fixnum] the merch year to locate + # @param julian_month [Fixnum] the month to find merch months for + # @param week_number [Nil] set week_number to nil + # @param options [Hash] options to set your calendar, if none it will default to RetailCalendar + # @return [Array] + # @overload find(year, julian_month, week_number) + # @param year [Fixnum] the merch year to locate + # @param julian_month [Fixnum] the month to find merch months for + # @param week_number [Fixnum] the specific week number. + # @param options [Hash] options to set your calendar, if none it will default to RetailCalendar + # @return [MerchWeek] the specific merch week + def self.find(year, julian_month, week_number=nil, options={}) + calendar = options.fetch(:calendar, RetailCalendar.new) + if week_number.nil? + calendar.weeks_for_month(year, julian_month) + else + calendar.weeks_for_month(year, julian_month)[week_number-1] end end - + # Returns the +MerchWeek+ for today's date + # + # @param [Hash] opts the options to set your calendar, if none it will default to RetailCalendar + # # @option opts [Class] :calendar The Calendar Class + # @return [MerchWeek] + def self.today(options={}) + MerchWeek.from_date(Date.today, options) + end # Pass in a date, make sure it is a valid date object # @private def initialize(date, options = {}) @date = date - + + #defaults to Retail Calendar if no other calendar is defined + @calendar = options.fetch(:calendar, RetailCalendar.new) # Placeholders. These should be populated by functions if nil # week_start: nil, week_end: nil, week_number: nil @start_of_year = options[:start_of_year] @@ -81,62 +93,55 @@ def year_week end # This returns the "merch month" number for a date - # Merch months are shifted by one. Month 1 is Feb + # Month 1 is Feb for the retail calendar + # Month 1 is August for the fiscal calendar # # @return [Fixnum] def merch_month # TODO: This is very inefficient, but less complex than strategic guessing # maybe switch to a binary search or something + merch_year = calendar.merch_year_from_date(date) @merch_month ||= (1..12).detect do |num| - retail_calendar.end_of_month(start_of_year.year, num) >= date && date >= retail_calendar.start_of_month(start_of_year.year, num) + calendar.end_of_month(merch_year, num) >= date && date >= calendar.start_of_month(merch_year, num) end end - # The merch year + # Returns the Merch year depending whether it is from the Retail or Fiscal calendar # # @return [Fixnum] def year - start_of_year.year + @year ||= calendar.merch_year_from_date(date) end - # The julian month that this merch week falls in + # Returns julian month where the merch week falls # # @return [Fixnum] def month - @month ||= MerchCalendar.merch_to_julian(merch_month) + @month ||= calendar.merch_to_julian(merch_month) end - # The specific quarter this week falls in + # Returns the quarter that this merch week falls # # @return [Fixnum] def quarter - case merch_month - when 7,8,9 - return 1 - when 10,11,12 - return 2 - when 1,2,3 - return 3 - else - return 4 - end + @quarter ||= calendar.quarter(merch_month) end - # Returns the date of the start of this week + # Returns the start date of this week # # @return [Date] def start_of_week @start_of_week ||= (start_of_month + (7 * (week - 1))) end - # Returns the date of the end of this week + # Returns the end date of this week # # @return [Date] def end_of_week @end_of_week ||= (start_of_week + 6) end - # the number of the week within the given month + # The number of the week within the given merch month # will be between 1 and 5 # # @return [Fixnum] @@ -144,45 +149,40 @@ def week @week ||= (((date-start_of_month)+1)/7.0).ceil end - # The date of the start of the corresponding merch year + # The start date of the corresponding merch year # # @return [Date] def start_of_year - @start_of_year ||= year_start_date + @start_of_year ||= calendar.start_of_year(year) end # The end date of the corresponding merch year # # @return [Date] def end_of_year - @end_of_year ||= retail_calendar.end_of_year(year) + @end_of_year ||= calendar.end_of_year(year) end # The start date of the merch month # # @return [Date] def start_of_month - @start_of_month ||= retail_calendar.start_of_month(year, merch_month) + @start_of_month ||= calendar.start_of_month(year, merch_month) end # The end date of the merch month # # @return [Date] def end_of_month - @end_of_month ||= retail_calendar.end_of_month(year, merch_month) + @end_of_month ||= calendar.end_of_month(year, merch_month) end # The merch season this date falls under. # Returns a string of +Fall/Winter+ or +Spring/Summer+ - # + # THIS MIGHT NEED TO CHANGE DEPENDING ON THE CALENDAR # @return [String] def season - case merch_month - when 1,2,3,4,5,6 - "Spring/Summer" - when 7,8,9,10,11,12 - "Fall/Winter" - end + @season ||= calendar.season(merch_month) end # Outputs a text representation of this merch week @@ -193,7 +193,6 @@ def season # * +:elasticsearch+ (default) "2012-12w05" # # @param format [Symbol] the format identifier to return. Default is +:short+ - # # @return [Date] def to_s(format = :short) case format @@ -205,20 +204,5 @@ def to_s(format = :short) "#{MONTHS[month - 1]} W#{week}" end end - - private - - def year_start_date - start_date = retail_calendar.start_of_year(date.year) - if start_date > date - start_date = retail_calendar.start_of_year(date.year - 1) - end - start_date - end - - def retail_calendar - @retail_calendar ||= RetailCalendar.new - end - end end diff --git a/lib/merch_calendar/retail_calendar.rb b/lib/merch_calendar/retail_calendar.rb index 74a144b..714fcdc 100644 --- a/lib/merch_calendar/retail_calendar.rb +++ b/lib/merch_calendar/retail_calendar.rb @@ -2,6 +2,7 @@ module MerchCalendar class RetailCalendar + QUARTER_1 = 1 QUARTER_2 = 2 QUARTER_3 = 3 @@ -9,28 +10,36 @@ class RetailCalendar FOUR_WEEK_MONTHS = [2, 5, 8, 11] FIVE_WEEK_MONTHS = [3, 6, 9, 12] - + + # The the first date of the retail year + # + # @param [Fixnum] year - the retail year + # @return [Date] the first date of the retail year def end_of_year(year) year_end = Date.new((year + 1), 1, -1) # Jan 31st wday = (year_end.wday + 1) % 7 - if wday > 3 # this rounds up to the next saturday + if wday > 3 year_end += 7 - wday - else # rounding down to the next saturday + else year_end -= wday end year_end end - # The day after last years' end date + # The last date of the retail year + # + # @param [Fixnum] year - the retail year + # @return [Date] the last date of the retail year def start_of_year(year) end_of_year(year - 1) + 1 end - # The starting date of a given month - # THIS IS THE MERCH MONTH - # 1 = feb - + # The starting date of the given merch month + # + # @param [Fixnum] year - the retail year + # @param [Fixnum] merch_month - the nth month of the retail calendar + # @return [Date] the start date of the merch month 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 @@ -48,6 +57,11 @@ def start_of_month(year, merch_month) start end + # The ending date of the given merch month + # + # @param [Fixnum] year - the retail year + # @param [Fixnum] merch_month - the nth month of the retail calendar + # @return [Date] the end date of the merch month def end_of_month(year, merch_month) if merch_month == 12 end_of_year(year) @@ -57,16 +71,30 @@ def end_of_month(year, merch_month) end # Returns the date that corresponds to the first day in the merch week + # + # @param [Fixnum] year - the retail year + # @param [Fixnum] merch_month - the nth month of the retail calendar + # @param [Fixnum] merch_week - the nth week of the merch month + # @return [Date] the start date of the 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 + # + # @param [Fixnum] year - the retail year + # @param [Fixnum] merch_month - the nth month of the retail calendar + # @param [Fixnum] merch_week - the nth week of the merch month + # @return [Date] the end date of the merch week def end_of_week(year, month, merch_week) start_of_month(year, month) + (6 + ((merch_week - 1) * 7)) end # Return the starting date for a particular quarter + # + # @param [Fixnum] year - the retail year + # @param [Fixnum] quarter - the quarter of the year, a number from 1 - 4 + # @return [Date] the start date of the quarter def start_of_quarter(year, quarter) case quarter when QUARTER_1 @@ -81,6 +109,10 @@ def start_of_quarter(year, quarter) end # Return the ending date for a particular quarter + # + # @param [Fixnum] year - the retail year + # @param [Fixnum] quarter - the quarter of the year, a number from 1 - 4 + # @return [Date] the end date of the quarter def end_of_quarter(year, quarter) case quarter when QUARTER_1 @@ -93,12 +125,97 @@ def end_of_quarter(year, quarter) end_of_month(year, 12) end end + + # Returns the quarter that the merch month falls in + # + # @param [Fixnum] merch month + # @return [Date] the quarter that the merch_month falls in + def quarter(merch_month) + case merch_month + when 1,2,3 + return QUARTER_1 + when 4,5,6 + return QUARTER_2 + when 7,8,9 + return QUARTER_3 + when 10,11,12 + return QUARTER_4 + end + end + + #Returns the season given for the merch_month + # + # @param [Fixnum] merch_month - the nth month of the retail calendar + # @return [String] the season that the merch_month falls under + def season(merch_month) + case merch_month + when 1,2,3,4,5,6 + "Spring/Summer" + when 7,8,9,10,11,12 + "Fall/Winter" + end + end - # Return the number of weeks in a particular year + # Returns the number of weeks in the retail year + # + # @param [Fixnum] year - the retail year + # @return [Fixnum] the number of weeks within the retail year def weeks_in_year(year) ((start_of_year(year + 1) - start_of_year(year)) / 7).to_i end + # Given any julian date it will return what retail year it belongs to + # + # @param [Date] the julian date to convert to its Retail Year + # @return [Fixnum] the retail year that the julian date falls into + def merch_year_from_date(date) + date_end_of_year = end_of_year(date.year) + date_start_of_year = start_of_year(date.year) + if date < date_start_of_year + date.year - 1 + else + date.year + end + end + + + # Converts a merch month to the correct julian month + # + # @param [Fixnum] the merch month to convert + # @return [Fixnum] the julian month + def merch_to_julian(merch_month) + if merch_month > 12 || merch_month <= 0 + raise ArgumentError + end + + if merch_month == 12 + 1 + else + merch_month + 1 + end + end + + # Converts a julian month to a merch month + # + # @param [Fixnum] the julian month to convert + # @return [Fixnum] the merch month + def julian_to_merch(julian_month) + if julian_month > 12 || julian_month <= 0 + raise ArgumentError + end + + if julian_month == 1 + 12 + else + julian_month - 1 + end + end + + # Given beginning and end dates it will return an array of Retail Month's Start date + # + # @param start_date [Date] the starting date + # @param end_date [Date] the ending date + # @return [Array] Array of start dates of each Retail Month from given dates def merch_months_in(start_date, end_date) merch_months = [] prev_date = start_date - 2 @@ -112,5 +229,50 @@ def merch_months_in(start_date, end_date) end merch_months end + + # Returns an array of Merch Weeks that pertains to the Julian Month of a Retail Year + # + # @param year [Fixnum] the Retail year + # @param month_param [Fixnum] the julian month + # @return [Array] Array of MerchWeeks + def weeks_for_month(year, month_param) + merch_month = get_merch_month_param(month_param) + + start_date = start_of_month(year, merch_month) + + weeks = (end_of_month(year, merch_month) - start_date + 1) / 7 + + (1..weeks).map do |week_num| + week_start = start_date + ((week_num - 1) * 7) + week_end = week_start + 6 + + MerchWeek.new(week_start, { + start_of_week: week_start, + end_of_week: week_end, + week: week_num, + calendar: RetailCalendar.new + }) + end + end + + private + + def get_merch_month_param(param) + if param.is_a? Fixnum + return julian_to_merch(param) + elsif param.is_a? Hash + julian_month = param.delete(:julian_month) || param.delete(:month) + merch_month = param.delete(:merch_month) + + if merch_month + return merch_month + elsif julian_month + return julian_to_merch(julian_month) + end + end + + raise ArgumentError + end + end end diff --git a/lib/merch_calendar/stitch_fix_fiscal_year_calendar.rb b/lib/merch_calendar/stitch_fix_fiscal_year_calendar.rb index 144c924..98d5d33 100644 --- a/lib/merch_calendar/stitch_fix_fiscal_year_calendar.rb +++ b/lib/merch_calendar/stitch_fix_fiscal_year_calendar.rb @@ -9,12 +9,18 @@ class StitchFixFiscalYearCalendar FOUR_WEEK_MONTHS = [2, 5, 8, 11] FIVE_WEEK_MONTHS = [3, 6, 9, 12] - # The date of the first day of the year + # The date of the first day of the fiscal year + # + # @param [Fixnum] year - the fiscal year + # @return [Date] the first date of the fiscal year def start_of_year(year) end_of_year(year - 1) + 1 end - # The date of the last day of the year + # The date of the last day of the fiscal year + # + # @param [Fixnum] year - the fiscal year + # @return [Date] the last date of the fiscal year def end_of_year(year) year_end = Date.new((year), 7, -1) wday = (year_end.wday + 1) % 7 @@ -28,6 +34,10 @@ def end_of_year(year) end # Return the starting date for a particular quarter + # + # @param [Fixnum] year - the fiscal year + # @param [Fixnum] quarter - the quarter of the year, a number from 1 - 4 + # @return [Date] the start date of the quarter def start_of_quarter(year, quarter) case quarter when QUARTER_1 @@ -40,8 +50,29 @@ def start_of_quarter(year, quarter) start_of_month(year, 10) end end + + # Returns the quarter that the merch month falls in + # + # @param [Fixnum] merch month + # @return [Date] the quarter that the merch_month falls in + def quarter(merch_month) + case merch_month + when 1,2,3 + return QUARTER_1 + when 4,5,6 + return QUARTER_2 + when 7,8,9 + return QUARTER_3 + when 10,11,12 + return QUARTER_4 + end + end # Return the ending date for a particular quarter + # + # @param [Fixnum] year - the fiscal year + # @param [Fixnum] quarter - the quarter of the year, a number from 1 - 4 + # @return [Date] the end date of the quarter def end_of_quarter(year, quarter) case quarter when QUARTER_1 @@ -55,9 +86,24 @@ def end_of_quarter(year, quarter) end end - # The date of the first day of the merch month + #Returns the season given for the merch_month + # + # @param [Fixnum] merch_month - the nth month of the retail calendar + # @return [String] the season that the merch_month falls under + def season(merch_month) + case merch_month + when 1,2,3,4,5,6 + "Fall/Winter" + when 7,8,9,10,11,12 + "Spring/Summer" + end + end + + # The starting date of the given merch month + # # @param [Fixnum] year - the fiscal year - # @param [Fixnum] merch_month - the nth month of the offset calendar + # @param [Fixnum] merch_month - the nth month of the fiscal calendar + # @return [Date] the start date of the merch month 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 @@ -75,9 +121,11 @@ def start_of_month(year, merch_month) start end - # The date of the last day of the merch month + # The ending date of the given merch month + # # @param [Fixnum] year - the fiscal year - # @param [Fixnum] merch_month - the nth month of the offset calendar + # @param [Fixnum] merch_month - the nth month of the fiscal calendar + # @return [Date] the end date of the merch month def end_of_month(year, merch_month) if merch_month == 12 end_of_year(year) @@ -87,33 +135,138 @@ def end_of_month(year, merch_month) end # Returns the date that corresponds to the first day in the merch week + # + # @param [Fixnum] year - the fiscal year + # @param [Fixnum] merch_month - the nth month of the fiscal calendar + # @param [Fixnum] merch_week - the nth week of the merch month + # @return [Date] the start date of the 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 + # + # @param [Fixnum] year - the fiscal year + # @param [Fixnum] merch_month - the nth month of the fiscal calendar + # @param [Fixnum] merch_week - the nth week of the merch month + # @return [Date] the end date of the 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 + # + # @param [Fixnum] year - the fiscal year + # @return [Fixnum] the number of weeks within the fiscal year def weeks_in_year(year) ((start_of_year(year + 1) - start_of_year(year)) / 7).to_i end + + # Given any julian date it will return what Fiscal Year it belongs to + # + # @param [Date] the julian date to convert to its Fiscal Year + # @return [Fixnum] the fiscal year that the julian date falls into + def merch_year_from_date(date) + if end_of_year(date.year) >= date + return date.year + else + return date.year + 1 + end + end + + # Converts a merch month to the correct julian month + # + # @param merch_month [Fixnum] the merch month to convert + # @return [Fixnum] the julian month + def merch_to_julian(merch_month) + if merch_month > 12 || merch_month <= 0 + raise ArgumentError + end + + if merch_month <= 5 + merch_month + 7 + else + merch_month - 5 + end + end + + # Converts a julian month to a fiscal month + # + # @param julian_month [Fixnum] the julian month to convert + # @return [Fixnum] the merch month + def julian_to_merch(julian_month) + if julian_month > 12 || julian_month <= 0 + raise ArgumentError + end + + if julian_month <= 7 + julian_month + 5 + else + julian_month - 7 + end + end + + # Given beginning and end dates it will return an array of Fiscal Month's Start date + # + # @param start_date [Date] the starting date + # @param end_date [Date] the ending date + # @return [Array] Array of start dates of each Fiscal Month from given dates + def merch_months_in(start_date, end_date) + merch_months_combos = merch_year_and_month_from_dates(start_date, end_date) + merch_months_combos.map { | merch_month_combo | start_of_month(merch_month_combo[0], merch_month_combo[1]) } + end + + # Returns an array of Merch Weeks that pertains to the Julian Month of a Fiscal Year + # + # @param year [Fixnum] the fiscal year + # @param month_param [Fixnum] the julian month + # @return [Array] Array of MerchWeeks that falls within that julian month + def weeks_for_month(year, month_param) + merch_month = julian_to_merch(month_param) + + start_date = start_of_month(year, merch_month) + + weeks = (end_of_month(year, merch_month) - start_date + 1) / 7 + + (1..weeks).map do |week_num| + week_start = start_date + ((week_num - 1) * 7) + week_end = week_start + 6 + + MerchWeek.new(week_start, { + start_of_week: week_start, + end_of_week: week_end, + week: week_num, + calendar: StitchFixFiscalYearCalendar.new + }) + end + end + + private - def merch_months_in(start_date, end_date) + # Returns an array of merch_months and year combination that falls in and between the start and end date + # + # Ex: if start_date = August 1, 2018 and end_date = October 1, 2018 + # it returns [[2019, 1], [ 2019, 2], [2019, 3]] + def merch_year_and_month_from_dates(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 + + middle_of_start_month = Date.new(start_date.year, start_date.month, 14) + middle_of_end_month = Date.new(end_date.year, end_date.month, 14) + date = middle_of_start_month + + while date <= middle_of_end_month do + merch_months.push(date_conversion(date)) + date = date >> 1 end merch_months end - + + # This isn't a true date conversion, only used for merch_year_and_month_from_dates + # when its julian month actually falls in the wrong merch year + # EX: The true date_conversion of July 1, 2018 => [ 2019, 1 ] + # BUT this method here will return [2019, 12] because July is merch_month 12 for fiscal year + def date_conversion(date) + [ merch_year_from_date(date), julian_to_merch(date.month) ] + end end end diff --git a/spec/merch_calendar/merch_week_spec.rb b/spec/merch_calendar/merch_week_spec.rb index 9b9574f..6b6326e 100644 --- a/spec/merch_calendar/merch_week_spec.rb +++ b/spec/merch_calendar/merch_week_spec.rb @@ -1,12 +1,14 @@ require 'spec_helper' describe MerchCalendar::MerchWeek do + let!(:fiscal_calendar_options) { { calendar: MerchCalendar::StitchFixFiscalYearCalendar.new } } describe ".find" do it "returns an array of weeks" do weeks = described_class.find(2014,1) expect(weeks).to be_an Array expect(weeks.size).to eq 4 + expect(weeks[0].calendar.class).to eq MerchCalendar::RetailCalendar end it "with year, month, week" do @@ -16,6 +18,25 @@ expect(mw.merch_month).to eq 12 expect(mw.week).to eq 1 end + context "using the Fiscal Calendar instead of the Retail Calendar" do + it "returns an array of weeks" do + weeks = described_class.find(2019, 7, nil, fiscal_calendar_options) + expect(weeks).to be_an Array + expect(weeks.size).to eq 5 + expect(weeks[0].calendar.class).to eq MerchCalendar::StitchFixFiscalYearCalendar + end + + it "with year, month, week" do + mw = described_class.find(2019, 7, 5, fiscal_calendar_options) + expect(mw.year).to eq 2019 + expect(mw.month).to eq 7 + expect(mw.merch_month).to eq 12 + expect(mw.week).to eq 5 + expect(mw.end_of_week).to eq Date.new(2019,8,3) + expect(mw.start_of_week).to eq Date.new(2019,7,28) + expect(mw.calendar.class).to eq MerchCalendar::StitchFixFiscalYearCalendar + end + end end describe ".from_date" do @@ -23,9 +44,10 @@ context "parameters" do context "allows valid date types" do it "allows a date string" do - mw = described_class.from_date("1990-10-01") - expect(mw.date.to_s).to eq "1990-10-01" + mw = described_class.from_date("2018-10-01") + expect(mw.date.to_s).to eq "2018-10-01" expect(mw.date.month).to eq 10 + expect(mw.merch_month).to eq 8 end end @@ -40,6 +62,17 @@ it { expect{described_class.from_date("2015")}.to raise_error(ArgumentError) } it { expect{described_class.from_date("2015-04")}.to raise_error(ArgumentError) } end + + context "wants to know a date in a Fiscal Calendar" do + it "allows calendar to be passed and translate date to what it looks like in a FY year" do + mw = described_class.from_date("2019-08-04", fiscal_calendar_options) + expect(mw.date.to_s).to eq "2019-08-04" + expect(mw.date.month).to eq 8 + expect(mw.merch_month).to eq 1 + expect(mw.year).to eq 2020 + expect(mw.calendar.class).to eq MerchCalendar::StitchFixFiscalYearCalendar + end + end end end @@ -47,65 +80,111 @@ it "returns a merch week based on today's date" do mw = described_class.today expect(mw.date.to_s).to eq Date.today.to_s + expect(mw.calendar.class).to eq MerchCalendar::RetailCalendar + end + + context "passing in the Fiscal Calendar into options" do + it "returns a merch week based on today's date" do + mw = described_class.today(fiscal_calendar_options) + expect(mw.date.to_s).to eq Date.today.to_s + expect(mw.calendar.class).to eq MerchCalendar::StitchFixFiscalYearCalendar + end end end describe "#to_s" do let(:merch_week) { described_class.from_date("2014-01-01") } + let(:fiscal_week) { described_class.from_date("2019-08-01", fiscal_calendar_options) } it ":short / default format" do expect(merch_week.to_s(:short)).to eq "Dec W5" expect(merch_week.to_s).to eq "Dec W5" expect("#{merch_week}").to eq "Dec W5" + + expect(fiscal_week.to_s(:short)).to eq "Jul W5" + expect(fiscal_week.to_s).to eq "Jul W5" end it ":long format" do expect(merch_week.to_s(:long)).to eq "2013:48 Dec W5" + expect(fiscal_week.to_s(:long)).to eq "2019:53 Jul W5" end it ":elasticsearch format" do expect(merch_week.to_s(:elasticsearch)).to eq "2013-12w05" + expect(fiscal_week.to_s(:elasticsearch)).to eq "2019-07w05" end end - it "#end_of_week" do - mw = described_class.find(2014,1,1) + describe "#end_of_week" do + it "returns the end of the week based on the Retail Calendar" do + mw = described_class.find(2014,1,1) + expect(mw.end_of_week).to eq (mw.start_of_week + 6) + end - expect(mw.end_of_week).to eq (mw.start_of_week + 6) + it "returns the end of the week based on the Retail Calendar" do + mw = described_class.find(2019,1,1, fiscal_calendar_options) + expect(mw.end_of_week).to eq (mw.start_of_week + 6) + end end - describe "#end_of_month" do - it "for a 4 week month" do - mw = described_class.find(2014, 2, 1) - expect(mw.end_of_month - mw.start_of_month + 1).to eq (4*7) - end + context "using the Retail Calendar" do + it "for a 4 week month" do + mw = described_class.find(2017, 2, 1) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (4*7) + end + + it "for a 5 week month" do + mw = described_class.find(2017, 3, 1) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (5*7) + end - it "for a 5 week month" do - mw = described_class.find(2014, 3, 1) - expect(mw.end_of_month - mw.start_of_month + 1).to eq (5*7) + it "for a 4-5-5 quarter" do + mw = described_class.find(2017, 11, 1) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (4*7) + + mw = described_class.find(2017, 12, 1) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (5*7) + + mw = described_class.find(2017, 1, 1) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (5*7) + end end + + context "using the Stitch Fix Fiscal Calendar" do + it "for a 4 week month" do + mw = described_class.find(2017, 2, 1, fiscal_calendar_options) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (4*7) + end + + it "for a 5 week month" do + mw = described_class.find(2017, 3, 1, fiscal_calendar_options) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (5*7) + end + + it "for a 4-5-5 quarter" do + mw = described_class.find(2019, 5, 1, fiscal_calendar_options) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (4*7) - it "for a 4 to 5 week month in a leap year" do - mw = described_class.find(2011, 1, 1) - expect(mw.end_of_month - mw.start_of_month + 1).to eq (4*7) + mw = described_class.find(2019, 6, 1, fiscal_calendar_options) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (5*7) - mw = described_class.find(2012, 1, 1) - expect(mw.end_of_month - mw.start_of_month + 1).to eq (5*7) + mw = described_class.find(2019, 7, 1, fiscal_calendar_options) + expect(mw.end_of_month - mw.start_of_month + 1).to eq (5*7) + end end end describe "#season" do - context "Fall/Winter" do + context "when it comes from the Retail calendar" do it { expect(described_class.from_date("2011-08-06").season).to eq "Fall/Winter" } it { expect(described_class.from_date("2011-09-06").season).to eq "Fall/Winter" } it { expect(described_class.from_date("2011-10-06").season).to eq "Fall/Winter" } it { expect(described_class.from_date("2011-11-06").season).to eq "Fall/Winter" } it { expect(described_class.from_date("2011-12-06").season).to eq "Fall/Winter" } it { expect(described_class.from_date("2012-01-06").season).to eq "Fall/Winter" } - end - - context "Spring/Summer" do + it { expect(described_class.from_date("2011-02-06").season).to eq "Spring/Summer" } it { expect(described_class.from_date("2011-03-06").season).to eq "Spring/Summer" } it { expect(described_class.from_date("2011-04-06").season).to eq "Spring/Summer" } @@ -113,37 +192,111 @@ it { expect(described_class.from_date("2011-06-06").season).to eq "Spring/Summer" } it { expect(described_class.from_date("2011-07-06").season).to eq "Spring/Summer" } end + + context "when it comes from the Stitch Fix Fiscal Calendar" do + it { expect(described_class.from_date("2012-08-06", fiscal_calendar_options).season).to eq "Fall/Winter" } + it { expect(described_class.from_date("2012-09-06", fiscal_calendar_options).season).to eq "Fall/Winter" } + it { expect(described_class.from_date("2012-10-06", fiscal_calendar_options).season).to eq "Fall/Winter" } + it { expect(described_class.from_date("2012-11-06", fiscal_calendar_options).season).to eq "Fall/Winter" } + it { expect(described_class.from_date("2012-12-06", fiscal_calendar_options).season).to eq "Fall/Winter" } + it { expect(described_class.from_date("2012-01-06", fiscal_calendar_options).season).to eq "Fall/Winter" } + + it { expect(described_class.from_date("2011-02-06", fiscal_calendar_options).season).to eq "Spring/Summer" } + it { expect(described_class.from_date("2011-03-06", fiscal_calendar_options).season).to eq "Spring/Summer" } + it { expect(described_class.from_date("2011-04-06", fiscal_calendar_options).season).to eq "Spring/Summer" } + it { expect(described_class.from_date("2011-05-06", fiscal_calendar_options).season).to eq "Spring/Summer" } + it { expect(described_class.from_date("2011-06-06", fiscal_calendar_options).season).to eq "Spring/Summer" } + it { expect(described_class.from_date("2011-07-06", fiscal_calendar_options).season).to eq "Spring/Summer" } + end end - context "logic" do + context "logic for Retail Calendar" do [ - OpenStruct.new(date: "2011-05-01", year: 2011, month: 5, week: 1, quarter: 4, year_week: 14, start_date: "2011-01-30"), + OpenStruct.new(date: "2011-05-01", year: 2011, month: 5, week: 1, quarter: 2, year_week: 14, start_date: "2011-01-30"), - OpenStruct.new(date: "2014-08-31", year: 2014, month: 9, week: 1, quarter: 1, year_week: 31, start_date: "2014-02-02"), - OpenStruct.new(date: "2017-12-30", year: 2017, month: 12, week: 5, quarter: 2, year_week: 48, start_date: "2017-01-29"), - OpenStruct.new(date: "2014-01-01", year: 2013, month: 12, week: 5, quarter: 2, year_week: 48, start_date: "2013-02-03"), - OpenStruct.new(date: "2014-01-04", year: 2013, month: 12, week: 5, quarter: 2, year_week: 48, start_date: "2013-02-03"), - OpenStruct.new(date: "2014-01-05", year: 2013, month: 1, week: 1, quarter: 2, year_week: 49, start_date: "2013-02-03"), - OpenStruct.new(date: "2014-01-12", year: 2013, month: 1, week: 2, quarter: 2, year_week: 50, start_date: "2013-02-03"), - OpenStruct.new(date: "2014-01-19", year: 2013, month: 1, week: 3, quarter: 2, year_week: 51, start_date: "2013-02-03"), - OpenStruct.new(date: "2014-01-26", year: 2013, month: 1, week: 4, quarter: 2, year_week: 52, start_date: "2013-02-03"), + OpenStruct.new(date: "2014-08-31", year: 2014, month: 9, week: 1, quarter: 3, year_week: 31, start_date: "2014-02-02"), + OpenStruct.new(date: "2014-01-01", year: 2013, month: 12, week: 5, quarter: 4, year_week: 48, start_date: "2013-02-03"), + OpenStruct.new(date: "2014-01-04", year: 2013, month: 12, week: 5, quarter: 4, year_week: 48, start_date: "2013-02-03"), + OpenStruct.new(date: "2014-01-05", year: 2013, month: 1, week: 1, quarter: 4, year_week: 49, start_date: "2013-02-03"), + OpenStruct.new(date: "2014-01-12", year: 2013, month: 1, week: 2, quarter: 4, year_week: 50, start_date: "2013-02-03"), + OpenStruct.new(date: "2014-01-19", year: 2013, month: 1, week: 3, quarter: 4, year_week: 51, start_date: "2013-02-03"), + OpenStruct.new(date: "2014-01-26", year: 2013, month: 1, week: 4, quarter: 4, year_week: 52, start_date: "2013-02-03"), # 2013 - OpenStruct.new(date: "2013-02-03", year: 2013, month: 2, week: 1, quarter: 3, year_week: 1, start_date: "2013-02-03"), + OpenStruct.new(date: "2013-02-03", year: 2013, month: 2, week: 1, quarter: 1, year_week: 1, start_date: "2013-02-03"), + + #2014 + OpenStruct.new(date: "2014-02-02", year: 2014, month: 2, week: 1, quarter: 1, year_week: 1, start_date: "2014-02-02"), + OpenStruct.new(date: "2014-02-01", year: 2013, month: 1, week: 4, quarter: 4, year_week: 52, start_date: "2013-02-03"), + OpenStruct.new(date: "2015-01-31", year: 2014, month: 1, week: 4, quarter: 4, year_week: 52, start_date: "2014-02-02"), + + #2015 + OpenStruct.new(date: "2015-02-01", year: 2015, month: 2, week: 1, quarter: 1, year_week: 1, start_date: "2015-02-01"), + + #2017 + OpenStruct.new(date: "2017-12-30", year: 2017, month: 12, week: 5, quarter: 4, year_week: 48, start_date: "2017-01-29") + ].each do |date_check| + + context "using date '#{date_check.date}'" do + let(:merch_week) { described_class.from_date(date_check.date) } - # 2014 - OpenStruct.new(date: "2014-02-02", year: 2014, month: 2, week: 1, quarter: 3, year_week: 1, start_date: "2014-02-02"), - OpenStruct.new(date: "2015-01-31", year: 2014, month: 1, week: 4, quarter: 2, year_week: 52, start_date: "2014-02-02"), + it "#year_week" do + expect(merch_week.year_week).to eq date_check.year_week + end - OpenStruct.new(date: "2014-02-01", year: 2013, month: 1, week: 4, quarter: 2, year_week: 52, start_date: "2013-02-03"), + it "#month" do + expect(merch_week.month).to eq date_check.month + end - OpenStruct.new(date: "2015-02-01", year: 2015, month: 2, week: 1, quarter: 3, year_week: 1, start_date: "2015-02-01"), + it "#quarter" do + expect(merch_week.quarter).to eq date_check.quarter + end + + it "#week" do + expect(merch_week.week).to eq date_check.week + end + it "#start_of_year" do + expect(merch_week.start_of_year.to_s).to eq date_check.start_date + end + + it "#year" do + expect(merch_week.year).to eq date_check.year + end + end + end + end + + context "logic for Stitch Fix Fiscal Calendar" do + [ + #2018 + OpenStruct.new(date: "2018-07-26", year: 2018, month: 7, week: 4, quarter: 4, year_week: 52, start_date: "2017-07-30"), + + #2019 + OpenStruct.new(date: "2018-07-31", year: 2019, month: 8, week: 1, quarter: 1, year_week: 1, start_date: "2018-07-29"), + OpenStruct.new(date: "2019-02-01", year: 2019, month: 2, week: 1, quarter: 3, year_week: 27, start_date: "2018-07-29"), + OpenStruct.new(date: "2019-08-02", year: 2019, month: 7, week: 5, quarter: 4, year_week: 53, start_date: "2018-07-29"), + + # 2020 + OpenStruct.new(date: "2019-08-08", year: 2020, month: 8, week: 1, quarter: 1, year_week: 1, start_date: "2019-08-04"), + OpenStruct.new(date: "2020-05-05", year: 2020, month: 5, week: 1, quarter: 4, year_week: 40, start_date: "2019-08-04"), + OpenStruct.new(date: "2020-08-01", year: 2020, month: 7, week: 4, quarter: 4, year_week: 52, start_date: "2019-08-04"), + + #2023 + OpenStruct.new(date: "2022-07-31", year: 2023, month: 8, week: 1, quarter: 1, year_week: 1, start_date: "2022-07-31"), + OpenStruct.new(date: "2023-04-22", year: 2023, month: 4, week: 3, quarter: 3, year_week: 38, start_date: "2022-07-31"), + OpenStruct.new(date: "2023-07-25", year: 2023, month: 7, week: 4, quarter: 4, year_week: 52, start_date: "2022-07-31"), + + #2024 + OpenStruct.new(date: "2023-08-01", year: 2024, month: 8, week: 1, quarter: 1, year_week: 1, start_date: "2023-07-30"), + OpenStruct.new(date: "2023-11-08", year: 2024, month: 11, week: 2, quarter: 2, year_week: 15, start_date: "2023-07-30"), + OpenStruct.new(date: "2024-07-29", year: 2024, month: 7, week: 5, quarter: 4, year_week: 53, start_date: "2023-07-30"), + ].each do |date_check| context "using date '#{date_check.date}'" do - let(:merch_week) { described_class.from_date(date_check.date) } + let(:merch_week) { described_class.from_date(date_check.date, fiscal_calendar_options) } it "#year_week" do expect(merch_week.year_week).to eq date_check.year_week @@ -171,5 +324,4 @@ end end end - end diff --git a/spec/merch_calendar/retail_calendar_spec.rb b/spec/merch_calendar/retail_calendar_spec.rb index ddebd09..f29b974 100644 --- a/spec/merch_calendar/retail_calendar_spec.rb +++ b/spec/merch_calendar/retail_calendar_spec.rb @@ -5,11 +5,14 @@ it "returns 53 for a leap year" do expect(subject.weeks_in_year(2012)).to eq 53 expect(subject.weeks_in_year(2017)).to eq 53 + expect(subject.weeks_in_year(2023)).to eq 53 end it "returns 52 for a normal year" do expect(subject.weeks_in_year(2013)).to eq 52 expect(subject.weeks_in_year(2018)).to eq 52 + expect(subject.weeks_in_year(2019)).to eq 52 + expect(subject.weeks_in_year(2020)).to eq 52 end end @@ -57,6 +60,35 @@ expect(subject.end_of_quarter(2019, 4)).to eq Date.new(2020, 2, 1) end end + + describe "#quarter" do + it "returns the correct quarter number" do + expect(subject.quarter(5)).to eq 2 + expect(subject.quarter(7)).to eq 3 + expect(subject.quarter(2)).to eq 1 + expect(subject.quarter(11)).to eq 4 + end + end + + describe "#season" do + context "for merch_months in the Spring and Summer Season" do + it { expect(subject.season(1)).to eq "Spring/Summer" } + it { expect(subject.season(2)).to eq "Spring/Summer" } + it { expect(subject.season(3)).to eq "Spring/Summer" } + it { expect(subject.season(4)).to eq "Spring/Summer" } + it { expect(subject.season(5)).to eq "Spring/Summer" } + it { expect(subject.season(6)).to eq "Spring/Summer" } + end + + context "for merch_months in the Fall and Winter Season" do + it { expect(subject.season(7)).to eq "Fall/Winter" } + it { expect(subject.season(8)).to eq "Fall/Winter" } + it { expect(subject.season(9)).to eq "Fall/Winter" } + it { expect(subject.season(10)).to eq "Fall/Winter" } + it { expect(subject.season(11)).to eq "Fall/Winter" } + it { expect(subject.season(12)).to eq "Fall/Winter" } + end + end describe "#start_of_year" do it "returns the correct date" do @@ -103,7 +135,6 @@ merch_months = subject.merch_months_in(start_date, end_date) expect(merch_months.count).to be 11 - merch_months.each do |merch_month| expect(merch_month.year).to be 2014 end @@ -121,5 +152,91 @@ expect(merch_months[10].strftime('%Y-%m-%d')).to eq '2014-11-30' end end + + describe "#merch_year_from_date" do + it "returns the correct merch calendar year" do + expect(subject.merch_year_from_date(Date.new(2012, 1, 28))).to eq 2011 + expect(subject.merch_year_from_date(Date.new(2012, 1, 29))).to eq 2012 + expect(subject.merch_year_from_date(Date.new(2018, 1, 24))).to eq 2017 + expect(subject.merch_year_from_date(Date.new(2018, 2, 3))).to eq 2017 + expect(subject.merch_year_from_date(Date.new(2018, 2, 4))).to eq 2018 + expect(subject.merch_year_from_date(Date.new(2019, 2, 2))).to eq 2018 + expect(subject.merch_year_from_date(Date.new(2019, 2, 3))).to eq 2019 + end + end + + describe "#merch_to_julian" do + it "converts merch months to julian months" do + expect(subject.merch_to_julian(1)).to eq 2 + expect(subject.merch_to_julian(2)).to eq 3 + expect(subject.merch_to_julian(3)).to eq 4 + expect(subject.merch_to_julian(4)).to eq 5 + expect(subject.merch_to_julian(5)).to eq 6 + expect(subject.merch_to_julian(6)).to eq 7 + expect(subject.merch_to_julian(7)).to eq 8 + expect(subject.merch_to_julian(8)).to eq 9 + expect(subject.merch_to_julian(9)).to eq 10 + expect(subject.merch_to_julian(10)).to eq 11 + expect(subject.merch_to_julian(11)).to eq 12 + expect(subject.merch_to_julian(12)).to eq 1 + end + + it "raises an error for invalid merch months" do + expect { subject.merch_to_julian(13) }.to raise_error ArgumentError + expect { subject.merch_to_julian(0) }.to raise_error ArgumentError + end + end + + describe "#julian_to_merch" do + it "converts julian months to merch months" do + expect(subject.julian_to_merch(2)).to eq 1 + expect(subject.julian_to_merch(3)).to eq 2 + expect(subject.julian_to_merch(4)).to eq 3 + expect(subject.julian_to_merch(5)).to eq 4 + expect(subject.julian_to_merch(6)).to eq 5 + expect(subject.julian_to_merch(7)).to eq 6 + expect(subject.julian_to_merch(8)).to eq 7 + expect(subject.julian_to_merch(9)).to eq 8 + expect(subject.julian_to_merch(10)).to eq 9 + expect(subject.julian_to_merch(11)).to eq 10 + expect(subject.julian_to_merch(12)).to eq 11 + expect(subject.julian_to_merch(1)).to eq 12 + end + + it "raises an error for invalid merch months" do + expect { subject.julian_to_merch(13) }.to raise_error ArgumentError + expect { subject.julian_to_merch(0) }.to raise_error ArgumentError + end + end + + describe "#weeks_for_month" do + it "returns 4 weeks for a 4-week month Fiscal Year 2019 for Aug" do + weeks = subject.weeks_for_month(2018, 2) + expect(weeks.size).to eq 4 + end + it "returns 5 weeks for a 5-week month Fiscal Year 2019 for Sept" do + weeks = subject.weeks_for_month(2018, 3) + expect(weeks.size).to eq 5 + end + + it "returns 5 weeks during a 4-5-5 quarter" do + weeks = subject.weeks_for_month(2017, julian_month: 11) + expect(weeks.size).to eq 4 + + weeks = subject.weeks_for_month(2017, merch_month: 11) + expect(weeks.size).to eq 5 + + weeks = subject.weeks_for_month(2017, 1) + expect(weeks.size).to eq 5 + + weeks = subject.weeks_for_month(2018, 2) + expect(weeks.size).to eq 4 + end + + it "raises an ArgumentError if the param is not a hash with a key we care about or Fixnum" do + expect { subject.weeks_for_month(2018, "3") }.to raise_error ArgumentError + expect { subject.weeks_for_month(2018, some_month: 4) }.to raise_error ArgumentError + end + end end diff --git a/spec/merch_calendar/stitch_fix_fiscal_year_calendar_spec.rb b/spec/merch_calendar/stitch_fix_fiscal_year_calendar_spec.rb index 0189104..e4a8c2f 100644 --- a/spec/merch_calendar/stitch_fix_fiscal_year_calendar_spec.rb +++ b/spec/merch_calendar/stitch_fix_fiscal_year_calendar_spec.rb @@ -26,15 +26,15 @@ expect(subject.weeks_in_year(2018)).to eq 52 end - it "returns 53 for a leap year - 2019" do + it "returns 53 for a year that includes a 4-5-5 quarter - 2019" do expect(subject.weeks_in_year(2019)).to eq 53 end it "returns 52 for a normal year - 2020" do - expect(subject.weeks_in_year(2020)).to eq 52 + expect(subject.weeks_in_year(2023)).to eq 52 end - it "returns 53 for a leap year - 2024" do + it "returns 53 for a year that includes a 4-5-5 quarter - 2024" do expect(subject.weeks_in_year(2024)).to eq 53 end @@ -167,6 +167,35 @@ end end + describe "#quarter" do + it "returns the correct quarter number" do + expect(subject.quarter(5)).to eq 2 + expect(subject.quarter(7)).to eq 3 + expect(subject.quarter(2)).to eq 1 + expect(subject.quarter(11)).to eq 4 + end + end + + describe "#season" do + context "returns Fall/Winter from its merch_month" do + it { expect(subject.season(1)).to eq "Fall/Winter" } + it { expect(subject.season(2)).to eq "Fall/Winter" } + it { expect(subject.season(3)).to eq "Fall/Winter" } + it { expect(subject.season(4)).to eq "Fall/Winter" } + it { expect(subject.season(5)).to eq "Fall/Winter" } + it { expect(subject.season(6)).to eq "Fall/Winter" } + end + + context "returns Spring Summer from its merch_month" do + it { expect(subject.season(7)).to eq "Spring/Summer" } + it { expect(subject.season(8)).to eq "Spring/Summer" } + it { expect(subject.season(9)).to eq "Spring/Summer" } + it { expect(subject.season(10)).to eq "Spring/Summer" } + it { expect(subject.season(11)).to eq "Spring/Summer" } + it { expect(subject.season(12)).to eq "Spring/Summer" } + end + end + describe "#start_of_year" do it "returns the correct date for 2018" do expect(subject.start_of_year(2018)).to eq Date.new(2017, 7, 30) @@ -215,42 +244,121 @@ end end + describe "#merch_year_from_date" do + it "converts julian dates to its fiscal year" do + expect(subject.merch_year_from_date(Date.new(2018, 7, 24))).to eq 2018 + expect(subject.merch_year_from_date(Date.new(2018, 7, 29))).to eq 2019 + expect(subject.merch_year_from_date(Date.new(2018, 8, 1))).to eq 2019 + expect(subject.merch_year_from_date(Date.new(2019, 8, 1))).to eq 2019 + expect(subject.merch_year_from_date(Date.new(2019, 8, 4))).to eq 2020 + expect(subject.merch_year_from_date(Date.new(2024, 2, 3))).to eq 2024 + expect(subject.merch_year_from_date(Date.new(2024, 7, 30))).to eq 2024 + expect(subject.merch_year_from_date(Date.new(2024, 8, 4))).to eq 2025 + end + end + + describe "#merch_months_in" do it "returns merch date for start_date if start_date is the same as end_date" do - start_date = Date.new(2018,8,1) + start_date = Date.new(2020,8,2) end_date = start_date - start_merch_date = MerchCalendar.start_of_month(start_date.year, merch_month: start_date.month) + start_merch_date = subject.start_of_month(start_date.year, start_date.month) merch_months = subject.merch_months_in(start_date, end_date) expect(merch_months.count).to be(1) - expect(merch_months.first.year).to eq start_merch_date.year - expect(merch_months.first.month).to eq start_merch_date.month - expect(merch_months.first.day).to eq start_merch_date.day + expect(merch_months[0].strftime('%Y-%m-%d')).to eq '2020-08-02' end - it "returns valid merch dates for 2014" do + it "returns valid merch dates for FY 2019" do start_date = Date.new(2018, 8, 1) - end_date = Date.new(2019, 7, 1) + end_date = Date.new(2019, 8, 1) merch_months = subject.merch_months_in(start_date, end_date) - expect(merch_months.count).to be 11 + + expect(merch_months.count).to be 13 expect(merch_months[0].year).to be 2018 - expect(merch_months[4].year).to be 2019 - expect(merch_months[10].year).to be 2019 - - expect(merch_months[0].strftime('%Y-%m-%d')).to eq '2018-09-02' - expect(merch_months[1].strftime('%Y-%m-%d')).to eq '2018-10-07' - expect(merch_months[2].strftime('%Y-%m-%d')).to eq '2018-11-04' - expect(merch_months[3].strftime('%Y-%m-%d')).to eq '2018-12-02' - expect(merch_months[4].strftime('%Y-%m-%d')).to eq '2019-01-06' - expect(merch_months[5].strftime('%Y-%m-%d')).to eq '2019-02-03' - expect(merch_months[6].strftime('%Y-%m-%d')).to eq '2019-03-03' - expect(merch_months[7].strftime('%Y-%m-%d')).to eq '2019-04-07' - expect(merch_months[8].strftime('%Y-%m-%d')).to eq '2019-05-05' - expect(merch_months[9].strftime('%Y-%m-%d')).to eq '2019-06-02' - expect(merch_months[10].strftime('%Y-%m-%d')).to eq '2019-07-07' + expect(merch_months[6].year).to be 2019 + expect(merch_months[12].year).to be 2019 + + expect(merch_months[0].strftime('%Y-%m-%d')).to eq '2018-07-29' + expect(merch_months[1].strftime('%Y-%m-%d')).to eq '2018-08-26' + expect(merch_months[2].strftime('%Y-%m-%d')).to eq '2018-09-30' + expect(merch_months[3].strftime('%Y-%m-%d')).to eq '2018-10-28' + expect(merch_months[4].strftime('%Y-%m-%d')).to eq '2018-11-25' + expect(merch_months[5].strftime('%Y-%m-%d')).to eq '2018-12-30' + expect(merch_months[6].strftime('%Y-%m-%d')).to eq '2019-01-27' + expect(merch_months[7].strftime('%Y-%m-%d')).to eq '2019-02-24' + expect(merch_months[8].strftime('%Y-%m-%d')).to eq '2019-03-31' + expect(merch_months[9].strftime('%Y-%m-%d')).to eq '2019-04-28' + expect(merch_months[10].strftime('%Y-%m-%d')).to eq '2019-05-26' + expect(merch_months[11].strftime('%Y-%m-%d')).to eq '2019-06-30' + expect(merch_months[12].strftime('%Y-%m-%d')).to eq '2019-08-04' + end + end + + describe "#julian_to_merch" do + it "converts julian months to merch months" do + expect(subject.julian_to_merch(8)).to eq 1 + expect(subject.julian_to_merch(9)).to eq 2 + expect(subject.julian_to_merch(10)).to eq 3 + expect(subject.julian_to_merch(11)).to eq 4 + expect(subject.julian_to_merch(12)).to eq 5 + expect(subject.julian_to_merch(1)).to eq 6 + expect(subject.julian_to_merch(2)).to eq 7 + expect(subject.julian_to_merch(3)).to eq 8 + expect(subject.julian_to_merch(4)).to eq 9 + expect(subject.julian_to_merch(5)).to eq 10 + expect(subject.julian_to_merch(6)).to eq 11 + expect(subject.julian_to_merch(7)).to eq 12 + expect { subject.julian_to_merch(13) }.to raise_error ArgumentError + expect { subject.julian_to_merch(0) }.to raise_error ArgumentError + end + end + + describe "#merch_to_julian" do + it "converts merch months to julian months" do + expect(subject.merch_to_julian(1)).to eq 8 + expect(subject.merch_to_julian(2)).to eq 9 + expect(subject.merch_to_julian(3)).to eq 10 + expect(subject.merch_to_julian(4)).to eq 11 + expect(subject.merch_to_julian(5)).to eq 12 + expect(subject.merch_to_julian(6)).to eq 1 + expect(subject.merch_to_julian(7)).to eq 2 + expect(subject.merch_to_julian(8)).to eq 3 + expect(subject.merch_to_julian(9)).to eq 4 + expect(subject.merch_to_julian(10)).to eq 5 + expect(subject.merch_to_julian(11)).to eq 6 + expect(subject.merch_to_julian(12)).to eq 7 + expect { subject.merch_to_julian(13) }.to raise_error ArgumentError + expect { subject.merch_to_julian(0) }.to raise_error ArgumentError + end + end + + describe "#weeks_for_month" do + context "correct number of weeks given julian month and fiscal year" do + it "returns 4 weeks for a 4-week month Fiscal Year 2019 for Aug" do + weeks = subject.weeks_for_month(2019, 8) + expect(weeks.size).to eq 4 + end + it "returns 5 weeks for a 5-week month Fiscal Year 2019 for Sept" do + weeks = subject.weeks_for_month(2019, 9) + expect(weeks.size).to eq 5 + end + it "returns 5 weeks during a 4-5-5 quarter" do + weeks = subject.weeks_for_month(2019, 5) + expect(weeks.size).to eq 4 + + weeks = subject.weeks_for_month(2019, 6) + expect(weeks.size).to eq 5 + + weeks = subject.weeks_for_month(2019, 7) + expect(weeks.size).to eq 5 + + weeks = subject.weeks_for_month(2020, 8) + expect(weeks.size).to eq 4 + end end end end