Skip to content

Commit 8adcc29

Browse files
committed
Merge branch 'feature/2.8'
2 parents 6b87042 + 89f80cd commit 8adcc29

File tree

1 file changed

+88
-172
lines changed

1 file changed

+88
-172
lines changed

src/Database/PicoDatabase.php

Lines changed: 88 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)