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 {