Umrechnung
Umrechnung auf Basis des letzten verfuegbaren Kurses zwischen den bevorzugten Waehrungen.
@@ -92,7 +84,16 @@ $pageData = json_encode([
-
Letzter Snapshot
+
+
+
Letzte Kurse
+
Letzter gespeicherter Snapshot fuer = e((string) ($settings['display_base_currency'] ?? $settings['default_base_currency'] ?? 'EUR')) ?>.
+
+
+
@@ -106,32 +107,33 @@ $pageData = json_encode([
-
-
Letzte Abrufe
-
-
-
-
- | Datum |
- Basis |
- Provider |
-
-
-
-
- | Noch keine Abrufe vorhanden. |
-
-
-
- | = e((string) ($fetch['fetched_at'] ?? '')) ?> |
- = e((string) ($fetch['base_currency'] ?? '')) ?> |
- = e((string) ($fetch['provider'] ?? '')) ?> |
-
-
-
-
-
-
+
+
+
+
Letzte Abrufe
+
+
+
+
+ | Datum |
+ Basis |
+ Provider |
+
+
+
+
+ | Noch keine Abrufe vorhanden. |
+
+
+
+ | = e((string) ($fetch['fetched_at_display'] ?? $fetch['fetched_at'] ?? '')) ?> |
+ = e((string) ($fetch['base_currency'] ?? '')) ?> |
+ = e((string) ($fetch['provider'] ?? '')) ?> |
+
+
+
+
+
diff --git a/modules/fx-rates/src/Api/Router.php b/modules/fx-rates/src/Api/Router.php
index fe2a43e..b1ee7f0 100644
--- a/modules/fx-rates/src/Api/Router.php
+++ b/modules/fx-rates/src/Api/Router.php
@@ -73,17 +73,12 @@ final class Router
if ($path === 'v1/refresh' && $method === 'POST') {
$input = $this->input();
$base = $this->stringOrNull($input['base'] ?? null);
- $currencies = $this->parseCsv($input['currencies'] ?? null);
- if ($currencies === null) {
- $settings = module_fn('fx-rates', 'settings');
- $currencies = is_array($settings['preferred_currencies'] ?? null) ? $settings['preferred_currencies'] : null;
- }
$force = !empty($input['force']);
$maxAgeHours = is_numeric($input['max_age_hours'] ?? null) ? (float) $input['max_age_hours'] : 24.0;
$result = $force
- ? $this->service->refreshLatestRates($currencies, $base)
- : $this->service->ensureFreshLatestRates($maxAgeHours, $base, $currencies);
+ ? $this->service->refreshLatestRates(null, $base)
+ : $this->service->ensureFreshLatestRates($maxAgeHours, $base, null);
$this->respond(['data' => $result], 201);
}
diff --git a/modules/fx-rates/src/Domain/FxRatesService.php b/modules/fx-rates/src/Domain/FxRatesService.php
index c3672cb..476e23a 100644
--- a/modules/fx-rates/src/Domain/FxRatesService.php
+++ b/modules/fx-rates/src/Domain/FxRatesService.php
@@ -19,17 +19,17 @@ final class FxRatesService
public function latestStatus(): ?array
{
- return $this->repository->getLatestFetch($this->defaultBaseCurrency());
+ return $this->localizeFetch($this->repository->getLatestFetch($this->defaultBaseCurrency()));
}
public function latestStatuses(): array
{
- return $this->repository->listLatestFetches();
+ return array_map(fn (array $fetch): array => $this->localizeFetch($fetch), $this->repository->listLatestFetches());
}
public function recentFetches(int $limit = 20): array
{
- return $this->repository->listRecentFetches($limit);
+ return array_map(fn (array $fetch): array => $this->localizeFetch($fetch), $this->repository->listRecentFetches($limit));
}
public function snapshot(?string $baseCurrency = null, ?string $at = null, ?array $symbols = null, ?int $windowMinutes = null): ?array
@@ -53,7 +53,7 @@ final class FxRatesService
return null;
}
- return $this->rebaseSnapshot($snapshot, $base, $symbols);
+ return $this->localizeSnapshot($this->rebaseSnapshot($snapshot, $base, $symbols));
}
$atUtc = $this->normalizeTimestamp($at);
@@ -76,10 +76,10 @@ final class FxRatesService
return null;
}
- return $rebased + [
+ return $this->localizeSnapshot($rebased + [
'requested_at' => $atUtc,
'distance_seconds' => $nearest['distance_seconds'] ?? null,
- ];
+ ]);
}
public function findRate(?string $fromCurrency, ?string $toCurrency, ?string $at = null, ?int $windowMinutes = null): ?array
@@ -91,7 +91,7 @@ final class FxRatesService
}
if ($from === $to) {
- return [
+ return $this->localizeRateResult([
'base_currency' => $from,
'target_currency' => $to,
'rate' => 1.0,
@@ -99,7 +99,7 @@ final class FxRatesService
'fetched_at' => $at ? $this->normalizeTimestamp($at) : null,
'rate_date' => $at ? substr((string) $this->normalizeTimestamp($at), 0, 10) : gmdate('Y-m-d'),
'is_exact_pair' => true,
- ];
+ ]);
}
$cacheKey = implode(':', [$from, $to, $at ?? '', (string) ($windowMinutes ?? 0)]);
@@ -124,7 +124,7 @@ final class FxRatesService
$rates = is_array($snapshot['rates'] ?? null) ? $snapshot['rates'] : [];
$resolved = $this->resolveRateFromSnapshot($snapshot, $rates, $from, $to);
if ($resolved !== null) {
- return $this->memoryCache[$cacheKey] = $resolved;
+ return $this->memoryCache[$cacheKey] = $this->localizeRateResult($resolved);
}
}
@@ -148,7 +148,7 @@ final class FxRatesService
public function refreshLatestRates(?array $currencies = null, ?string $baseCurrency = null): array
{
$requestedBase = $this->normalizeCurrency($baseCurrency ?: $this->defaultBaseCurrency());
- $payload = $this->fetchLatestPayload($requestedBase, $currencies);
+ $payload = $this->fetchLatestPayload($requestedBase, null);
$base = $this->normalizeCurrency((string) ($payload['base'] ?? $requestedBase));
if ($base === '') {
$base = $requestedBase !== '' ? $requestedBase : 'USD';
@@ -169,7 +169,7 @@ final class FxRatesService
'rate_date' => $rateDate,
'updated_count' => count($saved['rates'] ?? []),
'rates' => $saved['rates'] ?? [],
- 'fetch' => $saved['fetch'] ?? null,
+ 'fetch' => $this->localizeFetch(is_array($saved['fetch'] ?? null) ? $saved['fetch'] : null),
];
}
@@ -186,7 +186,7 @@ final class FxRatesService
'rate_date' => $latest['rate_date'] ?? null,
'updated_count' => 0,
'rates' => [],
- 'fetch' => $latest,
+ 'fetch' => $this->localizeFetch($latest),
'reused' => true,
];
}
@@ -210,7 +210,7 @@ final class FxRatesService
$direct = $this->repository->listDirectHistory($fromCurrency, $toCurrency, $this->normalizeTimestamp($from), $this->normalizeTimestamp($to), $limit);
if ($direct !== []) {
- return $direct;
+ return array_map(fn (array $row): array => $this->localizeRateResult($row), $direct);
}
$inverse = $this->repository->listDirectHistory($toCurrency, $fromCurrency, $this->normalizeTimestamp($from), $this->normalizeTimestamp($to), $limit);
@@ -237,7 +237,7 @@ final class FxRatesService
];
}
- return $result;
+ return array_map(fn (array $row): array => $this->localizeRateResult($row), $result);
}
public function runScheduledRefresh(array $context = []): array
@@ -268,7 +268,7 @@ final class FxRatesService
'ok' => true,
'message' => 'Kein FX-Abruf: fuer heute existiert bereits ein Snapshot nach ' . str_pad((string) $targetHour, 2, '0', STR_PAD_LEFT) . ':00.',
'skipped' => true,
- 'fetch' => $latest,
+ 'fetch' => $this->localizeFetch($latest),
'context' => $context,
];
}
@@ -647,6 +647,57 @@ final class FxRatesService
return $filtered;
}
+ private function localizeFetch(?array $fetch): ?array
+ {
+ if (!is_array($fetch)) {
+ return null;
+ }
+
+ $fetch['fetched_at_display'] = $this->formatDisplayTimestamp($fetch['fetched_at'] ?? null);
+ $fetch['created_at_display'] = $this->formatDisplayTimestamp($fetch['created_at'] ?? null);
+ return $fetch;
+ }
+
+ private function localizeSnapshot(?array $snapshot): ?array
+ {
+ if (!is_array($snapshot)) {
+ return null;
+ }
+
+ $snapshot['fetched_at_display'] = $this->formatDisplayTimestamp($snapshot['fetched_at'] ?? null);
+ if (array_key_exists('requested_at', $snapshot)) {
+ $snapshot['requested_at_display'] = $this->formatDisplayTimestamp($snapshot['requested_at']);
+ }
+ return $snapshot;
+ }
+
+ private function localizeRateResult(array $rate): array
+ {
+ $rate['fetched_at_display'] = $this->formatDisplayTimestamp($rate['fetched_at'] ?? null);
+ if (array_key_exists('requested_at', $rate)) {
+ $rate['requested_at_display'] = $this->formatDisplayTimestamp($rate['requested_at']);
+ }
+ return $rate;
+ }
+
+ private function formatDisplayTimestamp(mixed $value): string
+ {
+ $raw = trim((string) $value);
+ if ($raw === '') {
+ return '';
+ }
+
+ try {
+ $date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $raw, new DateTimeZone('UTC'));
+ if (!$date instanceof DateTimeImmutable) {
+ $date = new DateTimeImmutable($raw, new DateTimeZone('UTC'));
+ }
+ return $date->setTimezone($this->displayTimezone())->format('d.m.Y H:i:s');
+ } catch (\Throwable) {
+ return $raw;
+ }
+ }
+
private function normalizeCurrencyApiComCurrenciesPayload(array $payload): array
{
$rawCurrencies = is_array($payload['data'] ?? null) ? $payload['data'] : null;
@@ -815,4 +866,9 @@ final class FxRatesService
return new DateTimeZone('Europe/Berlin');
}
}
+
+ private function displayTimezone(): DateTimeZone
+ {
+ return $this->scheduleTimezone();
+ }
}