|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
[2009-07-06 22:24 UTC] brad at omnis dot com
Description:
------------
dateTime->add(dateInterval) isn't applying proper calendar math when adding intervals.
Reproduce code:
---------------
$dateTest = new dateTime('2008-01-31',new dateTimeZone("GMT"));
print_r($dateTest);
$dateTest->add(new dateInterval('P1M'));
print_r($dateTest);
Expected result:
----------------
DateTime Object
(
[date] => 2008-01-31 00:00:00
[timezone_type] => 3
[timezone] => UTC
)
DateTime Object
(
[date] => 2008-02-28 00:00:00
[timezone_type] => 3
[timezone] => UTC
)
Actual result:
--------------
DateTime Object
(
[date] => 2008-01-31 00:00:00
[timezone_type] => 3
[timezone] => UTC
)
DateTime Object
(
[date] => 2008-03-02 00:00:00
[timezone_type] => 3
[timezone] => UTC
)
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits
|
|||||||||||||||||||||||||||
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Fri Nov 21 04:00:02 2025 UTC |
DateInterval is used for iteration. For stuff like this: $db = new DateTime('2008-12-31'); $de = new DateTime('2009-12-31'); $di = DateInterval::createFromDateString('third tuesday of next month'); $dp = new DatePeriod($db, $di, $de, DatePeriod::EXCLUDE_START_DATE); foreach($dp as $dt) { echo $dt->format("F jS\n") . "<br>\n"; } I also disagree with your suggestion that "calendar math" is "precision math calculations". The strtotime approach is actually the precise approach since you never have inconsistencies like this: mysql> SELECT DATE_ADD('2009-01-31 23:59:59',INTERVAL 1 MONTH); +--------------------------------------------------+ | DATE_ADD('2009-01-31 23:59:59',INTERVAL 1 MONTH) | +--------------------------------------------------+ | 2009-02-28 23:59:59 | +--------------------------------------------------+ mysql> SELECT DATE_SUB('2009-02-28 23:59:59',INTERVAL 1 MONTH); +--------------------------------------------------+ | DATE_SUB('2009-02-28 23:59:59',INTERVAL 1 MONTH) | +--------------------------------------------------+ | 2009-01-28 23:59:59 | +--------------------------------------------------+ So, you essentially have $a + month = $b $b - month != $a Or have 2 times 24-hours apart map to the same exact timestamp when you add a month: mysql> SELECT DATE_ADD('2009-01-31 23:59:59',INTERVAL 1 MONTH); +--------------------------------------------------+ | DATE_ADD('2009-01-31 23:59:59',INTERVAL 1 MONTH) | +--------------------------------------------------+ | 2009-02-28 23:59:59 | +--------------------------------------------------+ mysql> SELECT DATE_ADD('2009-01-30 23:59:59',INTERVAL 1 MONTH); +--------------------------------------------------+ | DATE_ADD('2009-01-30 23:59:59',INTERVAL 1 MONTH) | +--------------------------------------------------+ | 2009-02-28 23:59:59 | +--------------------------------------------------+ I understand why it works that way, but it certainly isn't ideal, nor is it precise if you aren't expecting that. Neither way is perfect. We chose to have all relative date operations follow the same rules. Having Interval and Modify use different relative date rules would be very confusing.Let me start off by saying "Calendar Math != Numeric Math". Lets get that clear. Calendars are messy (i.e. 60m = 1h, 24h = 1d, leap seconds, leap years, etc) and things don't always back out as you might wish they would. Your examples are EXACTLY what I would expect them to do when asking a computer to do that type of math you've given to it to do. You also have to ask the question, what would a human operator expect from adding +1 month to January 31st. Everyone I have spoken with agrees that it should be February and not March. "DateInterval is used for iteration." In your opinion. Last I checked PHP is used by more persons than just yourself. If this language were only used by PHP Devs then we would not be having this discussion. For your example of the usage of DateInterval I think it's great, but that does not mean that my usage is somehow invalid. My first inclination when I saw the new functions, was "Great! I hope these new functions are calendar based date math in PHP instead of numeric math based." My "precision math" comment should have said "accurate math", bad choice of words on my part, but the point is still valid. I would challenge you to find a single person, who doesn't have a predisposition to UNIX date semantics, who would agree that adding 1 month to January 31st should result in a date in March. Further, have you or any other PHP dev written PHP code that used strtotime("+1 month") where it would be acceptable to have January 31st + 1 month = March? Because after lots of thought, I can not come up with a single example of where I would find that acceptable. I don't see what the opposition to having another way of doing the date calculation is. As it is right now, I guarantee you that standard php date functions are inadequate for use in any sort of billing/accounting systems without additional modification to handle their shortcomings. I am obviously not the only person to request this functionality since there are numerous bug reports on the issue. What is the opposition to giving those users the choice of how to deal with date math? Here are examples of how other scripting languages that compete with PHP do it (hint they ALL have ways to achieve the results I expect): :::::::::::::: java: datetest.java :::::::::::::: import java.util.Date; import java.util.Calendar; import java.text.SimpleDateFormat; import java.util.*; class datetest { public static void main(String args[]) { SimpleDateFormat formatter = new SimpleDateFormat("E yyyy.MM.dd 'at' hh:mm:ss a zzz"); Calendar rightNow = Calendar.getInstance(); rightNow.set(2009,0,31); System.out.println( formatter.format(rightNow.getTime())); rightNow.add(Calendar.MONTH,+ 1); System.out.println(formatter.format(rightNow.getTime())); } } :::::::::::::: perl: datetest.pl :::::::::::::: use DateTime; use DateTime::Duration; my $date = DateTime->new( year => 2009, month => 1, day => 31, locale => 'en_US', ); my $duration = DateTime::Duration->new( months => 1, end_of_month => 'preserve', ); $date->add( $duration ); # One month more print $date->mdy('/'); # And show it :::::::::::::: python: datetest.py :::::::::::::: from datetime import *; from dateutil.relativedelta import * import calendar print date(2009,1,31)+relativedelta(months=+1) :::::::::::::: ruby: datetest.rb :::::::::::::: require 'date' d1 = Date.new(y=2009,m=1,d=31) puts d1 d2 = (d1 >> 1) puts d2Also c#: ----------------------- using System; class Program { static void Main() { int year = 2009; int month = 1; int day = 31; DateTime MyDate = new DateTime(year, month, day); Console.WriteLine(MyDate.Month + "/" + MyDate.Day + "/" + MyDate.Year); MyDate = MyDate.AddMonths(1); Console.WriteLine(MyDate.Month + "/" + MyDate.Day + "/" + MyDate.Year); } } ----------------------- So, any change in opinion yet? I've demonstrated that of all the major languages with which PHP compares (apologies to any languages I've forgotten) that PHP is the ONLY one that works like this and has no option to do it the other way.