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

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

ただいまの
回答率

89.96%

〇ヶ月後、〇年後の応当日。

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 290

Take_it

score 189

やりたいこと

年次有給休暇の発効処理。
入社年月日の6か月後(半年後)の応当日、以後そこからさらに1年後(入社から1年半後)、2年後(入社から2年半後)・・・というように日付を取得したい。

試したこと

本番環境ではデータベースに登録してある入社年月日から順に取得し、システムにアクセスした日を超えたらループを終了することを想定しています。
日付を正しく取得できるかを確かめるための以下のテストでは、2019-01-01から2019-12-31までの1年間の日付に対して、半年後~10年半後までを生成してみました。

<?php
ini_set( 'display_errors', 1 );
  $base_dates = array();
  $st = new DateTimeImmutable('2019-01-01');
  $date = new DateTime('2019-01-01');
  $ed = new DateTimeImmutable('2019-12-31');
  $stop = 0;
  $array = array();
  echo "<pre>";
  print_r($date);
  echo "</pre>";
//  exit;
  while($stop<1)://一致するまで繰り返す
    $day = $date->format('Y-m-d');
    $hantoshi = new DateTime($day);
    $hantoshi = $hantoshi->modify('+6 months')->format('Y-m-d');
    $y1 = new DateTime($day);
    $y1 = $y1->modify('+6 months +1 year')->format('Y-m-d');
    $y2 = new DateTime($day);
    $y2 = $y2->modify('+6 months +2 year')->format('Y-m-d');
    $y3 = new DateTime($day);
    $y3 = $y3->modify('+6 months +3 year')->format('Y-m-d');
    $y4 = new DateTime($day);
    $y4 = $y4->modify('+6 months +4 year')->format('Y-m-d');
    $y5 = new DateTime($day);
    $y5 = $y5->modify('+6 months +5 year')->format('Y-m-d');
    $y6 = new DateTime($day);
    $y6 = $y6->modify('+6 months +6 year')->format('Y-m-d');
    $y7 = new DateTime($day);
    $y7 = $y7->modify('+6 months +7 year')->format('Y-m-d');
    $y8 = new DateTime($day);
    $y8 = $y8->modify('+6 months +8 year')->format('Y-m-d');
    $y9 = new DateTime($day);
    $y9 = $y9->modify('+6 months +9 year')->format('Y-m-d');
    $y10 = new DateTime($day);
    $y10 = $y10->modify('+6 months +10 year')->format('Y-m-d');
    $array[$day]['半年後'] = $hantoshi;
    $array[$day]['1年半後'] = $y1;
    $array[$day]['2年半後'] = $y2;
    $array[$day]['3年半後'] = $y3;
    $array[$day]['4年半後'] = $y4;
    $array[$day]['5年半後'] = $y5;
    $array[$day]['6年半後'] = $y6;
    $array[$day]['7年半後'] = $y7;
    $array[$day]['8年半後'] = $y8;
    $array[$day]['9年半後'] = $y9;
    $array[$day]['10年半後'] = $y10;

    $date = $date->modify('+1 day');
    if($date>$ed):
      ++$stop;
    endif;
  endwhile;

  echo "<pre>";
    print_r($array);
    echo "</pre>";
  exit;
?>


これだと結果として月末が31日まである月とそうではない月とで、月末応当日が期待した結果になりません。
例:5/31の半年後は11/30としたいが、12/1になってしまう。

そこで、次のやり方を試みました。

<?php
ini_set( 'display_errors', 1 );
  $base_dates = array();
  $st = new DateTimeImmutable('2010-01-01');
  $date = new DateTime('2010-01-01');
  $ed = new DateTimeImmutable('2010-12-31');
  $now = new DateTimeImmutable();
  $today = $now->format('Y-m-d');//対象日
  $stop = 0;
  $array = array();

  while($stop<1)://一致するまで繰り返す
    $day = $date->format('Y-m-d');
    $d = $date->format('d');//日付のみ抽出
    $h = new DateTime($day);
    $h = $h->modify('+6 months');
    $h_d = $h->format('d');
    if($h_d!=$d)://異なる日が返った
      $h = $h->modify('last day of last month');//前月の月末日
    endif;
    $array[$day]['半年後'] = $h->format('Y-m-d');//半年後の日付。$hは半年後のDateTimeオブジェクト
    //対象日と一致するか過去の日付群
      $stop2 = 0;
      $cnt = 1;
      while($stop2<1):
        $h->modify('+1 year');//1年後ろへ
        if($h>=$now)://超えた
          ++$stop2;
        else://超えてない
          $array[$day][$cnt] = $h->format('Y-m-d');
          ++$cnt;
        endif;
      endwhile;
    $date = $date->modify('+1 day');
    if($date>$ed):
      ++$stop;
    endif;
  endwhile;

  echo "<pre>";
    print_r($array);
    echo "</pre>";
  exit;
