質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

5回答

4711閲覧

Ruby 日付の計算で1日ずれてしまう

K_T_T_K

総合スコア231

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

0クリップ

投稿2014/11/26 16:03

ライブラリ(date)を使えばすぐに解決できてしまう問題ですが、Rubyの勉強のために
あえて書かずにコードを書いています。
次のようなコードを書きました。

lang

1def get_week(year, month, day) 2 # 変数定義 3 count = 0 4 years = 1 5 monthes = 1 6 days = 0 7 week = ["日","月","火","水","木","金","土"] 8 9 while years < year 10 while monthes <= 12 11 count += get_days(years, monthes) 12 monthes += 1 13 end 14 monthes = 1 15 years +=1 16 end 17 while monthes < month 18 count += get_days(year, monthes) 19 monthes += 1 20 end 21 count += day 22 23 return week[count % 7] 24end 25 26 27def get_days(year, month) 28 if (year % 4 == 0 && year % 400 == 0 || (year % 100 != 0)) 29 if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12) 30 days = 31 31 elsif (month == 4 || 6 || 9 || 11) 32 days = 30 33 elsif (month == 2) 34 days = 28 35 end 36 else 37 if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12) 38 days = 31 39 elsif (month == 4 || 6 || 9 || 11) 40 days = 30 41 elsif (month == 2) 42 days = 29 43 end 44 end 45end 46 47puts "年を入力してください:" 48year = gets.to_i 49puts "月を入力してください:" 50month = gets.to_i 51puts "日を入力してください:" 52day = gets.to_i 53 54week = get_week(year, month, day) 55puts "#{year}年#{month}月#{day}日は#{week}曜日です" 56

これを実行し、
順に
2014
11
27と本日の日付を入力し、曜日を求めようとすると、結果が金曜日になってしまいます・・
どこがおかしいのでしょうか?
何かアドバイスご教授お願いします。

また回答者様なら上記のコードをライブラリを使わない場合、どのように変えますでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

前回含めて誰も突っ込んでないのですが、

if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12)

「monthが大の月であれば」と言うことを判断したいのでしょうが、これでは駄目です。
PHPの経験があるとのことでしたが、PHPでもこんな事は出来ませんよ。
こんな書き方ができるのは多分COBOLくらい。

正しくは、

lang

1if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12 2# または 3if [1,3,5,7,8,10,12].include?(month)

ただ、普通はcase/whenを使います。

lang

1def get_days(year, month) 2 case month 3 when 1,3,5,7,8,10,12 4 31 5 when 4,6,9,11 6 30 7 when 2 # 閏年判断は2月の時だけすればいい 8 if year % 4 == 0 && year % 100 != 0 || year % 400 == 0 9 29 10 else 11 28 12 end 13 else 14 fail "argument(month=#{month}) error" 15 end 16end

閏年の判断もおかしかったので直しました。

投稿2014/11/27 05:54

otn

総合スコア84423

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

西暦 1 年 1 月 0 日(?)を日曜日としてその日からの日数を計算しているように見えますが、その前提は正しいのでしょうか?

また回答者様なら上記のコードをライブラリを使わない場合、どのように変えますでしょうか?

lang

1def get_week(y, m, d) 2 w = ( y + ( y / 4 ) - ( y / 100 ) + (y / 400 ) + ( ( 13 * m + 8 ) / 5 ) + 7 * ( 4 * m + 5 ) + d ) % 7 3 ["日","月","火","水","木","金","土"][w] 4end 5 6puts "年を入力してください:" 7year = gets.to_i 8puts "月を入力してください:" 9month = gets.to_i 10puts "日を入力してください:" 11day = gets.to_i 12 13week = get_week(year, month, day) 14puts "#{year}年#{month}月#{day}日は#{week}曜日です"

投稿2014/11/27 00:29

ngyuki

総合スコア4514

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

前回の回答 のコードに曜日を求めるメソッドを追加してみました。
計算方法は、 1900年1月1日からの経過日を求め、それの 7 での剰余を求めて 1900年1月1日 の曜日からのズレから曜日を元まえるととしました。

... ツェラーの公式という便利なものがあるので、それを用いています。...

という方法もありますが、今回はそれは使わずに。

2 つのコードがあります。
utils.rb
utils_test.rb

