From d02aee64e458ba2da3a1e9ae2525f38f15b541fa Mon Sep 17 00:00:00 2001
From: Isaac Connor <isaac@zoneminder.com>
Date: Wed, 2 Oct 2019 09:07:18 -0400
Subject: [PATCH] Add setting of timezone to Options/Config instead of php.ini

---
 .../lib/ZoneMinder/ConfigData.pm.in           | 15 +++++++
 web/includes/functions.php                    |  2 +-
 web/index.php                                 |  1 +
 web/skins/classic/views/options.php           | 44 ++++++++++++++++---
 4 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in
index 2b2a20958..7e1b2d2ac 100644
--- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in
+++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in
@@ -162,6 +162,12 @@ our %types = (
     pattern     => qr|^([a-zA-Z0-9_.-]+)\@([a-zA-Z0-9_.-]+)$|,
     format      => q( $1\@$2 )
   },
+  timezone => {
+    db_type     => 'string',
+    hint        => 'America/Toronto',
+    pattern      => qr|^(.+)$|,
+    format      => q($1)
+  }
 );
 
 our @options = (
@@ -777,6 +783,15 @@ our @options = (
     type        => $types{string},
     category    => 'config',
   },
+  {
+    name        => 'ZM_TIMEZONE',
+    default     =>  'UTC',
+    description => 'The timezone that php should use.',
+    help        => q`
+      This should be set equal to the system timezone of the mysql server`,
+    type  =>  $types{timezone},
+    category => 'system',
+  },
   {
     name        => 'ZM_CPU_EXTENSIONS',
     default     => 'yes',
diff --git a/web/includes/functions.php b/web/includes/functions.php
index 7d8ace272..236b74f82 100644
--- a/web/includes/functions.php
+++ b/web/includes/functions.php
@@ -2362,7 +2362,7 @@ function check_timezone() {
                #");
 
   if ( $sys_tzoffset != $php_tzoffset )
-    ZM\Error("ZoneMinder is not installed properly: php's date.timezone does not match the system timezone!");
+    ZM\Error("ZoneMinder is not installed properly: php's date.timezone $php_tzoffset does not match the system timezone $sys_tzoffset!");
 
   if ( $sys_tzoffset != $mysql_tzoffset )
     ZM\Error("ZoneMinder is not installed properly: mysql's timezone does not match the system timezone! Event lists will display incorrect times.");
diff --git a/web/index.php b/web/index.php
index 5c2448291..83be992e8 100644
--- a/web/index.php
+++ b/web/index.php
@@ -202,6 +202,7 @@ isset($action) || $action = NULL;
 
 if ( (!$view and !$request) or ($view == 'console') ) {
   // Verify the system, php, and mysql timezones all match
+  date_default_timezone_set(ZM_TIMEZONE);
   check_timezone();
 }
 
diff --git a/web/skins/classic/views/options.php b/web/skins/classic/views/options.php
index b3f925f8a..aa2ed1e3f 100644
--- a/web/skins/classic/views/options.php
+++ b/web/skins/classic/views/options.php
@@ -389,6 +389,40 @@ foreach ( array_map('basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR)) as $
         $configCats[$tab]['ZM_SKIN_DEFAULT']['Hint'] = join('|', array_map('basename', glob('skins/*',GLOB_ONLYDIR)));
         $configCats[$tab]['ZM_CSS_DEFAULT']['Hint'] = join('|', array_map ( 'basename', glob('skins/'.ZM_SKIN_DEFAULT.'/css/*',GLOB_ONLYDIR) ));
         $configCats[$tab]['ZM_BANDWIDTH_DEFAULT']['Hint'] = $bandwidth_options;
+        function timezone_list() {
+    static $timezones = null;
+
+    if ($timezones === null) {
+        $timezones = [];
+        $offsets = [];
+        $now = new DateTime('now', new DateTimeZone('UTC'));
+
+        foreach (DateTimeZone::listIdentifiers() as $timezone) {
+            $now->setTimezone(new DateTimeZone($timezone));
+            $offsets[] = $offset = $now->getOffset();
+            $timezones[$timezone] = '(' . format_GMT_offset($offset) . ') ' . format_timezone_name($timezone);
+        }
+
+        array_multisort($offsets, $timezones);
+    }
+
+    return $timezones;
+}
+
+function format_GMT_offset($offset) {
+    $hours = intval($offset / 3600);
+    $minutes = abs(intval($offset % 3600 / 60));
+    return 'GMT' . ($offset ? sprintf('%+03d:%02d', $hours, $minutes) : '');
+}
+
+function format_timezone_name($name) {
+    $name = str_replace('/', ', ', $name);
+    $name = str_replace('_', ' ', $name);
+    $name = str_replace('St ', 'St. ', $name);
+    return $name;
+}
+        $configCats[$tab]['ZM_TIMEZONE']['Hint'] = timezone_list();
+
     }
 ?>
       <form name="optionsForm" class="form-horizontal" method="post" action="?">
@@ -396,10 +430,10 @@ foreach ( array_map('basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR)) as $
         <input type="hidden" name="tab" value="<?php echo $tab ?>"/>
         <input type="hidden" name="action" value="options"/>
 <?php
-    $configCat = $configCats[$tab];
-    foreach ( $configCat as $name=>$value ) {
-        $shortName = preg_replace( '/^ZM_/', '', $name );
-        $optionPromptText = !empty($OLANG[$shortName])?$OLANG[$shortName]['Prompt']:$value['Prompt'];
+        $configCat = $configCats[$tab];
+        foreach ( $configCat as $name=>$value ) {
+          $shortName = preg_replace( '/^ZM_/', '', $name );
+          $optionPromptText = !empty($OLANG[$shortName])?$OLANG[$shortName]['Prompt']:$value['Prompt'];
 ?>
             <div class="form-group">
               <label for="<?php echo $name ?>" class="col-sm-3 control-label"><?php echo $shortName ?></label>
@@ -409,7 +443,7 @@ foreach ( array_map('basename', glob('skins/'.$skin.'/css/*',GLOB_ONLYDIR)) as $
 ?>
               <input type="checkbox" id="<?php echo $name ?>" name="newConfig[<?php echo $name ?>]" value="1"<?php if ( $value['Value'] ) { ?> checked="checked"<?php } ?><?php echo $canEdit?'':' disabled="disabled"' ?>/>
 <?php
-        } elseif ( is_array( $value['Hint'] ) ) {
+        } elseif ( is_array($value['Hint']) ) {
           echo htmlSelect("newConfig[$name]", $value['Hint'], $value['Value']);
         } elseif ( preg_match('/\|/', $value['Hint']) ) {
 ?>