Need a method to check if date is real, for instance:
Carbon::parse('30-02-2015')->exists(); // bool(false)
Carbon::parse('28-02-2015')->exists(); // bool(true)
But PHP internally moves the date to the next valid one, so might be tricky.
Only way I see this work is to keep the original input as reference and then compare input === current and return this result.
Exactly. Basically need to check if raw input is === to result. Maybe by reformat the result after the fromDateFormat, for instance. Or build own date parser (what can be bad).
Then again this is not a validation library. This feature might be out of scope.
Yeah, I don't through about that.
I think this would be easy to do yourself. ?
$d = Carbon::parse('30-02-2015');
$existed = $d->toDateString() === '2015-02-03';
checkdate(int $month, int $day, int $year) will do the trick
I do it in this way:
$dateAsString = '1985-12-25';
if (Carbon::createFromFormat('Y-m-d', $dateAsString)->format('Y-m-d') === $dateAsString) {
// Date is valid
}
Since Carbon::createFromFormat uses DateTime::createFromFormat and it returns next available date if it is not valid.
Indeed, the Carbon::parse('30-02-2015')->exists() would not be great to implement as it mean we must save the input given to parse so we can check it against ->toDateString() (same as ->format('Y-m-d')) if exists is called later. It would store a value we won't use most of the time.
Then it only works if you parse using the Y-m-d format, but you can: Carbon::parse('1 March 2018')
Right now, Carbon::parse('30-02-2015') will just be turned into 2015-03-02 and we don't know it was created from 30-02, so the date produced exists in fact, it just does not match the string passed to parse originally.
So the method given by @zaplachinsky is good and the most explicit to check a Y-m-d strictly formatted date is valid. https://github.com/briannesbitt/Carbon/issues/450#issuecomment-504679414
And method given by @andreich1980 is the best to check numbers validity (trailing zeros will not matter) https://github.com/briannesbitt/Carbon/issues/450#issuecomment-407877525
In both case, it's relevant only statically, not on an existing Carbon object.
You can create the following macros if you like:
Carbon::macro('checkDate', function ($year, $month = null, $day = null) {
if (isset($this)) {
throw new \RuntimeException('Carbon::checkDate() must be called statically.');
}
if ($day === null) {
[$year, $month, $day] = explode('-', $year);
}
return checkdate($month, $day, $year);
});
Carbon::checkDate('2010-1-6'); // true
Carbon::checkDate('2010-01-06'); // true
Carbon::checkDate('2010-2-30'); // false
Carbon::checkDate('2010-02-30'); // false
Carbon::checkDate(2010, 1, 6); // true
Carbon::checkDate('2010', '01', '06'); // true
Carbon::checkDate(2010, 2, 30); // false
Carbon::checkDate('2010', '02', '30'); // false
\Carbon\Carbon::macro('isDateValid', function ($date) {
$dateParts = explode('-', $date);
if (count($dateParts) !== 3) throw new \InvalidArgumentException("date is not in ISO 8601 format.");
[$year, $month, $day] = $dateParts;
if (mb_strlen($year) !== 4) throw new \InvalidArgumentException("year is not in ISO 8601 format.");
if (mb_strlen($month) !== 2) throw new \InvalidArgumentException("month is not in ISO 8601 format.");
if (mb_strlen($day) !== 2) throw new \InvalidArgumentException("day is not in ISO 8601 format.");
return checkdate((int) $month, (int) $day, (int) $year);
});
function isValidForCarbon( $date ) {
return new Carbon( $date ) instanceof Carbon;
}
@Santukon the problem with your solution is that Carbon parses correctly wrong dates, so this
\Carbon\Carbon::parse('2000-02-31 17:00:00');
Becomes
2000-03-02 17:00:00.0
Not sure if this is a bug or a feature, @briannesbitt. 🤷♂️
But PHP has a builtin date parser that handles all that:
My take:
Carbon::macro('checkDate', function (
$date,
$month = null,
$day = null
) {
if (!is_null($day)) {
$date = "{$date}-{$month}-{$day}";
}
$parsed = date_parse($date);
return $parsed['error_count'] == 0 &&
($parsed['warning_count'] == 0 ||
!in_array(
'The parsed date was invalid',
$parsed['warnings'],
));
});
var_dump(Carbon::checkDate('2019-02-29 00:00:00') ? 'true' : 'false');
var_dump(Carbon::checkDate('2020-02-29 00:00:00') ? 'true' : 'false');
var_dump(Carbon::checkDate('2019-02-29') ? 'true' : 'false');
var_dump(Carbon::checkDate('2020-02-29') ? 'true' : 'false');
var_dump(Carbon::checkDate(2019,02,29) ? 'true' : 'false');
var_dump(Carbon::checkDate(2020,02,29) ? 'true' : 'false');
Returns:
string(5) "false"
string(4) "true"
string(5) "false"
string(4) "true"
string(5) "false"
string(4) "true"
Not sure if this is a bug or a feature
This is the PHP native behavior when creating a DateTime, it overflows to a valid date. We don't override this behavior to be compatible.
Thanks for your macro, it adds nicely the support for time string.
Late to the party, but I think this would be nice to have in Carbon:
function isValidDate($string, $format = 'Y-m-d H:i:s')
{
$d = DateTime::createFromFormat($format, $string);
return $d && $d->format($format) == $string;
}
Something like:
Carbon::isValidDate('0000-00-00'); // false
try {
Carbon::createSafe(0, 0, 0, 0, 0, 0);
} catch (\Carbon\Exceptions\InvalidDateException $exp) {
echo $exp->getMessage(); // year : 0 is not a valid value.
}
@kylekatarnls is that supposed to be the current available alternative? It looks quite verbose and not really suitable to validate a string (in my case I needed to validate a JSON field)
A boolean returns give you no clue on the problem. If you're validating a user input, giving to user proper error message to help to fix the input is the right thing to do and exception is the appropriate tool to do that.
I would not do in much more opinionated tool as for the format checking, we have hasFormat() and considering trailing 0 is an issue or not for instance (2020-02-01 vs 2020-2-1), and what is part of the validation vs. normalization (auto-correction of the input) will be your business logic. It would be legit for such a validation function/macro to be in your application code:
Carbon::macro('isValidDate', function ($string, $format = 'Y-m-d H:i:s')
{
$d = DateTime::createFromFormat($format, $string);
return $d && $d->format($format) === $string;
});
var_dump(Carbon::isValidDate('2013-01-01', 'Y-m-d'));
var_dump(Carbon::isValidDate('2013-1-1', 'Y-m-d'));
For instance I don't think the second case in this exemple would be the expected behavior for everyone. Some would prefer for this part to just automatically add the missing 0 to get the normalized string rather than annoying user to add them manually.
That makes me feel checking string validity should not be the responsibility of the Carbon. We should only provide the tools to make your own validation.
Most helpful comment
Indeed, the
Carbon::parse('30-02-2015')->exists()would not be great to implement as it mean we must save the input given toparseso we can check it against->toDateString()(same as->format('Y-m-d')) ifexistsis called later. It would store a value we won't use most of the time.Then it only works if you parse using the Y-m-d format, but you can:
Carbon::parse('1 March 2018')Right now,
Carbon::parse('30-02-2015')will just be turned into2015-03-02and we don't know it was created from 30-02, so the date produced exists in fact, it just does not match the string passed to parse originally.So the method given by @zaplachinsky is good and the most explicit to check a Y-m-d strictly formatted date is valid. https://github.com/briannesbitt/Carbon/issues/450#issuecomment-504679414
And method given by @andreich1980 is the best to check numbers validity (trailing zeros will not matter) https://github.com/briannesbitt/Carbon/issues/450#issuecomment-407877525
In both case, it's relevant only statically, not on an existing Carbon object.
You can create the following macros if you like: