Ruby 日付の計算で1日ずれてしまう
受付中
回答 5
投稿
- 評価
- クリップ 0
- VIEW 3,145
あえて書かずにコードを書いています。
次のようなコードを書きました。
def get_week(year, month, day)
# 変数定義
count = 0
years = 1
monthes = 1
days = 0
week = ["日","月","火","水","木","金","土"]
while years < year
while monthes <= 12
count += get_days(years, monthes)
monthes += 1
end
monthes = 1
years +=1
end
while monthes < month
count += get_days(year, monthes)
monthes += 1
end
count += day
return week[count % 7]
end
def get_days(year, month)
if (year % 4 == 0 && year % 400 == 0 || (year % 100 != 0))
if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12)
days = 31
elsif (month == 4 || 6 || 9 || 11)
days = 30
elsif (month == 2)
days = 28
end
else
if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12)
days = 31
elsif (month == 4 || 6 || 9 || 11)
days = 30
elsif (month == 2)
days = 29
end
end
end
puts "年を入力してください:"
year = gets.to_i
puts "月を入力してください:"
month = gets.to_i
puts "日を入力してください:"
day = gets.to_i
week = get_week(year, month, day)
puts "#{year}年#{month}月#{day}日は#{week}曜日です"
これを実行し、
順に
2014
11
27と本日の日付を入力し、曜日を求めようとすると、結果が金曜日になってしまいます・・
どこがおかしいのでしょうか?
何かアドバイスご教授お願いします。
また回答者様なら上記のコードをライブラリを使わない場合、どのように変えますでしょうか?
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
+1
また回答者様なら上記のコードをライブラリを使わない場合、どのように変えますでしょうか?
- ツェラーの公式 - Wikipedia
def get_week(y, m, d)
w = ( y + ( y / 4 ) - ( y / 100 ) + (y / 400 ) + ( ( 13 * m + 8 ) / 5 ) + 7 * ( 4 * m + 5 ) + d ) % 7
["日","月","火","水","木","金","土"][w]
end
puts "年を入力してください:"
year = gets.to_i
puts "月を入力してください:"
month = gets.to_i
puts "日を入力してください:"
day = gets.to_i
week = get_week(year, month, day)
puts "#{year}年#{month}月#{day}日は#{week}曜日です"
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
if (month == 1 || 3 || 5 || 7 || 8 || 10 || 12)「monthが大の月であれば」と言うことを判断したいのでしょうが、これでは駄目です。
PHPの経験があるとのことでしたが、PHPでもこんな事は出来ませんよ。
こんな書き方ができるのは多分COBOLくらい。
正しくは、
if month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12
# または
if [1,3,5,7,8,10,12].include?(month)
ただ、普通はcase/whenを使います。
def get_days(year, month)
case month
when 1,3,5,7,8,10,12
31
when 4,6,9,11
30
when 2 # 閏年判断は2月の時だけすればいい
if year % 4 == 0 && year % 100 != 0 || year % 400 == 0
29
else
28
end
else
fail "argument(month=#{month}) error"
end
end
閏年の判断もおかしかったので直しました。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
count=1
になる0年1月1日は月曜日では無いのでは?
最後に
count -= 1
にすると上手くいきそうです。
他の細かいところは計算が合ってるかどうかはちょっと分かりません。
追記のを使うと、
(count%7) == 0
のときに曜日が出なくなります。
あと、入力範囲チェックはした方が良いと思います。
0年13月0日を計算しても無意味だと思うので。
それと、テストを書いて、既存のライブラリーと答え合わせをしてみるもの良いかと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
閏年は「(4で割り切れる)かつ(100で割り切れない)、ただし(400で割り切れるは閏年)」です。
def leap?(year)
### year % 4 == 0 && year % 400 == 0 || (year % 100 != 0) # <== 元のコード
((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0) # <== 修正後
end
(1900..2100).each {|y| puts "#{y}: #{leap?(y) ? "leap" : "-"}" } # これでテストする
また回答者様なら上記のコードをライブラリを使わない場合、どのように変えますでしょうか?私なら以下のようにします。
4年間(閏年,平年,平年,平年)をひとかたまりと考え、各月ごとの経過日数を定数テーブル(DAYS)にします。
# year, month, day は入力済み、かつ妥当性が検証されているとします。
# 4年間(閏年,平年,平年,平年)の日数
DAYS_PER_4YEARS = 366 + (365 * 3)
# 4年間(第0月目〜第47月目)各月の最終日
LAST_DAY = [31,29,31,30,31,30,31,31,30,31,30,31] +
([31,28,31,30,31,30,31,31,30,31,30,31] * 3)
# 4年間(第0月目〜第47月目)各月の経過日数
DAYS = LAST_DAY.each_with_object([0]) {|n, memo| memo << memo[-1] + n}
# 値の調整
y = year - 2000 # 2000年を0年目とする
m = month - 1 # 1月を0月目とする
d = day - 1 # 1日を0日目とする
# パラメタの計算
y1 = y / 4 # 4年間がいくつあるか
m1 = ((y % 4) * 12) + m # 4年間の範囲の第何月目か
d1 = d # 第何日目か
# 2000/01/01 からの経過日数 x を求める
x = (DAYS_PER_4YEARS * y1) + DAYS[m1] + d1
# 曜日コードは(日=0, 月=1, 火=2, ..., 土=6)とする
# 2000/01/01 が土曜日(=6)であることから、曜日コード w を求める
w = (6 + x) % 7
week = %w(日 月 火 水 木 金 土)[w] # 曜日コードを文字列に変換
puts "#{year}年#{month}月#{day}日は#{week}曜日です"
DAYS は計算の都合上第48月目が計算されていますが、無視してください。
Enumerable#each_with_object を使いましたが、以下のようにベタに書いてももちろん構いません。
DAYS = [ 0, # 第0月目
31, # 第1月目
31+29, # 第2月目
31+29+31, # 第3月目
:
また、4年間(閏年,平年,平年,平年)の規則性は2100年で破れるので、year の妥当値は 2000〜2099 になります。
分岐構造(if や case (他言語では switch))は、コードが複雑になって分かりにくくなります。
また、動作パターンも増えて不具合を追いにくくなります。
コーディングの仕方は私のようでなくても構いませんが、月の日数のように不規則なものは「表引き」を使うようにするなどして、分岐構造が少なくなるようにするのも有効なアプローチと思います。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
計算方法は、 1900年1月1日からの経過日を求め、それの 7 での剰余を求めて 1900年1月1日 の曜日からのズレから曜日を元まえるととしました。
- 年月日から曜日を判定するプログラム (Ruby) http://mie219.blog.fc2.com/blog-entry-12.html
... ツェラーの公式という便利なものがあるので、それを用いています。...という方法もありますが、今回はそれは使わずに。
2 つのコードがあります。
utils.rb
utils_test.rb
utils.rb
# coding: utf-8
# 便利メソッドを定義する。
module Utils
# 閏年でないときの各月の日数
NORMAL_DAYS_OF_MINTH = {
1 => 31, 2 => 28, 3 => 31, 4 => 30, 5 => 31, 6 => 30,
7 => 31, 8 => 31, 9 => 30, 10 => 31, 11 => 30, 12 => 31
}.freeze
# year 年 month 月の日数を求める
def days(year, month)
check_year_month(year, month) # year, month の値範囲をチェックする。
# 閏年の2月は 29 日まである。
if month == 2 && leap?(year)
29
else
NORMAL_DAYS_OF_MINTH[month]
end
end
# 曜日の名前
WEEK_NAME = %w[日 月 火 水 木 金 土]
# 曜日を求める
def wday(year, month, day)
# 1900-01-01 は月曜
past_days(year, month, day) % 7
end
# 1900-01-01 からの経過日数を得る
def past_days(year, month, day)
ans = 0
(1...year).each do |y|
ans += 365
ans += 1 if leap?(y)
end
(1...month).each do |m|
ans += days(year, m)
end
ans += day
ans
end
# year が閏年かを調べる。
def leap?(year)
(year % 4 == 0) && ((year % 400 == 0) || (year % 100 != 0))
end
# year, month の値範囲をチェックする。
def check_year_month(year, month)
if (year < 1900) || (2038 < year) || (month < 1) || (12 < month)
throw ArgumentError.new("#{year}, #{month}")
end
end
end
# --- End of File ---
utils_test.rb
coding: utf-8
require 'date'
require File.join(File.expand_path(File.dirname(__FILE__)), 'utils.rb')
#--------- test ------
include Utils
TESTS = [
[2000, 1, 31],
[2000, 2, 29],
[2000, 3, 31],
[2000, 4, 30],
[2000, 5, 31],
[2000, 6, 30],
[2000, 7, 31],
[2000, 8, 31],
[2000, 9, 30],
[2000, 10, 31],
[2000, 11, 30],
[2000, 12, 31],
[2001, 1, 31],
[2001, 2, 28],
[2001, 3, 31],
[2001, 4, 30],
[2001, 5, 31],
[2001, 6, 30],
[2001, 7, 31],
[2001, 8, 31],
[2001, 9, 30],
[2001, 10, 31],
[2001, 11, 30],
[2001, 12, 31]
]
TESTS.each do |t|
ans = Utils.days t[0], t[1]
fail "#{t.join(' ')} != #{t[0]} #{t[1]} #{ans}" unless ans == t[2]
end
ERR_TESTS = [
[nil, nil, "undefined method `<' for nil:NilClass"],
[2000, nil, "undefined method `<' for nil:NilClass"],
[nil, 1, "undefined method `<' for nil:NilClass"],
[1899, 12, 'uncaught throw #<ArgumentError: 1899, 12>'],
[2039, 1, 'uncaught throw #<ArgumentError: 2039, 1>'],
[2000, 0, 'uncaught throw #<ArgumentError: 2000, 0>'],
[2000, 13, 'uncaught throw #<ArgumentError: 2000, 13>']
]
ERR_TESTS.each do |t|
begin
Utils.days t[0], t[1]
fail 'エラーが発生しませんでした。'
rescue => ex
raise ex.to_s + t.join(' ') if ex.to_s != t[2]
end
end
# Date のメソッドと結果を比較する。
(1900..2038).each do |y|
# うるう年の判定
fail "fail leap(#{y})" unless Utils.leap?(y) == Date.new(y).leap?
# 月の日数
(1..12).each do |m|
fail "fail days(#{y}, #{m})" unless Utils.days(y, m) == ((Date.new(y, m, -1) - Date.new(y, m, 1)).to_i + 1)
# 曜日
(1..8).each do |d|
fail "fail wday(#{y}, #{m}, #{d})" unless Utils.wday(y, m, d) == Date.new(y, m, d).wday
end
end
end
today = Date.today
puts "#{today} は #{WEEK_NAME[Utils.wday(today.year, today.month, today.day)]} 曜日です。"
# $ rubocop days.rb
# $ ruby days_test.rb
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.19%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる