add controllers, servoices

This commit is contained in:
root
2026-03-09 02:52:13 -04:00
parent c8de5f7edc
commit d76c871cb7
501 changed files with 34439 additions and 21843 deletions
+222
View File
@@ -0,0 +1,222 @@
<?php
namespace App\Services;
use App\Models\PreferencesModel;
use App\Models\SettingsModel;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\I18n\Time;
class TimeService
{
private string $defaultUserTimezone = 'America/New_York';
private string $serverTimezone = 'UTC';
// Cached per-request user timezone
private ?string $cachedUserTz = null;
/**
* Prime detection cache from the Request (optional call, lazy by default).
*/
public function primeFromRequest(?RequestInterface $request = null): void
{
$this->cachedUserTz = $this->detectUserTimezone($request);
}
/**
* Get the resolved user timezone (detect lazily if needed).
*/
public function userTimezone(?RequestInterface $request = null): string
{
if ($this->cachedUserTz !== null) {
return $this->cachedUserTz;
}
$this->cachedUserTz = $this->detectUserTimezone($request);
return $this->cachedUserTz;
}
/**
* Core detection logic (headers > user pref > settings > school config > default).
*/
public function detectUserTimezone(?RequestInterface $request = null): string
{
$request = $request ?: service('request');
// 1) Explicit client header
try {
$tzHeader = $request->getHeaderLine('X-Timezone')
?: $request->getHeaderLine('Timezone')
?: $request->getHeaderLine('Accept-Timezone')
?: null;
if ($tzHeader && in_array($tzHeader, timezone_identifiers_list(), true)) {
return $tzHeader;
}
} catch (\Throwable $e) {
// ignore
}
// 2) User preference (if logged-in)
try {
$userId = (int) (session('user_id') ?: 0);
if ($userId > 0) {
// Do not select a specific column; some DBs may not have it yet
$pref = (new PreferencesModel())
->where('user_id', $userId)
->first();
$tz = $pref['timezone'] ?? null;
if ($tz && in_array($tz, timezone_identifiers_list(), true)) {
return $tz;
}
}
} catch (\Throwable $e) {
// ignore
}
// 3) Global settings timezone (if available)
try {
$settings = (new SettingsModel())->getSettings();
$tz = $settings['timezone'] ?? null;
if ($tz && in_array($tz, timezone_identifiers_list(), true)) {
return $tz;
}
} catch (\Throwable $e) {
// ignore
}
// 4) School attendance timezone (if defined)
try {
$tz = (string) (config('School')->attendance['timezone'] ?? '');
if ($tz && in_array($tz, timezone_identifiers_list(), true)) {
return $tz;
}
} catch (\Throwable $e) {
// ignore
}
// 5) Default
return $this->defaultUserTimezone;
}
/**
* Server timezone (for storage/UTC operations).
*/
public function serverTimezone(): string
{
return $this->serverTimezone;
}
/**
* Current time in user timezone as CI Time.
*/
public function nowLocal(?string $tz = null): Time
{
$tz = $tz ?: $this->userTimezone();
return Time::now($tz);
}
/**
* Current time in UTC as CI Time.
*/
public function nowUTC(): Time
{
return Time::now($this->serverTimezone);
}
/**
* Convert any supported input to UTC string (Y-m-d H:i:s by default).
*
* @param string|Time|\DateTimeInterface|null $value
*/
public function toUTC($value, ?string $fromTz = null, string $format = 'Y-m-d H:i:s'): ?string
{
if ($value === null || $value === '') {
return null;
}
$fromTz = $fromTz ?: $this->userTimezone();
try {
if ($value instanceof Time) {
return $value->setTimezone($this->serverTimezone)->toDateTimeString();
}
if ($value instanceof \DateTimeInterface) {
return Time::createFromInstance($value)
->setTimezone($this->serverTimezone)
->format($format);
}
// assume string
return Time::parse((string)$value, $fromTz)
->setTimezone($this->serverTimezone)
->format($format);
} catch (\Throwable $e) {
return null;
}
}
/**
* Convert UTC (or provided timezone) to user-local string.
*
* @param string|Time|\DateTimeInterface|null $value
*/
public function toLocal($value, ?string $sourceTz = null, ?string $targetTz = null, string $format = 'Y-m-d H:i:s'): ?string
{
if ($value === null || $value === '') {
return null;
}
$targetTz = $targetTz ?: $this->userTimezone();
if ($sourceTz === null && $this->isDateOnlyString($value)) {
// Date-only strings should not shift across timezones.
$sourceTz = $targetTz;
} else {
$sourceTz = $sourceTz ?: $this->serverTimezone;
}
try {
if ($value instanceof Time) {
return $value->setTimezone($targetTz)->format($format);
}
if ($value instanceof \DateTimeInterface) {
return Time::createFromInstance($value)
->setTimezone($targetTz)
->format($format);
}
// assume string
return Time::parse((string)$value, $sourceTz)
->setTimezone($targetTz)
->format($format);
} catch (\Throwable $e) {
return null;
}
}
/**
* Convenience formatter for user-local.
*/
public function formatLocal($value, string $format = 'Y-m-d H:i', ?string $sourceTz = null): string
{
return (string) ($this->toLocal($value, $sourceTz, null, $format) ?? '');
}
/**
* Convenience formatter for UTC.
*/
public function formatUTC($value, string $format = 'Y-m-d H:i:s', ?string $fromTz = null): string
{
return (string) ($this->toUTC($value, $fromTz, $format) ?? '');
}
private function isDateOnlyString($value): bool
{
if (!is_string($value)) {
return false;
}
$value = trim($value);
return (bool) preg_match('/^\d{4}-\d{2}-\d{2}$/', $value);
}
}