一道面试题引发的思考

最近出了一个面试题,

给一个时间,获取这个月的结束的日期。比如 输入'2018-08-04' 输出'2018-08-31'

这个题目,相应如果熟悉php date 的应该很快能写出来。但是遇到一些求职者,可能没思考清楚,于是就开始编写了。
一个月的结束时间,只要知道这个月有多少天,就能知道最后一天是多少号了?但是这里你要思考的问题就有很多,因为月份有大小月份(1 3 5 7 8 10 12 则是大月有31天,其余的月份除了2月,都是小月,每个月有30天),你可能会忽略掉2月这个特殊的月份,因为2月又有29天和28天。假设你已经考虑到2月了,但是你又得需要考虑闰年了,闰年的规则你需要知道吧!
不考虑超大年的情况下的规则(按照历法 比如 3200 就不是闰年。有兴趣的可以查下资料)

普通年:能被4整除但不能被100整除的年份为普通闰年。(如2004年就是闰年,1999年不是闰年);
世纪年:能被400整除的为世纪闰年。(如2000年是闰年,1900年不是闰年);

于是有的求职者上来就写了一个这种代码

方法一

function monthDay($date) {
     $month31 = [1, 3, 5, 7, 8, 10, 12];
    list($year, $month) = explode('-',$date);
    if ($month != 2) {
        if (in_array($month, $month31)) {
            return "{$year}-{$month}-31";
        } else {
            return "{$year}-{$month}-30";
        }
    }
      if (  $year%4==0  && ($year%100!=0 ||  $year%400==0 ) ){
        return "{$year}-{$month}-29";
    }else{
        return "{$year}-{$month}-28";
    }
}

方法二

方法一的代码看着没啥问题,但是可能是一种特别复杂的实现方式,它考虑的因素比较多。
我们可以换种思考的方式,这个月有多少天,我们知道一个月的开始时间和一个月的结束时间 两者相减,也是等于当月的天数。一月末正好是一个月的开始。而且每个月的开始时间是固定的,都是1号,不会变的。下一个月的月数等于当月+1。
但是有个特殊的情况,如果是年底,那么12月的下一月就是新的一年的1月。

function endDayOfMonth($date) {
    list($year, $month) = explode('-',$date);
    $nextYear = $year;
    $nexMonth = $month+1;
    //如果是年底12月 下个月就是1月
    if($month == 12) {
        $nexMonth = "01";
        $nextYear = $year+1;
    }
    $begin = "{$year}-{$month}-01 00:00:00";
    $end = "{$nextYear}-{$nexMonth}-01 00:00:00";
    $day = (strtotime($end) - strtotime($begin) )/ (24*60*60);
    return "{$year}-{$month}-{$day}";
}

方法三

方法二的方法其实已经差不多接近了,但是还是可能不够特别好的。因为我们不需要算天数。我们知道新的一个月的第一天,减去一个1,就是当月的最后一秒。

function endDayOfMonth($date) {
    list($year, $month) = explode('-',$date);
    $nextYear = $year;
    $nexMonth = $month+1;
    //如果是年底12月 下个月就是1月
    if($month == 12) {
        $nexMonth = "01";
        $nextYear = $year+1;
    }
    $end = "{$nextYear}-{$nexMonth}-01 00:00:00";
    $endTimeStamp = strtotime($end) - 1 ;
    return date('Y-m-d',$endTimeStamp);
}

PHP自带函数实现

其实php自带的有多种实现的方式,比如date、DateTime、strtotime等

  • php date 函数格式化
    t 指定月份的天数; 如: "28" 至 "31"
    $date = '2018-08-08';
    echo date('Y-m-t',strtotime($date));
  • strtotime 字符串时间修饰词
    last day of this month 时间字符 类似我们常说的 -1 day
    echo date('Y-m-d',strtotime("last day of this month",strtotime('2018-02-01')));
    echo date('Y-m-d',strtotime("last day of 2018-02"));
  • php DateTime类 面向对象方式

    $date = new \DateTime('2000-02-01');
    $date->modify('last day of this month');
    echo $date->format('Y-m-d');

其实这题主要是我们常见的面试题演变的。主要是想看怎么考虑问题,很多的时候,我们陷入了一个误区里,考虑了复杂的实现,其实就是两个函数的使用,一个是 datestrtotime

求昨天的日期,strtotime('-1 day')

能使用date("Y-m-t") 的这种是最简单的,题目中没有提到不用内置函数,所以也考虑思考的方式,是否读了题目,就跟我们以前考试一样。做选择题,从下面选项选出不是的答案,我们看了第一个正确就选了。
想法很重要!

php
本作品采用《CC 协议》,转载必须注明作者和本文链接
闲云野鹤
本帖由系统于 5年前 自动加精
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 9

野鸽666

5年前 评论
ShiKi

666

5年前 评论

不知道这种获取上月同期会不会有坑。一直这样子用来着。

 $lastMonth = date('t', strtotime('-1 month', strtotime(date('Y-m', strtotime($startTime)) . '00:00:00')));
        if (date("d", strtotime($startTime)) > $lastMonth) {
            $last = $lastMonth;
        } else {
            $last = date('d', strtotime($startTime));
        }
 return date('Y-m-d', strtotime('-1 month', strtotime(date('Y-m', strtotime($startTime)) . '-' . $last . ' 00:00:00')));
5年前 评论

@xianyunyehe 好的。谢谢,我去学习学习。

5年前 评论

还有carbon也很方便

5年前 评论

@Ali 你这个太复杂
这样就行了
Carbon::parse('xxxx')->subMonthNoOverFlow()

5年前 评论

是的, date("Y-m-t")最简单

5年前 评论

英语不好拼不出last day of this month怎么办?

4年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!