php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #76848 Possible to create Datetime from wrong month day
Submitted: 2018-09-07 11:17 UTC Modified: 2022-10-19 15:18 UTC
Votes:9
Avg. Score:4.1 ± 1.4
Reproduced:8 of 9 (88.9%)
Same Version:6 (75.0%)
Same OS:7 (87.5%)
From: wojciech dot mocek at gmail dot com Assigned: derick (profile)
Status: Closed Package: Date/time related
PHP Version: 7.3.11 OS: Linux
Private report: No CVE-ID: None
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: wojciech dot mocek at gmail dot com
New email:
PHP Version: OS:

 

 [2018-09-07 11:17 UTC] wojciech dot mocek at gmail dot com
Description:
------------
Problem:
Possible to create Datetime from wrong month day. Example:
1. new \Datetime('2018-09-31') => gives 2018-10-01 
2. new \Datetime('2018-02-31') => gives 2018-03-03
and it applies to all months having less than 31 days.

There is no such date like 31 of September or 31 of February, or 31 of November, and so on, while PHP library parses such invalid date string and creates Datetime object.


Test script:
---------------
$ php <<< "<?php var_dump(new \Datetime('2018-09-31'), new \Datetime('2018-02-31'));"
-:1:
class DateTime#1 (3) {
  public $date => string(26) "2018-10-01 00:00:00.000000"
  public $timezone_type =>   int(3)
  public $timezone =>  string(13) "Europe/Berlin"
}
-:1:
class DateTime#2 (3) {
  public $date =>  string(26) "2018-03-03 00:00:00.000000"
  public $timezone_type =>   int(3)
  public $timezone => string(13) "Europe/Berlin"
}


Expected result:
----------------
Expected behaviour:
It should not be possible to create \Datetime object using invalid day of month.

$ php <<< "<?php var_dump(new \Datetime('2018-09-31'));"
PHP Fatal error:  Uncaught Exception: DateTime::__construct(): Failed to parse time string (2018-09-31) at position 9 (2): Unexpected character in -:1
Stack trace:
#0 -(1): DateTime->__construct('2018-09-31')
#1 {main}
  thrown in - on line 1


Actual result:
--------------
$ php <<< "<?php var_dump(new \Datetime('2018-09-31'), new \Datetime('2018-02-31'));"
-:1:
class DateTime#1 (3) {
  public $date => string(26) "2018-10-01 00:00:00.000000"
  public $timezone_type =>   int(3)
  public $timezone =>  string(13) "Europe/Berlin"
}
-:1:
class DateTime#2 (3) {
  public $date =>  string(26) "2018-03-03 00:00:00.000000"
  public $timezone_type =>   int(3)
  public $timezone => string(13) "Europe/Berlin"
}

Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2018-09-08 03:10 UTC] requinix@php.net
-Status: Open +Status: Not a bug
 [2018-09-08 03:10 UTC] requinix@php.net
That's right, it does not validate the date. It tries its best to come up with the most reasonable interpretation of the string passed to it. Just like how strtotime does.
 [2018-09-11 11:44 UTC] wojciech dot mocek at gmai dot com
You wrote:
1. "That's right, it does not validate the date."

Well, hmm, I just run script:
$ php <<< "<?php var_dump(new \Datetime('2018-09-32'), new \Datetime('2018-02-32'));"
PHP Fatal error:  Uncaught Exception: DateTime::__construct(): Failed to parse time string (2018-09-32) at position 9 (2): Unexpected character in Standard input code:1
Stack trace:
#0 Standard input code(1): DateTime->__construct('2018-09-32')
#1 {main}
  thrown in Standard input code on line 1

So it looks that is validates given input somehow.

2. "It tries its best to come up with the most reasonable interpretation of the string passed to it. Just like how strtotime does.". Come on, please ask people around to do something on "2018-02-31", I think noone will think to do it on "2018-03-03" My question #2 is: do you say that if tell me "Send this mail on 2018-02-31" it is MOST REASONABLE to interpret this as "send it on 2018-03-03" ?! 

3. "It tries its best to come up with the most reasonable interpretation of the string passed to it. Just like how strtotime does." . You mentioned about strtotime:
$ php <<< "<?php var_dump(strtotime('2018-02-31'));"
int(1520035200)
$ php <<< "<?php var_dump(strtotime('2018-02-32'));"
bool(false)
$ php <<< "<?php var_dump(new \Datetime('2018-02-32'));"
PHP Fatal error:  Uncaught Exception: DateTime::__construct(): Failed to parse time string (2018-02-32) at position 9 (2): Unexpected character in Standard input code:1
Stack trace:
#0 Standard input code(1): DateTime->__construct('2018-02-32')
#1 {main}
  thrown in Standard input code on line 1

 - 31 of February - there is no such date ever in calendar, but PHP creates time.
 - 32 of February - also there is no such date ever in calendar, PHP forbids to create time
"It tries its best to come up with the most reasonable interpretation of the string passed to it" - if it is possible to create time from "never existing 31 of February", so why it is not possible to create time from also "never existing 32 of February" ?

3. In normal life we cannot do anything on 31 of February, nor 31 of September. If you write business application for invoicing, banks, anything, you must validate the date. And if someone writes 31 of February to for example pay tax or send cash, it is mistake and NOT REASONABLE to process. So "day of month" validation should be implemented. Otherwise all business applications will have to use some custom PHP date library, only because native PHP allows to create such nonsense like date from '2018-02-31'.
 [2018-11-21 11:06 UTC] wojciech dot mocek at gmail dot com
-Status: Not a bug +Status: Open
 [2018-11-21 11:06 UTC] wojciech dot mocek at gmail dot com
Open.
 [2018-11-21 12:43 UTC] cmb@php.net
-Status: Open +Status: Feedback -Assigned To: +Assigned To: cmb
 [2018-11-21 12:43 UTC] cmb@php.net
And how should the following be handled?

  new DateTime('2018-01-31 +1 month')
 [2018-11-21 15:05 UTC] wojciech dot mocek at gmail dot com
-Status: Feedback +Status: Assigned
 [2018-11-21 15:05 UTC] wojciech dot mocek at gmail dot com
What is the correlation between your question and reported bug?
 [2018-11-21 15:10 UTC] cmb@php.net
-Status: Assigned +Status: Not a bug -Assigned To: cmb +Assigned To:
 [2018-11-21 15:10 UTC] cmb@php.net
Obviously,

  new DateTime('2018-01-31 +1 month')

and

  new DateTime('2018-02-31')

should both evaluate to the same value, shouldn't they?
 [2019-10-25 23:40 UTC] wojciech dot mocek at gmail dot com
You asked "should both evaluate to the same value, shouldn't they?". So the answer is: no, they should not. 
This is how it was solved in other object language:

$ docker container run --rm -it openjdk jshell
Oct 25, 2019 11:21:53 PM java.util.prefs.FileSystemPreferences$1 run
INFO: Created user preferences directory.
|  Welcome to JShell -- Version 13.0.1
|  For an introduction type: /help intro

jshell> var dateA = java.time.LocalDate.parse("2018-01-31");
jshell> dateA.plusMonths(1)
$2 ==> 2018-02-28

jshell> var dateB = java.time.LocalDate.parse("2018-02-31");
|  Exception java.time.format.DateTimeParseException: Text '2018-02-31' could not be parsed: Invalid date 'FEBRUARY 31'
(...)


Do you agree that PHP DateTime class is class for handling date and time? If yes, then do you agree that it should behave according to real date&time? If yes, then do we have in our calendar date '2018-02-31'?
 [2019-10-25 23:49 UTC] wojciech dot mocek at gmail dot com
-Status: Not a bug +Status: Open -PHP Version: 7.1.21 +PHP Version: 7.3.11
 [2019-10-25 23:49 UTC] wojciech dot mocek at gmail dot com
This bug occurs also on PHP 7.3.11.
 [2020-01-20 17:39 UTC] girgias@php.net
-Assigned To: +Assigned To: derick
 [2020-01-20 17:39 UTC] girgias@php.net
Delegating this to Derick as he is the maintainer of the date extension.
 [2020-03-18 17:29 UTC] fzty at pm dot me
I encountered this issue on PHP 7.4.1 NTS x64. 
I tested a few more edge cases where an exception was expected but instead a DateTime object was created:

# The zeroeth day of the month should not be accepted.
var_dump(new DateTime('2020-01-00')); // "2019-12-31 00:00:00.000000"

# The zeroeth month of the year should not be accepted.
var_dump(new DateTime('2020-00-00')); // "2019-11-30 00:00:00.000000"

# I'm not sure whether year 10000 should be accepted. 
var_dump(new DateTime('10000-01-01')); // "2000-01-01 10:00:00.000000"

Like wojciech said, this is not representative of real life scenarios. 
The end result is that we have to write extra boilerplate validation to handle such edge cases.
 [2020-12-07 13:21 UTC] cmb@php.net
-Status: Assigned +Status: Suspended
 [2020-12-07 13:21 UTC] cmb@php.net
Okay, apparently Java implements truncation, but PHP implements
wrap-around (like date(1) does).  It is debatable which solution
is preferable, but since PHP implements wrap-around since
"forever", and the behavior is documented[1], changing that would
require the RFC process[2].  Feel free to start it.  For the time
being, I'm suspending this ticket.

[1] <https://www.php.net/manual/en/datetime.examples-arithmetic.php#example-2506>
[2] <https://wiki.php.net/rfc/howto>
 [2022-10-19 15:18 UTC] derick@php.net
-Status: Suspended +Status: Closed
 [2022-10-19 15:18 UTC] derick@php.net
I would be opposed to changing how date/time support works in PHP, so I am just going to close this ticket.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 15:01:29 2024 UTC