如何使用strtotime和date获取相对于今天的上一个月和年份?

70

我需要获取相对于当前日期的上一个月和年份。

但是,请看下面的例子。

// Today is 2011-03-30
echo date('Y-m-d', strtotime('last month'));

// Output:
2011-03-02
这种行为在一定程度上是可以理解的,因为二月和三月的天数不同。上面的示例代码是我所需的内容,但仅在每个月的1日到28日之间完全正确地工作。
那么,如何以最优雅的方式获取上个月和年份(考虑使用date("Y-m")),并且适用于每年365天?最佳解决方案将基于strtotime参数解析。
更新。稍微澄清一下要求。
我有一段代码可以获取最近几个月的一些统计数据,但我首先显示上个月的统计数据,然后在需要时加载其他月份的数据。这是预期的目的。因此,在本月期间,我想找出我应该拉取哪个月-年份以加载上一个月的统计数据。
我还有一个时区感知的代码(现在并不重要),它接受strtotime兼容的字符串作为输入(用于初始化内部日期),然后允许使用strtotime兼容的字符串调整日期/时间。
我知道可以使用几个条件和基本数学运算来完成,但与此相比,真的很混乱,例如:
echo tz::date('last month')->format('Y-d')

所以,我只需要上个月和年份,以符合strtotime的格式。

回答(感谢@dnagirl):

// Today is 2011-03-30
echo date('Y-m-d', strtotime('first day of last month')); // Output: 2011-02-01

你需要的只是月份和年份吗?没有日期? - Naftali
您能澄清一下您需要的输入/输出和期望的行为吗? - Jarrod Nettles
1
date("m-Y", strtotime("-1 months")); 可以解决此问题。 - Varshaan
1
@Varshaan,感谢您的评论!请不要通过评论提出解决方案,而是创建一个答案。这样可以围绕您的答案展开讨论,并收集投票。 - mr.b
1
@mr.b同意并已发布为答案。 - Varshaan
16个回答

62
看一下DateTime类。它应该能正确计算,并且日期格式与strttotime兼容。类似这样的:
$datestring='2011-03-30 first day of last month';
$dt=date_create($datestring);
echo $dt->format('Y-m'); //2011-02

另外,我的tz::date()函数在内部使用DateTime对象来进行时区或时间调整。 - mr.b
@mr.b:很高兴我能帮到你。你的tz::date()函数中是否使用了DateTimeZone类?http://ca2.php.net/manual/en/class.datetimezone.php - dnagirl
tz类是TzDate类的便捷包装器。请参见http://pastebin.com/GzUcvvA0,也许您会发现它很有用。代码应该是自说明的。tz类在这里http://pastebin.com/8mcRu5qe;但是,它不是独立的,因为我使用Kohana框架,并且它与它提供的一些其他类绑定在一起。 - mr.b

40

如果日期本身并不重要,则执行以下操作:

echo date('Y-m-d', strtotime(date('Y-m')." -1 month"));

3
今天,它仍然显示为三月。我本来期望它是二月。 - Codecraft
4
抱歉,您的代码出了一点错误:echo date('Y-m-d', strtotime(date('Y-m-1')." -1 month")); 这是我想表达的意思。 - ITroubs
@Codecraft,“-1个月” 和 “-1个月”,我都尝试过,它们产生了不同的结果,连字符前面的空格可以正常工作:D。 - ha_ryu
完美,正是我所需要的。 - carnini
Codecraft:十二年后,SeanDowney的回答 关于为什么它被误认为是一个 bug 值得一读。一般情况下,如果当天是月份的 28-31 号,请小心进行这些操作。 - undefined
显示剩余3条评论

33
我发现一个答案,因为今天是31日,我也遇到了同样的问题。这并不是php中的错误,一些人会这样认为,但这是期望的功能(从某种意义上来说)。根据这个帖子,strtotime实际上是将月份向后推迟一个月,而不修改天数。所以在今天(5月31日)的情况下,它寻找4月31日,这是一个无效的日期。因此,它接着取4月30日,再加上1天,得到5月1日。
在您的示例2011-03-30中,它会向前推一个月到2月30日,但这是无效的,因为2月只有28天。然后它取这些天数的差值(30-28 = 2),再向2月28日之后移动两天,即3月2日。
正如其他人指出的那样,获取“上个月”的最佳方法是使用strtotime或DateTime对象添加“第一天”或“最后一天”。
// Today being 2012-05-31
//All the following return 2012-04-30
echo date('Y-m-d', strtotime("last day of -1 month"));
echo date('Y-m-d', strtotime("last day of last month"));
echo date_create("last day of -1 month")->format('Y-m-d'); 

