From b1034ed26a6390ea763d9e0b1e49d9433560ead0 Mon Sep 17 00:00:00 2001 From: jinyang Date: Wed, 4 Dec 2024 04:56:47 +0900 Subject: [PATCH] =?UTF-8?q?level3=E3=81=BE=E3=81=A7=E6=8C=91=E6=88=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/sg_strange_calendar.rb | 150 ++++++++++++++++++++++++++++++- test/sg_strange_calendar_test.rb | 9 -- 2 files changed, 148 insertions(+), 11 deletions(-) diff --git a/lib/sg_strange_calendar.rb b/lib/sg_strange_calendar.rb index 5bfc5e5..1770875 100644 --- a/lib/sg_strange_calendar.rb +++ b/lib/sg_strange_calendar.rb @@ -1,9 +1,155 @@ +require 'date' + +## +# SgStrangeCalendar クラスはカスタムカレンダーを生成するためのクラスです。 +# 年、日付の配置、およびカレンダーの形式(縦・横)を指定してカレンダーを作成します。 +# +# 主な機能: +# - カレンダーの縦方向/横方向の出力 +# - 日付のフォーマットと配置 class SgStrangeCalendar + # 各月の日数(通常年) + DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + # 各月の日数(閏年) + DAYS_IN_MONTH_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + # 月の総数 + MONTHS_NUM = 12 + + # 月の名前 + MONTHS_NAMES = %w[January February March April May June July August September October November December] + + # 曜日の名前 + DAYS_NAMES = %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday] + + private_constant :DAYS_IN_MONTH + private_constant :DAYS_IN_MONTH_LEAP + private_constant :MONTHS_NUM + private_constant :MONTHS_NAMES + private_constant :DAYS_NAMES + + ## + # カレンダーを初期化します。 + # + # - year: カレンダーを生成する年。 + # - today: 現在の日付(デフォルトはnil)。 + # + # @param year [Integer] カレンダーを生成する年 + # @param today [Date, nil] 現在の日付(オプション) def initialize(year, today = nil) - # write your code here + @year = year + @today = today + @isLeapYear = Date.leap?(year) + @primary_length = 12 + @secondary_length = 37 + + @first_date_of_year = Date.new(year, 1, 1) + @date_array = set_up_date_array(highlight_today: !@today.nil?) end + ## + # カレンダーを生成してそれを返す + # + # @param vertical [Boolean] カレンダーを縦方向に表示する場合はtrue(デフォルトはfalse) + # @return [String] テーブル def generate(vertical: false) - # write your code here + short_days = DAYS_NAMES.map { |day| day[0...2] } + repeated_days = short_days.cycle.take(37) + + short_months = MONTHS_NAMES.map { |month| month[0...3] } + + unless vertical + header_labels = repeated_days + row_labels = short_months + date_array = @date_array + else + header_labels = short_months + row_labels = repeated_days + date_array = @date_array.transpose + end + + date_format_size = header_labels.first.length + row_label_format_size = @year.to_s.length + + header = "#{@year} #{header_labels.join(' ')}" + rows = row_labels.map.with_index do |label, row_index| + # それぞれの要素の書式を合わせる + formatted_dates = date_array[row_index].map { |date| date.to_s.rjust(date_format_size) } + + formatted_dates = formatted_dates.each_with_object("") do |date, acc_string| + if date.include?("[") + # `[数字]` の場合、余分なスペースを削除してそのまま追加 + if date.length > date_format_size + 1 + acc_string.rstrip! + acc_string << "#{date}" + elsif date.length == date_format_size + 1 + acc_string << "#{date}" + else + acc_string << " #{date}" + end + else + # 通常の日付はスペース付きで追加 + acc_string << "#{date} " + end + end + + "#{label.ljust(row_label_format_size)} #{formatted_dates}".rstrip + end + + [header, rows].join("\n").rstrip + end + + private + + ## + # カレンダーの内部データ構造を初期化します。 + # + # @param highlight_today [Boolean] 今日の日付を強調するか否か + # @return [Array>] 月ごとの日付が格納された二次元配列 + def set_up_date_array(highlight_today:) + # 12 * 37の二次元配列 本カレンダーのテーブルの構造と一致する + date_array = Array.new(@primary_length) { Array.new(@secondary_length) } + + # 1日の前に何マススキップされているかを格納する変数 + skipped_date = @first_date_of_year.wday + + 0...@primary_length.times do |month_index| + month_array = date_array[month_index] + + # その月の日数 + days = days_in_month(month_index + 1) + + # その月において、日数を考慮しない際にもっと大きい日数 + # [nil, nil, 1, 2 .. 31, 32, 33]なら33を指す + max_date_number_in_this_row = nil + + 0...@secondary_length.times do |day_index| + date = day_index - skipped_date + 1 + if day_index >= skipped_date && date <= days + if highlight_today && @today.day == date && @today.month - 1 == month_index + month_array[day_index] = "[#{date}]" + else + month_array[day_index] = date + end + end + max_date_number_in_this_row = date if day_index == @secondary_length - 1 + end + + skipped_date = (9 - max_date_number_in_this_row + days) % 7 + end + + date_array + end + + ## + # 指定された月の日数を取得します。 + # + # @param month [Integer] 月(1〜12) + # @return [Integer] 月の日数 + def days_in_month(month) + # @yearは必須であるから、nil判定しない + @isLeapYear = Date.leap?(@year) if @isLeapYear.nil? + @isLeapYear ? DAYS_IN_MONTH_LEAP[month - 1]: DAYS_IN_MONTH[month - 1] 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)