?>


(こちらでは先ほどと違い、2010-01-01~2010-12-31の各日付に対しての、半年後~〇年半後を生成していき、今日の日付に到達したら処理を止めるようにしてあります)

この方法だと、5/31の半年後応当日は意図した通り11/30になっており、概ね良好なのですが、
8/29~8/31が入社日となった場合、平年はこれらは全て各年の2/28が応当日として正解ですが、閏年の場合は2/29を応当日としなければいけません。

入社年月日の月と日を取り出し、月が8でかつ日が29以上で、なおかつ生成した応当日の年が閏年の場合は・・・・と力業で解決する以外に、なにかもっとスマートに応当日を求める方法はないものでしょうか。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

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

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • m.ts10806

    2019/07/12 10:41

    あ、「応当日」も大丈夫ですね、これまた失礼

    キャンセル

  • Take_it

    2019/07/12 10:41

    応当日 は 応当日 で合ってると思いますが。
    https://ja.wiktionary.org/wiki/%E5%BF%9C%E5%BD%93%E6%97%A5

    発効日 は、 発行日 の誤用ではないかというご指摘でしょうか。
    別に紙切れを「発行」するわけではなく、「有給休暇取得の権利」が「効力を発する」ので、「発効日」と表現しています。

    キャンセル

  • m.ts10806

    2019/07/12 10:56

    回答依頼いただいていますし失礼もしてしまったので
    しっかり考えたうえで回答させていただきました。
    ご参考まで。

    キャンセル

回答 2

checkベストアンサー

+1

date()のフォーマット"t"で「指定の月の日数」が取得できます。

<?php
echo date("t",strtotime("20180201")).PHP_EOL;
echo date("t",strtotime("20190201")).PHP_EOL;
echo date("t",strtotime("20200201")).PHP_EOL;
echo date("t",strtotime("20210201")).PHP_EOL;
echo date("t",strtotime("20220201")).PHP_EOL;
echo date("t",strtotime("20230201")).PHP_EOL;
/**
28
28
29
28
28
28
**

問題になるのは2月だけなので、"t"を利用すれば閏年判定を入れなくても
「8月且つ29日以降の入社日であれば2月の最終日("t")とする」が対応できそうに思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/07/12 11:17

    なるほど!確かに閏年判定は関係なくできました。

    $d = $date->format('d');//日付のみ抽出

    のところで、
    $m = $date->format('m');//月のみ抽出
    も加えておき、

    $array[$day][$cnt] = $h->format('Y-m-d');

    を、

    if($m=='8' AND $d>=29):
    $array[$day][$cnt] = $h->format('Y-m-t');
    else:
    $array[$day][$cnt] = $h->format('Y-m-d');
    endif;

    とすることで、

    [2010-08-28] => Array
    (
    [半年後] => 2011-02-28
    [1] => 2012-02-28
    [2] => 2013-02-28
    [3] => 2014-02-28
    [4] => 2015-02-28
    [5] => 2016-02-28
    [6] => 2017-02-28
    [7] => 2018-02-28
    [8] => 2019-02-28
    )

    [2010-08-29] => Array
    (
    [半年後] => 2011-02-28
    [1] => 2012-02-29
    [2] => 2013-02-28
    [3] => 2014-02-28
    [4] => 2015-02-28
    [5] => 2016-02-29
    [6] => 2017-02-28
    [7] => 2018-02-28
    [8] => 2019-02-28
    )

    [2010-08-30] => Array
    (
    [半年後] => 2011-02-28
    [1] => 2012-02-29
    [2] => 2013-02-28
    [3] => 2014-02-28
    [4] => 2015-02-28
    [5] => 2016-02-29
    [6] => 2017-02-28
    [7] => 2018-02-28
    [8] => 2019-02-28
    )

    [2010-08-31] => Array
    (
    [半年後] => 2011-02-28
    [1] => 2012-02-29
    [2] => 2013-02-28
    [3] => 2014-02-28
    [4] => 2015-02-28
    [5] => 2016-02-29
    [6] => 2017-02-28
    [7] => 2018-02-28
    [8] => 2019-02-28
    )

    と、意図した結果を導くことができました。

    日付関係で、「〇ヶ月後、〇年後の応当日」って結構使うので、DateTimeクラスでもっとサクっと実現できたらいいのになぁ。。

    ありがとうございました!

    キャンセル

  • 2019/07/12 11:20

    ヒントになったようで何よりです。
    「29日以降」「半年後」というのも若干イレギュラーでもありますので、
    そこは固定で判定するしかないかなとも思いました。

    キャンセル

0

当該日が月末(翌日の日付が1日)だったとき、翌日に対して○ヶ月後を実行し1日引く

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

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

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