utils.rb

lang

1# coding: utf-8 2 3# 便利メソッドを定義する。 4module Utils 5 # 閏年でないときの各月の日数 6 NORMAL_DAYS_OF_MINTH = { 7 1 => 31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30, 8 7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31 9 }.freeze 10 11 # year 年 month 月の日数を求める 12 def days(year, month) 13 check_year_month(year, month) # year, month の値範囲をチェックする。 14 15 # 閏年の2月は 29 日まである。 16 if month == 2 && leap?(year) 17 29 18 else 19 NORMAL_DAYS_OF_MINTH[month] 20 end 21 end 22 23 # 曜日の名前 24 WEEK_NAME = %w[日 月 火 水 木 金 土] 25 26 # 曜日を求める 27 def wday(year, month, day) 28 # 1900-01-01 は月曜 29 past_days(year, month, day) % 7 30 end 31 32 # 1900-01-01 からの経過日数を得る 33 def past_days(year, month, day) 34 ans = 0 35 (1...year).each do |y| 36 ans += 365 37 ans += 1 if leap?(y) 38 end 39 (1...month).each do |m| 40 ans += days(year, m) 41 end 42 ans += day 43 ans 44 end 45 46 # year が閏年かを調べる。 47 def leap?(year) 48 (year % 4 == 0) && ((year % 400 == 0) || (year % 100 != 0)) 49 end 50 51 # year, month の値範囲をチェックする。 52 def check_year_month(year, month) 53 if (year < 1900) || (2038 < year) || (month < 1) || (12 < month) 54 throw ArgumentError.new("#{year}, #{month}") 55 end 56 end 57end 58 59# --- End of File ---

utils_test.rb

lang

1 coding: utf-8 2 3require 'date' 4require File.join(File.expand_path(File.dirname(__FILE__)), 'utils.rb') 5 6#--------- test ------ 7include Utils 8 9TESTS = [ 10 [2000, 1, 31], 11 [2000, 2, 29], 12 [2000, 3, 31], 13 [2000, 4, 30], 14 [2000, 5, 31], 15 [2000, 6, 30], 16 [2000, 7, 31], 17 [2000, 8, 31], 18 [2000, 9, 30], 19 [2000, 10, 31], 20 [2000, 11, 30], 21 [2000, 12, 31], 22 23 [2001, 1, 31], 24 [2001, 2, 28], 25 [2001, 3, 31], 26 [2001, 4, 30], 27 [2001, 5, 31], 28 [2001, 6, 30], 29 [2001, 7, 31], 30 [2001, 8, 31], 31 [2001, 9, 30], 32 [2001, 10, 31], 33 [2001, 11, 30], 34 [2001, 12, 31] 35] 36 37TESTS.each do |t| 38 ans = Utils.days t[0], t[1] 39 fail "#{t.join(' ')} != #{t[0]} #{t[1]} #{ans}" unless ans == t[2] 40end 41 42ERR_TESTS = [ 43 [nil, nil, "undefined method `<' for nil:NilClass"], 44 [2000, nil, "undefined method `<' for nil:NilClass"], 45 [nil, 1, "undefined method `<' for nil:NilClass"], 46 47 [1899, 12, 'uncaught throw #<ArgumentError: 1899, 12>'], 48 [2039, 1, 'uncaught throw #<ArgumentError: 2039, 1>'], 49 50 [2000, 0, 'uncaught throw #<ArgumentError: 2000, 0>'], 51 [2000, 13, 'uncaught throw #<ArgumentError: 2000, 13>'] 52] 53 54ERR_TESTS.each do |t| 55 begin 56 Utils.days t[0], t[1] 57 fail 'エラーが発生しませんでした。' 58 rescue => ex 59 raise ex.to_s + t.join(' ') if ex.to_s != t[2] 60 end 61end 62 63# Date のメソッドと結果を比較する。 64(1900..2038).each do |y| 65 # うるう年の判定 66 fail "fail leap(#{y})" unless Utils.leap?(y) == Date.new(y).leap? 67 68 # 月の日数 69 (1..12).each do |m| 70 fail "fail days(#{y}, #{m})" unless Utils.days(y, m) == ((Date.new(y, m, -1) - Date.new(y, m, 1)).to_i + 1) 71 # 曜日 72 (1..8).each do |d| 73 fail "fail wday(#{y}, #{m}, #{d})" unless Utils.wday(y, m, d) == Date.new(y, m, d).wday 74 end 75 end 76end 77 78today = Date.today 79puts "#{today} は #{WEEK_NAME[Utils.wday(today.year, today.month, today.day)]} 曜日です。" 80 81# $ rubocop days.rb 82# $ ruby days_test.rb

