diff --git a/lib/calendar/month.rb b/lib/calendar/month.rb new file mode 100644 index 0000000..f581bb8 --- /dev/null +++ b/lib/calendar/month.rb @@ -0,0 +1,36 @@ +require 'date' + +module Calendar + class Month + NAMES = %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec].freeze + + def initialize(year:, value:) + @year = year + @value = value + @name = NAMES[@value - 1] + @day_count = day_count + @dates = (1..@day_count).map { |number| Date.new(@year.value, @value, number) } + end + + def to_array + [@name, *([''] * first_date_wday), *@dates] + end + + private + + def day_count + case @value + when 1, 3, 5, 7, 8, 10, 12 then 31 + when 4, 6, 9, 11 then 30 + when 2 + @year.leap_year? ? 29 : 28 + else + raise 'Invalid month' + end + end + + def first_date_wday + @dates.first.wday + end + end +end diff --git a/lib/calendar/table.rb b/lib/calendar/table.rb new file mode 100644 index 0000000..0387794 --- /dev/null +++ b/lib/calendar/table.rb @@ -0,0 +1,85 @@ +module Calendar + class Table + def initialize(rows:, today: nil) + @rows = rows + @today = today + end + + def generate(vertical: false) + vertical ? generate_with_vertical : generate_with_horizontal + end + + private + + def generate_with_horizontal + header = @rows.first.join(' ') + body = @rows[1..].map do |row| + [ + "#{row.first} ", + *row[1..].map do |cell| + if cell.is_a?(Date) + string_format = cell == @today ? '[%-d]' : '%_2d' + cell.strftime(string_format) + else + ' ' + end + end + ].join(' ') + end + text = [header, *body].join("\n") + return text unless @today + + replace_today_for_horizontal(text:, today: @today) + end + + def generate_with_vertical + header = transposed_rows.first.join(' ') + body = transposed_rows[1..].map do |row| + [ + "#{row.first} ", + *row[1..].map do |cell| + if cell.is_a?(Date) + string_format = + if cell == @today + space_size = cell.day > 9 ? 0 : 1 + ' ' * space_size + '[%-d]' + else + '%_3d' + end + cell.strftime(string_format) + else + ' ' + end + end + ].join(' ').rstrip + end + text = [header, *body].join("\n") + return text unless @today + + replace_today_for_vertical(text:, today: @today) + end + + def transposed_rows + return @transposed_rows if @transposed_rows + + max_length = @rows.map { |row| row.length }.max + filled_rows = @rows.map do |row| + row + [''] * (max_length - row.length) + end + @transposed_rows = filled_rows.transpose + end + + def replace_today_for_horizontal(text:, today:) + result = /[^\S\n\r]\[(#{today.day})\][^\S\n\r]?/.match(text) + captured = result.to_a[0] + text.gsub(captured, today.day > 9 ? captured.strip : captured.rstrip) + end + + def replace_today_for_vertical(text:, today:) + result = /\[(#{today.day})\][^\S\n\r]?/.match(text) + captured = result.to_a[0] + text.gsub(captured, captured.rstrip) + end + end +end + diff --git a/lib/calendar/year.rb b/lib/calendar/year.rb new file mode 100644 index 0000000..2990ba8 --- /dev/null +++ b/lib/calendar/year.rb @@ -0,0 +1,32 @@ +require_relative 'month' +require_relative 'table' +require 'date' + +module Calendar + class Year + attr_reader :value, :today + + def initialize(value:, today: nil) + @value = value + @today = today + @months = (1..Month::NAMES.size).map { |value| Month.new(year: self, value:) } + end + + def leap_year? + date = Date.new(@value, 1, 1) + date.leap? + end + + def generate(vertical: false) + rows = [header, *@months.map(&:to_array)] + table = Table.new(rows:, today: @today) + table.generate(vertical:) + end + + private + + def header + [@value.to_s, *(%w[Su Mo Tu We Th Fr Sa] * 5), 'Su', 'Mo'] + end + end +end diff --git a/lib/sg_strange_calendar.rb b/lib/sg_strange_calendar.rb index 5bfc5e5..22fdf2c 100644 --- a/lib/sg_strange_calendar.rb +++ b/lib/sg_strange_calendar.rb @@ -1,9 +1,11 @@ +require_relative 'calendar/year' + class SgStrangeCalendar def initialize(year, today = nil) - # write your code here + @year = Calendar::Year.new(value: year, today:) end def generate(vertical: false) - # write your code here + @year.generate(vertical:) end end diff --git a/test/sg_strange_calendar_test.rb b/test/sg_strange_calendar_test.rb index 472d1d2..4494cd0 100644 --- a/test/sg_strange_calendar_test.rb +++ b/test/sg_strange_calendar_test.rb @@ -44,7 +44,6 @@ def test_level_1_for_2025 end def test_level_2_for_2024_01_01 - skip "レベル2にチャレンジする人はこの行を削除してください" expected = <<~TXT.chomp 2024 Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Jan [1] 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @@ -66,7 +65,6 @@ def test_level_2_for_2024_01_01 end def test_level_2_for_2024_12_09 - skip "レベル2にチャレンジする人はこの行を削除してください" expected = <<~TXT.chomp 2024 Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Jan 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @@ -88,7 +86,6 @@ def test_level_2_for_2024_12_09 end def test_level_2_for_2025_03_31 - skip "レベル2にチャレンジする人はこの行を削除してください" expected = <<~TXT.chomp 2025 Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Jan 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @@ -110,7 +107,6 @@ def test_level_2_for_2025_03_31 end def test_level_2_all - skip "レベル2にチャレンジする人はこの行を削除してください" file_path = File.expand_path('level2.txt', File.dirname(__FILE__)) calendars = File.read(file_path).lines.each_slice(13).map(&:join).map(&:chomp) from_date = Date.new(2025, 1, 1) @@ -123,7 +119,6 @@ def test_level_2_all end def test_level_3_for_2024 - skip "レベル2およびレベル3にチャレンジする人はこの行を削除してください" expected = <<~TXT.chomp 2024 Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Su 1 1 @@ -169,7 +164,6 @@ def test_level_3_for_2024 end def test_level_3_for_2024_01_01 - skip "レベル2およびレベル3にチャレンジする人はこの行を削除してください" expected = <<~TXT.chomp 2024 Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Su 1 1 @@ -216,7 +210,6 @@ def test_level_3_for_2024_01_01 end def test_level_3_for_2024_12_09 - skip "レベル2およびレベル3にチャレンジする人はこの行を削除してください" expected = <<~TXT.chomp 2024 Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Su 1 1 @@ -263,7 +256,6 @@ def test_level_3_for_2024_12_09 end def test_level_3_for_2025_03_31 - skip "レベル2およびレベル3にチャレンジする人はこの行を削除してください" expected = <<~TXT.chomp 2025 Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec Su 1 @@ -310,7 +302,6 @@ def test_level_3_for_2025_03_31 end def test_level_3_all - skip "レベル2およびレベル3にチャレンジする人はこの行を削除してください" file_path = File.expand_path('level3.txt', File.dirname(__FILE__)) calendars = File.read(file_path).lines.each_slice(38).map(&:join).map(&:chomp) from_date = Date.new(2025, 1, 1)