// All the following return 2012-04-01
echo date('Y-m-d', strtotime("first day of -1 month")); 
echo date('Y-m-d', strtotime("first day of last month"));
echo date_create("first day of -1 month")->format('Y-m-d');

因此,如果您正在创建查询等操作,则可以使用这些内容来创建日期范围。


18

如果您想获取特定日期的前一个年份和月份,并且已经有DateTime可用,那么可以执行以下操作:

$d = new \DateTimeImmutable('2013-01-01', new \DateTimeZone('UTC')); 
$firstDay = $d->modify('first day of previous month');
$year = $firstDay->format('Y'); //2012
$month = $firstDay->format('m'); //12

这对我来说是更好的解决方案。虽然我建议在修改之前克隆 $d,因为这将完全改变 $d 的值,因为它是一个 TimeDate 对象。 $d2 = clone $d 然后在 $d2 上调用 modify(),如 $d2->modify('first day of previous month'),以便 $d$d2 保持不同。 - Allan Dereal
在这种情况下,DateTimeImmutable更合适。 - Timo Huovinen

12
date('Y-m', strtotime('first day of last month'));

1
这很完美。通过使用第一天,它可以防止像2月31日、6月31日等这样的事情发生。 - Johnathan Elmore

8

strtotime函数有第二个时间戳参数,使得第一个参数相对于第二个参数。你可以这样做:

date('Y-m', strtotime('-1 month', time()))

非常好的简短代码,是否有适用于MySQL的相同选项?即:CURDATE()- $day // 30,但如果当前月份为31天,则不会返回真实月份。 - Milad Abooali

5

如果我理解问题正确,您只是想要上个月和所在的年份:

<?php

  $month = date('m');
  $year = date('Y');
  $last_month = $month-1%12;
  echo ($last_month==0?($year-1):$year)."-".($last_month==0?'12':$last_month);

?>

这是一个示例: http://codepad.org/c99nVKG8。它与IT技术有关,您可以查看链接了解更多信息。

5

嗯,正如有人提到的那样,这并不是一个bug。因为每个月的天数通常是不同的,所以这是预期的行为。使用strtotime获得上个月最简单的方法可能是从本月的第一天减去1个月。

$date_string = date('Y-m', strtotime('-1 month', strtotime(date('Y-m-01'))));

不考虑编程方面,如果我今天(2011年3月30日)在街上拦住你问上个月的这个时候是几号,而且你必须回答,你会诚实地回答“3月3日”吗?我不会! - Codecraft
@Codecraft:也许正确的答案是“从来没有这个时候”,因为确实没有“一个月前的这个时候”。但确实有一个月前的这个时候,这取决于“一个月前”的定义。我想整个问题都是由于这个定义引起的。 - mr.b
@dqhendricks:我没有提到具体的日期,对吧? :) 另外,选择要减去的天数很有趣 - strtotime 怎么会得出这样的结论,认为我想要减去二月的天数呢?为什么不是当前月份的天数呢?真奇怪。 - mr.b
@mr.b 或许因为我们要求上个月的日期,它算出了上个月有多少天,并从当前日期中减去了这些天哈哈。如果它减去本月的天数,就会出现其他类型的错误。从三月一日减去一个月将返回一个一月份的日期。 - dqhendricks

3
date("m-Y", strtotime("-1 months")); 

将解决此问题。


你的解决方案在很久以前对我起了作用,但现在又反咬了我一口。它有一个相同的问题,有些人称之为bug(其实不是)。当你在3月29日至31日期间调用它时,它不会给你返回2月份,因为忽略了闰年,2月29日至31日并不存在。在我的情况下,我使用了date("m-Y", strtotime("-6 months"))(当前日期为2023年8月31日),它返回的是"03-2023",而我们想要的是2023年2月。使用date("m-Y", strtotime("last day of -6 month"))帮助我修复了这个问题。在你的答案中添加这个说明或者澄清会很好。 - joeljpa

2
这是因为上个月的天数比当前月少。我通过首先检查前一个月是否比当前月少天数并根据此更改计算来解决了这个问题。
如果上个月的天数较少,则获取“-1个月”的最后一天,否则获取“-1个月”的当前日期:
if (date('d') > date('d', strtotime('last day of -1 month')))
{
    $first_end = date('Y-m-d', strtotime('last day of -1 month'));
}
else
{
    $first_end = date('Y-m-d', strtotime('-1 month'));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接