投稿2014/11/27 21:48

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

不具合原因とは直接関係ないようですが、閏年判定が違うようです。
閏年は「(4で割り切れる)かつ(100で割り切れない)、ただし(400で割り切れるは閏年)」です。

lang

1def leap?(year) 2 ### year % 4 == 0 && year % 400 == 0 || (year % 100 != 0) # <== 元のコード 3 ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0) # <== 修正後 4end 5 6(1900..2100).each {|y| puts "#{y}: #{leap?(y) ? "leap" : "-"}" } # これでテストする

また回答者様なら上記のコードをライブラリを使わない場合、どのように変えますでしょうか?

私なら以下のようにします。
4年間(閏年,平年,平年,平年)をひとかたまりと考え、各月ごとの経過日数を定数テーブル(DAYS)にします。

lang

1# year, month, day は入力済み、かつ妥当性が検証されているとします。 2 3# 4年間(閏年,平年,平年,平年)の日数 4DAYS_PER_4YEARS = 366 + (365 * 3) 5 6# 4年間(第0月目〜第47月目)各月の最終日 7LAST_DAY = [31,29,31,30,31,30,31,31,30,31,30,31] + 8 ([31,28,31,30,31,30,31,31,30,31,30,31] * 3) 9 10# 4年間(第0月目〜第47月目)各月の経過日数 11DAYS = LAST_DAY.each_with_object([0]) {|n, memo| memo << memo[-1] + n} 12 13# 値の調整 14y = year - 2000 # 2000年を0年目とする 15m = month - 1 # 1月を0月目とする 16d = day - 1 # 1日を0日目とする 17 18# パラメタの計算 19y1 = y / 4 # 4年間がいくつあるか 20m1 = ((y % 4) * 12) + m # 4年間の範囲の第何月目か 21d1 = d # 第何日目か 22 23# 2000/01/01 からの経過日数 x を求める 24x = (DAYS_PER_4YEARS * y1) + DAYS[m1] + d1 25 26# 曜日コードは(日=0, 月=1, 火=2, ..., 土=6)とする 27# 2000/01/01 が土曜日(=6)であることから、曜日コード w を求める 28w = (6 + x) % 7 29 30week = %w(日 月 火 水 木 金 土)[w] # 曜日コードを文字列に変換 31 32puts "#{year}年#{month}月#{day}日は#{week}曜日です"

DAYS は計算の都合上第48月目が計算されていますが、無視してください。
Enumerable#each_with_object を使いましたが、以下のようにベタに書いてももちろん構いません。

lang

1DAYS = [ 0, # 第0月目 2 31, # 第1月目 3 31+29, # 第2月目 4 31+29+31, # 第3月目 5 :

また、4年間(閏年,平年,平年,平年)の規則性は2100年で破れるので、year の妥当値は 2000〜2099 になります。

分岐構造(if や case (他言語では switch))は、コードが複雑になって分かりにくくなります。
また、動作パターンも増えて不具合を追いにくくなります。
コーディングの仕方は私のようでなくても構いませんが、月の日数のように不規則なものは「表引き」を使うようにするなどして、分岐構造が少なくなるようにするのも有効なアプローチと思います。

投稿2014/11/27 05:21

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

暦の難しい話は措くとして。

count=1になる0年1月1日は月曜日では無いのでは?
最後にcount -= 1にすると上手くいきそうです。
他の細かいところは計算が合ってるかどうかはちょっと分かりません。

追記のを使うと、(count%7) == 0のときに曜日が出なくなります。

あと、入力範囲チェックはした方が良いと思います。
0年13月0日を計算しても無意味だと思うので。

それと、テストを書いて、既存のライブラリーと答え合わせをしてみるもの良いかと思います。

投稿2014/11/27 00:02

argius

総合スコア9388

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問