LoginSignup
0
0

More than 3 years have passed since last update.

ララ「13歳と294日って、どうやって求めるルン?」 on PHP

Last updated at Posted at 2020-07-26

はじめに

「スター☆トゥインクルプリキュア」第2話(2019年2月10日放送)で、こんなやりとりがありました。

ひかる「ララちゃん1ってすごいよねぇ。ロケット操縦して、修理までしちゃうんだもん」
ララ「いい大人ルン。できて当然ルン」
ひかる「えっ?大人って?いくつなの?」
ララ「地球の年齢だと…」
AI「13歳と294日です」
ひかる「わたしと同い年じゃん!」

さて、今ララの年齢は、地球基準で何歳と何日なのでしょうか?

それをプログラムで算出したいと思って調べたのですが、出てくるのは「何年何ヶ月と何日」というのを求めるものばかり…。
そこで、本記事では「任意の2つの日付間の経過年数と日数」をPHPで求める方法を考えたいと思います。

なおPHP独自のクラスや構文が出てきますが、アルゴリズムは他の言語でも活用できると思います。

前提条件

「任意の2つの日付」を、「基準日(1)」と「基準日(2)」と表すこととします。
なお基準日(1)は、基準日(2)より前であるものとします。

まず、基準日(1)ですが、同年7月7日の第22話でララの誕生日であることに触れられていること、キャラクター設定上も7月7日が誕生日とされていること、そしてひかると同じ「中学2年生」などなどから、第22話の放送の14年前に当たる「2005年7月7日」とします。

日付計算の際には、基準日(1)当日を含めないものとします。

PHPのバージョンは7.2.30を前提としていますが、7系であれば多分大丈夫だと思います。

考え方

基準日(2)によって、次の3パターンに分けます。
基準日(2)を、以下では$y_2年m_2月d_2日$とします。

そして先ほどの「2005年7月7日」は次のように設定します。

y_1 = 2005, m_1 = 7, d_1 = 7

基準日(1)と基準日(2)の月日が同じ場合

数式で表すと、$m_1 = m_2 かつ d_1 = d_2$の場合、となります。
この場合は単純に、$(y_2 - y_1)歳と0日$を返せば良いことになります。

例)基準日(2)が「2019年7月7日」の場合
年数:$2019 - 2005 = 14$
結果:14歳と0日

基準日(1)と基準日(2)の月日が違う場合(パターン1)

ここでは$y_2年m_1月d_1日$が基準日(2)より前の場合を取り上げます。
それ以外の場合は、パターン2を参照してください。

この場合は、年の計算は先ほどと同様に$(y_2 - y_1)$で計算します。
あとは日付を、$y_2年y_2月y_2日 - y_2年m_1月d_1日$(初日不算入)で計算します。

例)基準日(2)が「2019年7月26日」の場合:
年数:$2019 - 2005 = 14$
日数:$2019年7月26日 - 2019年7月7日 = 19$
結果:14歳と19日

例)基準日(2)が「2020年7月26日」の場合:
☆2020年は閏年
年数:$2020 - 2005 = 15$
日数:$2020年7月26日 - 2020年7月7日 = 19$
結果:15歳と19日

基準日(1)と基準日(2)の月日が違う場合(パターン2)

ここでは$y_2年m_1月d_1日$が基準日(2)より後の場合を取り上げます。
それ以外の場合は、パターン1を参照してください。

この場合、年の計算は$(y_2 - y_1 - 1)$になります。
日付ですが、$y_2年y_2月y_2日 - (y_2 - 1)年m_1月d_1日$(初日不算入)で計算します。

例)基準日(2)が「2019年4月27日」の場合
年数:$2019 - 2005 - 1 = 13$
日数:$2019年4月27日 - (2019-1)年7月7日 = 2019年4月27日 - 2018年7月7日 = 294$
結果:13歳と294日

例)基準日(2)が「2020年4月27日」の場合
☆2020年は閏年
年数:$2020 - 2005 - 1 = 14$
日数:$2020年4月27日 - (2020-1)年7月7日 = 2020年4月27日 - 2019年7月7日 = 295$
結果:14歳と295日

例)基準日(2)が「2019年2月27日」の場合
年数:$2019 - 2005 - 1 = 13$
日数:$2019年2月27日 - (2019-1)年7月7日 = 2019年2月27日 - 2018年7月7日 = 235$
結果:13歳と235日

例)基準日(2)が「2020年2月27日」の場合
☆2020年は閏年
年数:$2020 - 2005 - 1 = 14$
日数:$2020年2月27日 - (2020-1)年7月7日 = 2020年2月27日 - 2019年7月7日 = 235$
結果:14歳と235日

実装していきます

長いので折りたたむルン
datediff.php
<?php
/**
 * dateDiffの戻り値クラス
 */
class DateDiffResult {
    /** 年数 */
    public $years;
    /** 日数 */
    public $days;

    function __construct(int $years, int $days) {
        $this->years = $years;
        $this->days = $days;
    }
}

