diff --git a/approot/messages.xml b/approot/messages.xml
index 000dfef9..8a8ccaca 100644
--- a/approot/messages.xml
+++ b/approot/messages.xml
@@ -129,7 +129,7 @@
Get report
Last scan
Not available
-{1} files were scanned in {2} at {4} on {3} (UTC) - {5} errors, {6} duplicates
+{1} files were scanned in {2} at {4} on {3} - {5} errors, {6} duplicates
Missing artist name
Unable to find track paths
Unable to find the path "{1}"
@@ -141,7 +141,7 @@
Scanner
Status
Not scheduled
-Scheduled at {2} on {1} (UTC)
+Scheduled at {2} on {1}
Scanning: step {1}/{2}
Associating artist images: {1}%...
Associating external lyrics: {1}%...
diff --git a/approot/messages_es.xml b/approot/messages_es.xml
index bc9b0183..67068d3f 100644
--- a/approot/messages_es.xml
+++ b/approot/messages_es.xml
@@ -129,7 +129,7 @@
Informe
Último escaneo
No disponible
-{1} ficheros analizados en {2} el {3} a las {4} (UTC) - {5} errores, {6} duplicados
+{1} ficheros analizados en {2} el {3} a las {4} - {5} errores, {6} duplicados
Falta el nombre del artista
No se pudieron encontrar las rutas de las pistas
No se pudo encontrar la ruta "{1}"
@@ -141,7 +141,7 @@
Escanear
Estado
No progamado
-Programado el {1} a las {2} (UTC)
+Programado el {1} a las {2}
Escaneando: etapa {1}/{2}
Association des images des artistes: {1}%...
Association des paroles externes: {1}%...
diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml
index 65d2a543..20544b6e 100644
--- a/approot/messages_fr.xml
+++ b/approot/messages_fr.xml
@@ -129,7 +129,7 @@
Rapport
Dernier scan
Non disponible
-{1} fichiers analysés en {2} à {4} le {3} (UTC) - {5} erreurs, {6} duplicatas
+{1} fichiers analysés en {2} à {4} le {3} - {5} erreurs, {6} duplicatas
Nom de l'artiste manquant
Impossible de trouver les chemins des pistes
Impossible de trouver le chemin "{1}"
@@ -141,7 +141,7 @@
Scanner
Statut
Non planifié
-Planifié à {2} le {1} (UTC)
+Planifié à {2} le {1}
En cours de scan : étape {1}/{2}
Association des images des artistes: {1}%...
Association des paroles externes: {1}%...
diff --git a/approot/messages_it.xml b/approot/messages_it.xml
index 39051ccb..c677a12d 100644
--- a/approot/messages_it.xml
+++ b/approot/messages_it.xml
@@ -129,7 +129,7 @@
Scarica un resoconto
Ultima scansione
Non disponibile
-{1} file scansionati in {2} alle {3} il {4} (UTC) - {5} errori, {6} duplicati
+{1} file scansionati in {2} alle {3} il {4} - {5} errori, {6} duplicati
Nome dell'artista mancante
Impossibile trovare i percorsi delle tracce
Impossibile trovare il percorso "{1}"
@@ -141,7 +141,7 @@
Scanner
Stato
Non pianificato
-Programmato alle {2} del {1} (UTC)
+Programmato alle {2} del {1}
Scansione: passo {1}/{2}
Associando immagini degli artisti: {1}%...
Associazione dei testi esterni: {1}%...
diff --git a/approot/messages_pl.xml b/approot/messages_pl.xml
index 25eba67e..144875ea 100644
--- a/approot/messages_pl.xml
+++ b/approot/messages_pl.xml
@@ -138,7 +138,7 @@
Pokaż raport
Ostatnie skanowanie
Niedostępne
-Przeskanowano {1} plików w {2} o {4} dnia {3} (UTC) - {5} błędów, {6} duplikatów
+Przeskanowano {1} plików w {2} o {4} dnia {3} - {5} błędów, {6} duplikatów
Brakuje nazwy artysty
Nie można znaleźć ścieżek utworów
Nie można znaleźć ścieżki „{1}”
@@ -150,7 +150,7 @@
Skaner
Status
Nie zaplanowano
-Zaplanowano o {2} dnia {1} (UTC)
+Zaplanowano o {2} dnia {1}
Skanowanie: krok {1}/{2}
Kojarzenie obrazów artystów: {1}%...
Kojarzenie zewnętrznych tekstów: {1}%...
diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml
index 8c4146d3..cd558e34 100644
--- a/approot/messages_zh.xml
+++ b/approot/messages_zh.xml
@@ -127,7 +127,7 @@
生成报告
最后扫描
不可用
-已扫描 {1} 个文件,用时 {2},在 {3} 的 {4} (UTC) - {5} 个错误,{6} 个重复项
+已扫描 {1} 个文件,用时 {2},在 {3} 的 {4} - {5} 个错误,{6} 个重复项
缺少艺术家名称
无法找到曲目路径
无法找到路径 "{1}"
@@ -139,7 +139,7 @@
扫描器
状态
无计划
-计划于 {2} 在 {1} (UTC)
+计划于 {2} 在 {1}
扫描中: 阶段 {1}/{2}
关联艺术家图片:{1}%...
关联外部歌词:{1}%...
diff --git a/src/libs/core/impl/Logger.cpp b/src/libs/core/impl/Logger.cpp
index acffea76..0ae6b682 100644
--- a/src/libs/core/impl/Logger.cpp
+++ b/src/libs/core/impl/Logger.cpp
@@ -19,15 +19,47 @@
#include "Logger.hpp"
-#include
-
#include
+#include
+#include
+#include
#include
#include
+#include
#include
#include "core/Exception.hpp"
-#include "core/String.hpp"
+
+namespace
+{
+ std::string localISO8601TimeString()
+ {
+ using Clock = std::chrono::system_clock;
+ const auto now{ Clock::now() };
+ const int ms{ static_cast(std::chrono::duration_cast(now.time_since_epoch()).count() % 1000) };
+ const std::time_t tt{ Clock::to_time_t(now) };
+ std::tm tm{};
+ ::localtime_r(&tt, &tm);
+
+ std::array datetime{};
+ std::array offset{};
+ std::strftime(datetime.data(), datetime.size(), "%Y-%m-%dT%H:%M:%S", &tm);
+ std::strftime(offset.data(), offset.size(), "%z", &tm); // "+HHMM" or "-HHMM"
+
+ // Append msecs and offset
+ const std::string_view offsetView{ offset.data() };
+ std::string result{ datetime.data() };
+ result += '.';
+ result += static_cast('0' + ms / 100);
+ result += static_cast('0' + (ms / 10) % 10);
+ result += static_cast('0' + ms % 10);
+ result += offsetView.substr(0, 3); // sign + HH
+ result += ':';
+ result += offsetView.substr(3); // MM
+
+ return result;
+ }
+} // namespace
namespace lms::core::logging
{
@@ -205,9 +237,8 @@ namespace lms::core::logging
{
assert(isSeverityActive(severity)); // should have been filtered out by a isSeverityActive call
OutputStream* outputStream{ _severityToOutputStreamMap.at(severity) };
- const Wt::WDateTime now{ Wt::WDateTime::currentDateTime() };
std::unique_lock lock{ outputStream->mutex };
- outputStream->stream << stringUtils::toISO8601String(now) << " " << std::this_thread::get_id() << " [" << getSeverityName(severity) << "] [" << getModuleName(module) << "] " << message << std::endl;
+ outputStream->stream << localISO8601TimeString() << " " << std::this_thread::get_id() << " [" << getSeverityName(severity) << "] [" << getModuleName(module) << "] " << message << std::endl;
}
-} // namespace lms::core::logging
\ No newline at end of file
+} // namespace lms::core::logging
diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp
index 5d1a1b58..58790df4 100644
--- a/src/lms/ui/LmsApplication.cpp
+++ b/src/lms/ui/LmsApplication.cpp
@@ -19,6 +19,8 @@
#include "LmsApplication.hpp"
+#include
+
#include
#include
#include
@@ -117,7 +119,7 @@ namespace lms::ui
return res;
}
- Wt::WLocale createLocale(const std::string& name)
+ Wt::WLocale createLocale(const std::string& name, [[maybe_unused]] const std::string& timeZoneName)
{
Wt::WLocale locale{ name };
locale.setDecimalPoint(Wt::WString::tr("Lms.locale.decimal-point").toUTF8());
@@ -126,6 +128,19 @@ namespace lms::ui
locale.setTimeFormat(Wt::WString::tr("Lms.locale.time-format").toUTF8());
locale.setDateTimeFormat(Wt::WString::tr("Lms.locale.date-time-format").toUTF8());
+#ifdef WT_DATE_TZ_USE_STD
+ if (!timeZoneName.empty())
+ {
+ try
+ {
+ locale.setTimeZone(std::chrono::locate_zone(timeZoneName));
+ }
+ catch (const std::runtime_error&)
+ {
+ // unknown zone, display stays UTC
+ }
+ }
+#endif
return locale;
}
@@ -224,7 +239,7 @@ namespace lms::ui
setTitle();
setLocalizedStrings(getOrCreateMessageBundle());
- setLocale(createLocale(Wt::WLocale::currentLocale().name()));
+ setLocale(createLocale(Wt::WLocale::currentLocale().name(), environment().timeZoneName()));
// Handle Media Scanner events and other session events
enableUpdates(true);
@@ -325,7 +340,7 @@ namespace lms::ui
setUserInfo(userId, strongAuth);
- LMS_LOG(UI, INFO, "User '" << getUserLoginName() << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", locale = '" << locale().name() << "'");
+ LMS_LOG(UI, INFO, "User '" << getUserLoginName() << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent() << ", locale = '" << locale().name() << "', timezone = '" << environment().timeZoneName() << "'");
_appManager.registerApplication(*this);
_appManager.applicationRegistered.connect(this, [this](LmsApplication& otherApplication) {
diff --git a/src/lms/ui/admin/ScannerController.cpp b/src/lms/ui/admin/ScannerController.cpp
index 103b8dd0..ad87ae72 100644
--- a/src/lms/ui/admin/ScannerController.cpp
+++ b/src/lms/ui/admin/ScannerController.cpp
@@ -21,6 +21,7 @@
#include
#include
+#include
#include
#include
#include
@@ -114,11 +115,26 @@ namespace lms::ui
{
if (status.lastCompleteScanStats)
{
+ const auto& locale{ Wt::WLocale::currentLocale() };
+ const Wt::WDateTime& stopTime{ status.lastCompleteScanStats->stopTime };
+ Wt::WString stopDateStr;
+ Wt::WString stopTimeStr;
+ if (locale.timeZone())
+ {
+ const Wt::WLocalDateTime local{ stopTime.toLocalTime() };
+ stopDateStr = local.date().toString(locale.dateFormat());
+ stopTimeStr = local.time().toString(locale.timeFormat());
+ }
+ else
+ {
+ stopDateStr = stopTime.date().toString(locale.dateFormat());
+ stopTimeStr = stopTime.time().toString(locale.timeFormat()) + " (UTC)";
+ }
_lastScanStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.last-scan-status")
.arg(status.lastCompleteScanStats->getTotalFileCount())
.arg(durationToString(status.lastCompleteScanStats->startTime, status.lastCompleteScanStats->stopTime))
- .arg(status.lastCompleteScanStats->stopTime.date().toString(Wt::WLocale::currentLocale().dateFormat()))
- .arg(status.lastCompleteScanStats->stopTime.time().toString(Wt::WLocale::currentLocale().timeFormat()))
+ .arg(stopDateStr)
+ .arg(stopTimeStr)
.arg(status.lastCompleteScanStats->errorsCount)
.arg(status.lastCompleteScanStats->duplicates.size()));
@@ -144,11 +160,27 @@ namespace lms::ui
break;
case IScannerService::State::Scheduled:
- _status->setText(Wt::WString::tr("Lms.Admin.ScannerController.status-scheduled")
- .arg(status.nextScheduledScan.date().toString(Wt::WLocale::currentLocale().dateFormat()))
- .arg(status.nextScheduledScan.time().toString(Wt::WLocale::currentLocale().timeFormat())));
- _stepStatus->setText("");
- break;
+ {
+ const auto& locale{ Wt::WLocale::currentLocale() };
+ Wt::WString nextDateStr;
+ Wt::WString nextTimeStr;
+ if (locale.timeZone())
+ {
+ const Wt::WLocalDateTime local{ status.nextScheduledScan.toLocalTime() };
+ nextDateStr = local.date().toString(locale.dateFormat());
+ nextTimeStr = local.time().toString(locale.timeFormat());
+ }
+ else
+ {
+ nextDateStr = status.nextScheduledScan.date().toString(locale.dateFormat());
+ nextTimeStr = status.nextScheduledScan.time().toString(locale.timeFormat()) + " (UTC)";
+ }
+ _status->setText(Wt::WString::tr("Lms.Admin.ScannerController.status-scheduled")
+ .arg(nextDateStr)
+ .arg(nextTimeStr));
+ _stepStatus->setText("");
+ break;
+ }
case IScannerService::State::InProgress:
assert(status.currentScanStepStats);
diff --git a/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp
index 85a0505d..9af91ce6 100644
--- a/src/lms/ui/admin/UserView.cpp
+++ b/src/lms/ui/admin/UserView.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include
#include
@@ -229,7 +230,10 @@ namespace lms::ui
t->bindString("title", title, Wt::TextFormat::Plain);
t->setCondition("if-has-last-login", true);
- t->bindString("last-login", user->getLastLogin().toString(), Wt::TextFormat::Plain);
+ const auto& locale{ Wt::WLocale::currentLocale() };
+ const Wt::WDateTime lastLogin{ user->getLastLogin() };
+ const Wt::WString lastLoginStr{ locale.timeZone() ? lastLogin.toLocalTime().toString() : lastLogin.toString(locale.dateTimeFormat()) + " (UTC)" };
+ t->bindString("last-login", lastLoginStr, Wt::TextFormat::Plain);
}
else
{