@@ -110,9 +110,9 @@ public static function fromPdo($pdo)
110110 {
111111 $ database = new self (new SecretObject ());
112112 $ database ->databaseConnection = $ pdo ;
113- $ database ->databaseType = $ database ->getDbType ($ pdo ->getAttribute (PDO ::ATTR_DRIVER_NAME ));
113+ $ database ->databaseType = self ::getDbType ($ pdo ->getAttribute (PDO ::ATTR_DRIVER_NAME ));
114+ $ database ->databaseCredentials = self ::getDatabaseCredentialsFromPdo ($ pdo );
114115 $ database ->connected = true ;
115- $ database ->databaseCredentials = $ database ->getDatabaseCredentialsFromPdo ($ pdo );
116116 return $ database ;
117117 }
118118
@@ -129,7 +129,7 @@ public static function fromPdo($pdo)
129129 *
130130 * @throws PDOException If there is an error with the PDO query or connection.
131131 */
132- private function getDatabaseCredentialsFromPdo ($ pdo )
132+ private static function getDatabaseCredentialsFromPdo ($ pdo )
133133 {
134134 // Get the driver name (e.g., mysql, pgsql, sqlite)
135135 $ driver = $ pdo ->getAttribute (PDO ::ATTR_DRIVER_NAME );
@@ -152,38 +152,19 @@ private function getDatabaseCredentialsFromPdo($pdo)
152152 $ timezone = null ;
153153
154154 // Determine the database type
155- $ dbType = $ this -> getDbType ($ driver );
155+ $ dbType = self :: getDbType ($ driver );
156156
157157 // Retrieve the schema and time zone based on the database type
158158 if ($ dbType == PicoDatabaseType::DATABASE_TYPE_PGSQL ) {
159159 // For PostgreSQL, fetch the current schema and time zone using queries
160160 $ stmt = $ pdo ->query ('SELECT current_schema() ' );
161161 $ schema = $ stmt ->fetchColumn (); // Fetch the schema name
162-
163- $ stmtTimezone = $ pdo ->query ('SHOW timezone ' );
164- $ systemTimeZone = $ stmtTimezone ->fetchColumn (); // Fetch the time zone
165-
166- $ timezone = $ this ->pgsqlToPhpTimezone ($ systemTimeZone );
162+ $ timezone = self ::convertOffsetToTimeZone (self ::getTimeZoneOffset ($ pdo ));
167163 }
168164 elseif ($ dbType == PicoDatabaseType::DATABASE_TYPE_MYSQL || $ dbType == PicoDatabaseType::DATABASE_TYPE_MARIADB ) {
169165 // For MySQL, the schema is the same as the database name
170166 $ schema = $ databaseName ; // MySQL schema is the database name
171-
172- // Retrieve the global time zone from MySQL
173- $ stmtTimezone = $ pdo ->query ('SELECT @@global.time_zone ' );
174- $ timezone = $ stmtTimezone ->fetchColumn (); // Fetch the global time zone
175-
176- // If the time zone is set to 'SYSTEM', retrieve the system's time zone and convert it
177- if ($ timezone == 'SYSTEM ' ) {
178- $ stmtSystemTimeZone = $ pdo ->query ('SELECT @@system_time_zone ' );
179- $ systemTimeZone = $ stmtSystemTimeZone ->fetchColumn ();
180-
181- // Convert MySQL system time zone to PHP-compatible time zone (e.g., 'Asia/Jakarta')
182- // This conversion may require a lookup table, as MySQL system time zones
183- // (e.g., 'CST', 'PST') are not directly equivalent to PHP time zones (e.g., 'Asia/Jakarta').
184- // Here, we will simply return the system time zone as a placeholder:
185- $ timezone = $ this ->mysqlToPhpTimezone ($ systemTimeZone );
186- }
167+ $ timezone = self ::convertOffsetToTimeZone (self ::getTimeZoneOffset ($ pdo ));
187168 }
188169 else {
189170 // For other drivers, set schema and time zone to null (or handle it as needed)
@@ -205,163 +186,98 @@ private function getDatabaseCredentialsFromPdo($pdo)
205186 }
206187
207188 /**
208- * Map MySQL system time zone or abbreviations like 'WIB' to a valid PHP time zone .
189+ * Retrieves the timezone offset from the database .
209190 *
210- * This function converts time zone abbreviations (like 'WIB', 'WITA', 'WIT') or system time zones
211- * to a recognized PHP time zone format (e.g., 'Asia/Jakarta').
191+ * This function detects the database type (MySQL, MariaDB, or PostgreSQL) from the given PDO connection
192+ * and executes the appropriate query to determine the timezone offset from UTC. It returns the
193+ * offset as a string in the format "+HH:MM" or "-HH:MM". If the database type is unsupported or
194+ * an error occurs, it defaults to "00:00".
212195 *
213- * @param string $timezoneAbbr The time zone abbreviation or system time zone (e.g., 'WIB', 'SYSTEM') .
214- * @return string|null Returns a PHP-compatible time zone (e.g., 'Asia/Jakarta') or null if not recognized .
196+ * @param PDO $pdo The PDO connection object .
197+ * @return string The timezone offset as a string (e.g., "+08:00", "-05:30"), or "00:00" on failure .
215198 */
216- private function mysqlToPhpTimezone ( $ timezoneAbbr )
199+ private static function getTimeZoneOffset ( $ pdo )
217200 {
218- $ timezoneMapping = [
219- // Indonesia
220- 'WIB ' => 'Asia/Jakarta ' , // Western Indonesia Time (e.g., Jakarta, Bali)
221- 'WITA ' => 'Asia/Makassar ' , // Central Indonesia Time (e.g., Bali, Sulawesi)
222- 'WIT ' => 'Asia/Jayapura ' , // Eastern Indonesia Time (e.g., Papua)
223-
224- // Common USA Time Zones
225- 'PST ' => 'America/Los_Angeles ' , // Pacific Standard Time (Standard Time)
226- 'PDT ' => 'America/Los_Angeles ' , // Pacific Daylight Time (Daylight Saving Time)
227- 'MST ' => 'America/Denver ' , // Mountain Standard Time
228- 'MDT ' => 'America/Denver ' , // Mountain Daylight Time
229- 'CST ' => 'America/Chicago ' , // Central Standard Time
230- 'CDT ' => 'America/Chicago ' , // Central Daylight Time
231- 'EST ' => 'America/New_York ' , // Eastern Standard Time
232- 'EDT ' => 'America/New_York ' , // Eastern Daylight Time
233- 'AKST ' => 'America/Anchorage ' , // Alaska Standard Time
234- 'AKDT ' => 'America/Anchorage ' , // Alaska Daylight Time
235- 'HST ' => 'Pacific/Honolulu ' , // Hawaii Standard Time
236-
237- // United Kingdom
238- 'GMT ' => 'Europe/London ' , // Greenwich Mean Time (Standard Time)
239- 'BST ' => 'Europe/London ' , // British Summer Time (Daylight Saving Time)
240-
241- // Central Europe
242- 'CET ' => 'Europe/Paris ' , // Central European Time
243- 'CEST ' => 'Europe/Paris ' , // Central European Summer Time (Daylight Saving Time)
244-
245- // Central Asia and Russia
246- 'MSK ' => 'Europe/Moscow ' , // Moscow Standard Time
247- 'MSD ' => 'Europe/Moscow ' , // Moscow Daylight Time (not used anymore)
248-
249- // Australia
250- 'AEST ' => 'Australia/Sydney ' , // Australian Eastern Standard Time
251- 'AEDT ' => 'Australia/Sydney ' , // Australian Eastern Daylight Time
252- 'ACST ' => 'Australia/Adelaide ' , // Australian Central Standard Time
253- 'ACDT ' => 'Australia/Adelaide ' , // Australian Central Daylight Time
254- 'AWST ' => 'Australia/Perth ' , // Australian Western Standard Time
255-
256- // Africa
257- 'CAT ' => 'Africa/Harare ' , // Central Africa Time
258- 'EAT ' => 'Africa/Nairobi ' , // East Africa Time
259- 'WAT ' => 'Africa/Algiers ' , // West Africa Time
260-
261- // India
262- 'IST ' => 'Asia/Kolkata ' , // Indian Standard Time
263-
264- // China and East Asia
265- 'CST ' => 'Asia/Shanghai ' , // China Standard Time
266- 'JST ' => 'Asia/Tokyo ' , // Japan Standard Time
267- 'KST ' => 'Asia/Seoul ' , // Korea Standard Time
268-
269- // Other time zones
270- 'UTC ' => 'UTC ' , // Coordinated Universal Time
271- 'Z ' => 'UTC ' , // Zulu time (same as UTC)
272- 'ART ' => 'Africa/Argentina ' , // Argentina Time
273- 'NFT ' => 'Pacific/Norfolk ' , // Norfolk Time Zone (Australia)
274-
275- // Time zones used in specific areas
276- 'NST ' => 'Asia/Kolkata ' , // Newfoundland Standard Time (if used as an abbreviation)
277- ];
278-
279- // Return the mapped PHP time zone or null if not found
280- return isset ($ timezoneMapping [$ timezoneAbbr ]) ? $ timezoneMapping [$ timezoneAbbr ] : null ;
201+ $ defaultValue = '00:00 ' ;
202+ try {
203+ // Detect the database driver
204+ $ driver = $ pdo ->getAttribute (PDO ::ATTR_DRIVER_NAME );
205+
206+ // Map the driver to a recognized database type
207+ $ dbType = self ::getDbType ($ driver );
208+
209+ // Prepare the query based on the database type
210+ if ($ dbType === PicoDatabaseType::DATABASE_TYPE_PGSQL ) {
211+ // Query to retrieve timezone offset in PostgreSQL
212+ $ query = "SELECT (EXTRACT(TIMEZONE FROM NOW()) / 3600)::TEXT || ':00' AS offset " ;
213+ } elseif (
214+ $ dbType === PicoDatabaseType::DATABASE_TYPE_MYSQL ||
215+ $ dbType === PicoDatabaseType::DATABASE_TYPE_MARIADB
216+ ) {
217+ // Query to retrieve timezone offset in MySQL or MariaDB
218+ $ query = "SELECT TIMEDIFF(NOW(), UTC_TIMESTAMP()) AS offset " ;
219+ } else {
220+ // Return default offset for unsupported database types
221+ return $ defaultValue ;
222+ }
223+
224+ // Execute the query and fetch the result
225+ $ stmt = $ pdo ->query ($ query );
226+ $ result = $ stmt ->fetch (PDO ::FETCH_ASSOC );
227+
228+ // Return the offset value if available
229+ return isset ($ result ['offset ' ]) ? $ result ['offset ' ] : $ defaultValue ;
230+ } catch (Exception $ e ) {
231+ // Handle any exceptions and return the default offset
232+ return $ defaultValue ;
233+ }
281234 }
282235
283236 /**
284- * Converts a PostgreSQL timezone string to a PHP-compatible timezone identifier.
237+ * Converts a timezone offset string to a corresponding PHP timezone name.
238+ *
239+ * This method takes a timezone offset string (e.g., "+08:00" or "-05:30") and computes
240+ * the total offset in seconds. It then attempts to map the offset to a standard PHP
241+ * timezone name. If no matching timezone is found, it falls back to returning a
242+ * UTC-based timezone string in the same offset format.
285243 *
286- * @param string $pgsqlTimezone The PostgreSQL timezone string (e.g., from SHOW timezone).
287- * @return string|null The PHP timezone identifier or null if no match is found.
244+ * Examples:
245+ * - Input: "+07:00" -> Output: "Asia/Jakarta" (if mapping exists).
246+ * - Input: "-05:30" -> Output: "UTC-05:30" (fallback if no mapping exists).
247+ *
248+ * @param string $offset The timezone offset string (e.g., "+08:00", "-05:30").
249+ * @return string The corresponding PHP timezone name, or a fallback UTC offset string (e.g., "UTC+08:00").
288250 */
289- private function pgsqlToPhpTimezone ( $ pgsqlTimezone )
251+ private static function convertOffsetToTimeZone ( $ offset )
290252 {
291- // Comprehensive map of PostgreSQL timezones to PHP-compatible timezone identifiers
292- $ timezoneMap = [
293- // UTC and variants
294- 'UTC ' => 'UTC ' ,
295- 'Etc/UTC ' => 'UTC ' ,
296- 'Etc/GMT ' => 'UTC ' ,
297-
298- // Common regions
299- 'US/Eastern ' => 'America/New_York ' ,
300- 'US/Central ' => 'America/Chicago ' ,
301- 'US/Mountain ' => 'America/Denver ' ,
302- 'US/Pacific ' => 'America/Los_Angeles ' ,
303- 'US/Alaska ' => 'America/Anchorage ' ,
304- 'US/Hawaii ' => 'Pacific/Honolulu ' ,
305- 'America/Chicago ' => 'America/Chicago ' ,
306- 'America/New_York ' => 'America/New_York ' ,
307- 'America/Los_Angeles ' => 'America/Los_Angeles ' ,
308- 'America/Denver ' => 'America/Denver ' ,
309- 'America/Anchorage ' => 'America/Anchorage ' ,
310- 'Pacific/Honolulu ' => 'Pacific/Honolulu ' ,
311-
312- // European timezones
313- 'Europe/London ' => 'Europe/London ' ,
314- 'Europe/Berlin ' => 'Europe/Berlin ' ,
315- 'Europe/Paris ' => 'Europe/Paris ' ,
316- 'Europe/Madrid ' => 'Europe/Madrid ' ,
317- 'Europe/Rome ' => 'Europe/Rome ' ,
318- 'Europe/Moscow ' => 'Europe/Moscow ' ,
319-
320- // Asia timezones
321- 'Asia/Tokyo ' => 'Asia/Tokyo ' ,
322- 'Asia/Shanghai ' => 'Asia/Shanghai ' ,
323- 'Asia/Kolkata ' => 'Asia/Kolkata ' ,
324- 'Asia/Dubai ' => 'Asia/Dubai ' ,
325- 'Asia/Singapore ' => 'Asia/Singapore ' ,
326- 'Asia/Hong_Kong ' => 'Asia/Hong_Kong ' ,
327- 'Asia/Seoul ' => 'Asia/Seoul ' ,
328-
329- // Australia timezones
330- 'Australia/Sydney ' => 'Australia/Sydney ' ,
331- 'Australia/Melbourne ' => 'Australia/Melbourne ' ,
332- 'Australia/Brisbane ' => 'Australia/Brisbane ' ,
333- 'Australia/Perth ' => 'Australia/Perth ' ,
334- 'Australia/Adelaide ' => 'Australia/Adelaide ' ,
335-
336- // Africa timezones
337- 'Africa/Johannesburg ' => 'Africa/Johannesburg ' ,
338- 'Africa/Cairo ' => 'Africa/Cairo ' ,
339- 'Africa/Lagos ' => 'Africa/Lagos ' ,
340-
341- // South America
342- 'America/Sao_Paulo ' => 'America/Sao_Paulo ' ,
343- 'America/Argentina/Buenos_Aires ' => 'America/Argentina/Buenos_Aires ' ,
344- 'America/Santiago ' => 'America/Santiago ' ,
345-
346- // Additional aliases or abbreviations
347- 'EST ' => 'America/New_York ' ,
348- 'CST ' => 'America/Chicago ' ,
349- 'MST ' => 'America/Denver ' ,
350- 'PST ' => 'America/Los_Angeles ' ,
351- ];
352-
353- // Check for exact matches in the map
354- if (isset ($ timezoneMap [$ pgsqlTimezone ])) {
355- return $ timezoneMap [$ pgsqlTimezone ];
356- }
253+ try {
254+ // Extract the sign ('+' or '-') from the offset
255+ $ sign = substr ($ offset , 0 , 1 ); // Get the first character ('+' or '-')
256+
257+ // Split the offset into hours and minutes (e.g., "+08:00" -> [8, 0])
258+ $ parts = explode (': ' , substr ($ offset , 1 )); // Remove the sign and split
259+ $ hours = (int )$ parts [0 ]; // Parse the hours
260+ $ minutes = isset ($ parts [1 ]) ? (int )$ parts [1 ] : 0 ; // Parse the minutes if available
261+
262+ // Calculate the total offset in seconds
263+ $ totalOffsetSeconds = ($ hours * 3600 ) + ($ minutes * 60 );
264+ if ($ sign === '- ' ) {
265+ $ totalOffsetSeconds = -$ totalOffsetSeconds ; // Negate if the offset is negative
266+ }
357267
358- // Use PHP's timezone database as a fallback for direct matches
359- if (in_array ($ pgsqlTimezone , timezone_identifiers_list (), true )) {
360- return $ pgsqlTimezone ;
361- }
268+ // Attempt to retrieve the PHP timezone name using the offset
269+ $ timeZone = timezone_name_from_abbr ("" , $ totalOffsetSeconds , 0 );
270+
271+ // Fallback: if no matching timezone is found, use a UTC-based string
272+ if ($ timeZone === false ) {
273+ $ timeZone = "UTC " . $ offset ; // Example: "UTC+08:00"
274+ }
362275
363- // Return null if no match is found
364- return null ;
276+ return $ timeZone ;
277+ } catch (Exception $ e ) {
278+ // Handle any exceptions by returning an error message
279+ return "UTC+00:00 " ;
280+ }
365281 }
366282
367283 /**
@@ -397,7 +313,7 @@ public function connect($withDatabase = true)
397313 if ($ databaseTimeZone !== null && !empty ($ databaseTimeZone )) {
398314 date_default_timezone_set ($ this ->databaseCredentials ->getTimeZone ());
399315 }
400- $ this ->databaseType = $ this -> getDbType ($ this ->databaseCredentials ->getDriver ());
316+ $ this ->databaseType = self :: getDbType ($ this ->databaseCredentials ->getDriver ());
401317 if ($ this ->getDatabaseType () == PicoDatabaseType::DATABASE_TYPE_SQLITE )
402318 {
403319 return $ this ->connectSqlite ();
@@ -498,7 +414,7 @@ private function connectRDMS($withDatabase = true)
498414 * - `PicoDatabaseType::DATABASE_TYPE_MARIADB`
499415 * - `PicoDatabaseType::DATABASE_TYPE_MYSQL`
500416 */
501- private function getDbType ($ databaseType ) // NOSONAR
417+ private static function getDbType ($ databaseType ) // NOSONAR
502418 {
503419 if (stripos ($ databaseType , 'sqlite ' ) !== false )
504420 {
0 commit comments