teraßyte Posted March 2 Posted March 2 (edited) 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. Edited March 2 by teraßyte Afrodude, Adlago, DawPi and 1 other 4
Jim M Posted March 2 Posted March 2 Thank you for bringing this issue to our attention! I can confirm this should be further reviewed and I have logged an internal bug report for our development team to investigate and address as necessary, in a future maintenance release.
teraßyte Posted June 25 Author Posted June 25 I noticed one more issue with the code in the first post when the time option is enabled for the min/max checks: if( $this->options['time'] ) { $string .=' ' . $this->options['min']->setTimeZone( $timezone )->localeTime( \IPS\Member::loggedIn() ); } ======================= if( $this->options['time'] ) { $string .=' ' . $this->options['max']->setTimeZone( $timezone )->localeTime( \IPS\Member::loggedIn() ); } The code passes the logged-in member's object as the first parameter to determine the language, but the function accepts that value as the third parameter instead: /** * Format the time according to the user's locale (without the date) * * @param bool $seconds If TRUE, will include seconds * @param bool $minutes If TRUE, will include minutes * @param \IPS\Lang|\IPS\Member|NULL $memberOrLanguage The language or member to use, or NULL for currently logged in member * @return string */ public function localeTime( $seconds=TRUE, $minutes=TRUE, $memberOrLanguage=NULL ) Both localTime() calls should be updated to: ->localeTime( TRUE, TRUE, \IPS\Member::loggedIn() ); It would be nice if there were also a new option to decide if we want to show the seconds/minutes in the error, but that's a request rather than a bug. 😋 SeNioR- and media 2
teraßyte Posted June 25 Author Posted June 25 (edited) To add one more issue: If you set a minimum time (for example: 25 June, 3:00 AM) and select a time lower than 3 AM (for example: 25 June, 2 AM) the field's error says: The date must be after 06/25/24 03:00:00 AM. However, if you enter that exact minimum date, the value is processed without error. The error should be updated to clarify that the minimum value is accepted, too: The date must start from 06/25/24 03:00:00 AM. === The date must be at least 06/25/24 03:00:00 AM. Either option or something else would work. Edited June 25 by teraßyte SeNioR- and media 2
Recommended Posts