The \IPS\Helpers\Form\Date class does not account properly for a member's timezone when the min and/or max options are set.
This is the file's code on lines 303-314 in the validate() function:
if ( $this->value and $this->options['min'] !== NULL and $this->options['min'] > $this->value )
{
$string = $this->options['min']->setTimeZone( $timezone )->localeDate( \IPS\Member::loggedIn() );
if( $this->options['time'] )
{
$string .=' ' . $this->options['min']->setTimeZone( $timezone )->localeTime( \IPS\Member::loggedIn() );
}
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_date_min', FALSE, array( 'sprintf' => array( $string ) ) ) );
}
/* Check maximum */
if ( $this->value and $this->options['max'] !== NULL and $this->options['max'] < $this->value )
{
$string = $this->options['max']->setTimeZone( $timezone )->localeDate( \IPS\Member::loggedIn() );
if( $this->options['time'] )
{
$string .=' ' . $this->options['max']->setTimeZone( $timezone )->localeTime( \IPS\Member::loggedIn() );
}
throw new \LengthException( \IPS\Member::loggedIn()->language()->addToStack('form_date_max', FALSE, array( 'sprintf' => array( $string ) ) ) );
}
The code doesn't account at all for the timezone when checking the min/max values against the entered value:
$this->options['min'] > $this->value
$this->options['max'] < $this->value
The timezone is added only inside the IF check to display the error, but not before it for the check:
$string = $this->options['min']->setTimeZone( $timezone )->localeDate( \IPS\Member::loggedIn() );
$string = $this->options['max']->setTimeZone( $timezone )->localeDate( \IPS\Member::loggedIn() );
This causes the check to fail for a user close to the UTC timezone, while it passes for a user with a more distant timezone.
Here is an example with 2 different timezones (Rome & New York):
DEBUG CODE:
==================================================
print_r( $this->value );
print_r( $this->options['min'] );
print_r( $this->options['min']->setTimeZone( $timezone ) );
var_dump( $this->options['min'] > $this->value );
exit;
==================================================
OUTPUT FOR ROME TIMEZONE:
==================================================
IPS\DateTime Object
(
[date] => 2024-03-02 00:01:00.000000
[timezone_type] => 3
[timezone] => Europe/Rome
)
IPS\DateTime Object
(
[date] => 2024-03-02 00:58:41.440303
[timezone_type] => 3
[timezone] => UTC
)
IPS\DateTime Object
(
[date] => 2024-03-02 01:58:41.440303
[timezone_type] => 3
[timezone] => Europe/Rome
)
bool(true)
==================================================
OUTPUT FOR NEW YORK TIMEZONE:
==================================================
IPS\DateTime Object
(
[date] => 2024-03-02 02:41:00.000000
[timezone_type] => 3
[timezone] => America/New_York
)
IPS\DateTime Object
(
[date] => 2024-03-02 00:52:00.648474
[timezone_type] => 3
[timezone] => UTC
)
IPS\DateTime Object
(
[date] => 2024-03-01 19:52:00.648474
[timezone_type] => 3
[timezone] => America/New_York
)
bool(false)
As you can see from the debug output above, the member with a Rome timezone fails to pass the check (TRUE triggers the error), while the New York timezone passes the check (FALSE doesn't trigger the error).
The timezone must be added to the min/max checks before the check is done, and not after to display only the error.