/**
 * 指定年月日の0時0分0秒のDateTimeを返す
 * 
 * @param int $year 年
 * @param int $month 月
 * @param int $day 日
 * @return DateTime
 */
function getDateTime(int $year, int $month, int $day) : DateTime {
    $date = new DateTime();
    $date->setDate($year, $month, $day);
    $date->setTime(0, 0, 0);
    return $date;
}

/**
 * 基準日(1)から基準日(2)まで、何年と何日経過したかを求める(初日不算入)
 * 
 * @param DateTime $inputDate1 基準日(1)
 * @param DateTime $inputDate2 基準日(2)
 * @return DateDiffResult
 */
function dateDiff(DateTime $inputDate1, DateTime $inputDate2) : DateDiffResult {
    // 各変数をセットする
    $y1 = intval($inputDate1->format("Y"));
    $m1 = intval($inputDate1->format("n"));
    $d1 = intval($inputDate1->format("j"));
    $y2 = intval($inputDate2->format("Y"));
    $m2 = intval($inputDate2->format("n"));
    $d2 = intval($inputDate2->format("j"));
    $date1 = getDateTime($y1, $m1, $d1);
    $date2 = getDateTime($y2, $m2, $d2);
    // 基準日(1)と基準日(2)の月日が同じ場合
    if ($m1 == $m2 && $d1 == $d2) {
        return new DateDiffResult($y2 - $y1, 0);
    }
    // y_2年m_1月d_1日
    $date3 = getDateTime($y2, $m1, $d1);

    // y_2年y_2月y_2日 - y_2年m_1月d_1日
    $diff1 = $date3->diff($date2);
    if ($diff1->format("%R") == "+") {
        // y_2年m_1月d_1日が基準日(2)より前の場合
        return new DateDiffResult($y2 - $y1, intval($diff1->format("%a")));
    } else {
        // y_2年m_1月d_1日が基準日(2)より後の場合
        $date4 = getDateTime($y2 - 1, $m1, $d1);
        $diff2 = $date4->diff($date2);
        return new DateDiffResult($y2 - $y1 - 1, intval($diff2->format("%a")));
    }
}

テストします

長いので折りたたむルン
datedifftest.php
<?php
require_once("datediff.php");

use PHPUnit\Framework\TestCase;

class DateDiffTest extends TestCase {

    // 基準日(1)と基準日(2)の月日が同じ場合
    public function testDiff0()
    {
        $date1 = getDateTime(2005, 7, 7);
        $date2 = getDateTime(2019, 7, 7);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(14, $actual->years);
        $this->assertEquals(0, $actual->days);
    }

    // 基準日(1)と基準日(2)の月日が違う場合(パターン1)その1
    public function testDiff1_1() {
        /**
         * 基準日(2)が「2019年7月26日」の場合:
         * 年数:$2019 - 2005 = 14$
         * 日数:$2019年7月26日 - 2019年7月7日 = 19$
         * 結果:14歳と19日
         */
        $date1 = getDateTime(2005, 7, 7);
        $date2 = getDateTime(2019, 7, 26);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(14, $actual->years);
        $this->assertEquals(19, $actual->days);
    }

    // 基準日(1)と基準日(2)の月日が違う場合(パターン1)その2
    public function testDiff1_2() {
        /**
         * 基準日(2)が「2020年7月26日」の場合:
         * ☆2020年は閏年
         * 年数:$2020 - 2005 = 15$
         * 日数:$2020年7月26日 - 2020年7月7日 = 19$
         * 結果:15歳と19日
         */
        $date1 = getDateTime(2005, 7, 7);
        $date2 = getDateTime(2020, 7, 26);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(15, $actual->years);
        $this->assertEquals(19, $actual->days);
    }

    // 基準日(1)と基準日(2)の月日が違う場合(パターン2)その1
    public function testDiff2_1() {
        /**
         * 基準日(2)が「2019年4月27日」の場合
         * 年数:$2019 - 2005 - 1 = 13$
         * 日数:$2019年4月27日 - (2019-1)年7月7日 = 2019年4月27日 - 2018年7月7日 = 294$
         * 結果:13歳と294日
         */
        $date1 = getDateTime(2005, 7, 7);
        $date2 = getDateTime(2019, 4, 27);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(13, $actual->years);
        $this->assertEquals(294, $actual->days);
    }

    // 基準日(1)と基準日(2)の月日が違う場合(パターン2)その2
    public function testDiff2_2() {
        /**
         * 基準日(2)が「2020年4月27日」の場合
         * ☆2020年は閏年
         * 年数:$2020 - 2005 - 1 = 14$
         * 日数:$2020年4月27日 - (2020-1)年7月7日 = 2020年4月27日 - 2019年7月7日 = 295$
         * 結果:14歳と295日
         */
        $date1 = getDateTime(2005, 7, 7);
        $date2 = getDateTime(2020, 4, 27);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(14, $actual->years);
        $this->assertEquals(295, $actual->days);
    }

    // 基準日(1)と基準日(2)の月日が違う場合(パターン2)その3
    public function testDiff2_3() {
        /**
         * 基準日(2)が「2019年2月27日」の場合
         * 年数:$2019 - 2005 - 1 = 13$
         * 日数:$2019年2月27日 - (2019-1)年7月7日 = 2019年2月27日 - 2018年7月7日 = 235$
         * 結果:13歳と235日
         */
        $date1 = getDateTime(2005, 7, 7);
        $date2 = getDateTime(2019, 2, 27);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(13, $actual->years);
        $this->assertEquals(235, $actual->days);
    }

    // 基準日(1)と基準日(2)の月日が違う場合(パターン2)その4
    public function testDiff2_4() {
        /**
         * 基準日(2)が「2020年2月27日」の場合
         * ☆2020年は閏年
         * 年数:$2020 - 2005 - 1 = 14$
         * 日数:$2020年2月27日 - (2020-1)年7月7日 = 2020年2月27日 - 2019年7月7日 = 235$
         * 結果:14歳と235日
         */
        $date1 = getDateTime(2005, 7, 7);
        $date2 = getDateTime(2020, 2, 27);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(14, $actual->years);
        $this->assertEquals(235, $actual->days);
    }

    // 閏年チェック(1)
    public function testDiff_uruu_1() {
        $date1 = getDateTime(2020, 2, 28);
        $date2 = getDateTime(2020, 3, 1);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(0, $actual->years);
        $this->assertEquals(2, $actual->days);
    }

    // 閏年チェック(2)
    public function testDiff_uruu_2() {
        $date1 = getDateTime(2020, 2, 28);
        $date2 = getDateTime(2021, 2, 1);
        $actual = dateDiff($date1, $date2);
        $this->assertEquals(0, $actual->years);
        $this->assertEquals(339, $actual->days);
    }
}

作りました

ここまでのものを使って、hokkaidosm.net プリキュアデータ内にララちゃんっていくつなの?を追加しました。
Mastodonでは毎日午前0時に配信予定です。

2020/08/01追記分

7月27日(リリース翌日)の午前0時過ぎ…

「15歳と19日です」(2020年7月26日現在)

ララ「オヨ!?何で昨日の日付ルン!?」

その日の昼…

「15歳と20日です」(2020年7月27日現在)

ララ「オヨ!?昼になったら今日の日付になってるルン!」
ララ「でも、Mastodonはちゃんと0時に今日のデータになってるルン」

原因を調べてみる

Mastodonを投稿するPHPスクリプト2は、次のcron文で呼び出しています。

0 0 * * * /usr/bin/php /var/www/html_precure/star_twincle/lala_age/toot.php >/dev/null 2>&1

一方で、Apacheの設定は次のようになっています(重要な部分だけ抜き出しています)。

/etc/httpd/conf.d/vhost.conf
<VirtualHost *:443>
    ServerName precure.hokkaidosm.net
    DocumentRoot /var/www/html_precure
    DirectoryIndex index.php index.html
    <Directory "/var/www/html_precure">
        <FilesMatch \.php$>
            SetHandler "proxy:fcgi://127.0.0.1:9072"
        </FilesMatch>
    </Directory>
</VirtualHost>

そしてこの 127.0.0.1:9072 の正体はこちらです。

/etc/opt/remi/php72/php-fpm.d/www.conf
listen = 127.0.0.1:9072

ということで、それぞれのデフォルトタイムゾーンを確認してみます。

[hokkaidosm@ik1-324-22250 ~]$ /usr/bin/php -i | grep "Default timezone"
Default timezone => Asia/Tokyo
[hokkaidosm@ik1-324-22250 ~]$ /opt/remi/php72/root/usr/bin/php -i | grep "Default timezone"
Default timezone => UTC

つまり、

  • cronで実行する場合:タイムゾーンは日本時間「Asia/Tokyo」
  • サイトへのアクセス:タイムゾーンはUTC(日本時間より9時間遅い)

となっていたわけです。

変えてみた

そこで、 getDateTime を変更しました。

datediff.php
/**
 * 指定年月日の0時0分0秒のDateTimeを返す
 * 
 * @param int $year 年
 * @param int $month 月
 * @param int $day 日
 * @return DateTime
 */
function getDateTime(int $year, int $month, int $day) : DateTime {
    // $date = new DateTime(); // これを…
    $date = new DateTime("now", new DateTimeZone('Asia/Tokyo')); // こう変えた
    $date->setDate($year, $month, $day);
    $date->setTime(0, 0, 0);
    return $date;
}

これで上手くいきました。

ひかる「タイムゾーンには注意しないとね」
ララ「ルン!」

※ちなみに、同じ環境で正常に動いているシステムがあることから、そちらへの影響を考えた結果、デフォルトタイムゾーンを変える、ということはしないこととしました。


  1. ひかるがララを呼ぶときに「ララ」と呼ぶのは、第3話の最後のほうからです。 

  2. https://precure.hokkaidosm.net/star_twincle/lala_age/toot.php にはアクセスできないように設定しています。 

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0