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

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

ただいまの
回答率

90.36%

  • Ruby

    10211questions

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

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

受付中

回答 5

投稿

  • 評価
  • クリップ 0
  • VIEW 2,296

K_T_T_K

score 313

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

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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 5

+1

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

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

ツェラーの公式 - 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で質問しよう!

  • ただいまの回答率 90.36%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Ruby

    10211questions

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