Files
2025-12-10 14:38:26 -08:00

237 lines
7.8 KiB
C++

// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************
#include "format_unit.h"
#include "basic_math.h"
#include "sys_error.h"
#include "i18n.h"
#include "time.h"
#include "globals.h"
#include "utf.h"
#include <iostream>
#include <langinfo.h>
#include <clocale> //thousands separator
#include "utf.h" //
using namespace zen;
std::wstring zen::formatTwoDigitPrecision(double value)
{
//print two digits: 0,1 | 1,1 | 11
if (std::abs(value) < 9.95) //9.99 must not be formatted as "10.0"
return printNumber<std::wstring>(L"%.1f", value);
return formatNumber(std::llround(value));
}
std::wstring zen::formatThreeDigitPrecision(double value)
{
//print three digits: 0,01 | 0,11 | 1,11 | 11,1 | 111
if (std::abs(value) < 9.995) //9.999 must not be formatted as "10.00"
return printNumber<std::wstring>(L"%.2f", value);
if (std::abs(value) < 99.95) //99.99 must not be formatted as "100.0"
return printNumber<std::wstring>(L"%.1f", value);
return formatNumber(std::llround(value));
}
std::wstring zen::formatFilesizeShort(int64_t size)
{
//if (size < 0) return _("Error"); -> really?
if (std::abs(size) <= 999)
return _P("1 byte", "%x bytes", static_cast<int>(size));
double sizeInUnit = static_cast<double>(size);
auto formatUnit = [&](const std::wstring& unitTxt) { return replaceCpy(unitTxt, L"%x", formatThreeDigitPrecision(sizeInUnit)); };
sizeInUnit /= bytesPerKilo;
if (std::abs(sizeInUnit) < 999.5)
return formatUnit(_("%x KB"));
sizeInUnit /= bytesPerKilo;
if (std::abs(sizeInUnit) < 999.5)
return formatUnit(_("%x MB"));
sizeInUnit /= bytesPerKilo;
if (std::abs(sizeInUnit) < 999.5)
return formatUnit(_("%x GB"));
sizeInUnit /= bytesPerKilo;
if (std::abs(sizeInUnit) < 999.5)
return formatUnit(_("%x TB"));
sizeInUnit /= bytesPerKilo;
return formatUnit(_("%x PB"));
}
namespace
{
enum class UnitRemTime
{
sec,
min,
hour,
day
};
std::wstring formatUnitTime(int val, UnitRemTime unit)
{
switch (unit)
{
case UnitRemTime::sec: return _P("1 sec", "%x sec", val);
case UnitRemTime::min: return _P("1 min", "%x min", val);
case UnitRemTime::hour: return _P("1 hour", "%x hours", val);
case UnitRemTime::day: return _P("1 day", "%x days", val);
}
assert(false);
return _("Error");
}
template <int M, int N>
std::wstring roundToBlock(double timeInHigh,
UnitRemTime unitHigh, const int (&stepsHigh)[M],
int unitLowPerHigh,
UnitRemTime unitLow, const int (&stepsLow)[N])
{
assert(unitLowPerHigh > 0);
const double granularity = 0.1;
const double timeInLow = timeInHigh * unitLowPerHigh;
const int blockSizeLow = granularity * timeInHigh < 1 ?
numeric::roundToGrid(granularity * timeInLow, std::begin(stepsLow), std::end(stepsLow)):
numeric::roundToGrid(granularity * timeInHigh, std::begin(stepsHigh), std::end(stepsHigh)) * unitLowPerHigh;
const int roundedtimeInLow = std::lround(timeInLow / blockSizeLow) * blockSizeLow;
std::wstring output = formatUnitTime(roundedtimeInLow / unitLowPerHigh, unitHigh);
if (unitLowPerHigh > blockSizeLow)
output += L' ' + formatUnitTime(roundedtimeInLow % unitLowPerHigh, unitLow);
return output;
}
}
std::wstring zen::formatRemainingTime(double timeInSec)
{
const int steps10[] = {1, 2, 5, 10};
const int steps24[] = {1, 2, 3, 4, 6, 8, 12, 24};
const int steps60[] = {1, 2, 5, 10, 15, 20, 30, 60};
//determine preferred unit
double timeInUnit = timeInSec;
if (timeInUnit <= 60)
return roundToBlock(timeInUnit, UnitRemTime::sec, steps60, 1, UnitRemTime::sec, steps60);
timeInUnit /= 60;
if (timeInUnit <= 60)
return roundToBlock(timeInUnit, UnitRemTime::min, steps60, 60, UnitRemTime::sec, steps60);
timeInUnit /= 60;
if (timeInUnit <= 24)
return roundToBlock(timeInUnit, UnitRemTime::hour, steps24, 60, UnitRemTime::min, steps60);
timeInUnit /= 24;
return roundToBlock(timeInUnit, UnitRemTime::day, steps10, 24, UnitRemTime::hour, steps24);
//note: for 10% granularity steps10 yields a valid blocksize only up to timeInUnit == 100!
//for larger time sizes this results in a finer granularity than expected: 10 days -> should not be a problem considering "usual" remaining time for synchronization
}
std::wstring zen::formatProgressPercent(double fraction, int decPlaces)
{
if (decPlaces == 0) //special case for perf
return numberTo<std::wstring>(static_cast<int>(std::floor(fraction * 100))) + L'%';
//round down! don't show 100% when not actually done: https://freefilesync.org/forum/viewtopic.php?t=9781
const double blocks = std::pow(10, decPlaces);
const double percent = std::floor(fraction * 100 * blocks) / blocks;
assert(0 <= decPlaces && decPlaces <= 9);
wchar_t format[] = L"%.0f" L"%%" /*literal %: need to localize?*/;
format[2] += static_cast<wchar_t>(std::clamp(decPlaces, 0, 9));
return printNumber<std::wstring>(format, percent);
}
std::wstring zen::formatNumber(int64_t n)
{
//::setlocale (LC_ALL, ""); -> see localization.cpp::wxWidgetsLocale
static_assert(sizeof(long long int) == sizeof(n));
return printNumber<std::wstring>(L"%'lld", n); //considers grouping (')
}
std::wstring zen::formatUtcToLocalTime(time_t utcTime)
{
auto fmtFallback = [utcTime] //don't take "no" for an answer!
{
if (const TimeComp tc = getUtcTime(utcTime);
tc != TimeComp())
{
wchar_t buf[128] = {}; //the only way to format abnormally large or invalid modTime: std::strftime() will fail!
if (const int rv = std::swprintf(buf, std::size(buf), L"%d-%02d-%02d %02d:%02d:%02d GMT", tc.year, tc.month, tc.day, tc.hour, tc.minute, tc.second);
0 < rv && rv < std::ssize(buf))
return std::wstring(buf, rv);
}
return L"time_t = " + numberTo<std::wstring>(utcTime);
};
const TimeComp& loc = getLocalTime(utcTime); //returns TimeComp() on error
/*const*/ std::wstring dateTimeFmt = utfTo<std::wstring>(formatTime(Zstr("%x %X"), loc));
if (dateTimeFmt.empty())
return fmtFallback();
return dateTimeFmt;
}
WeekDay impl::getFirstDayOfWeekImpl() //throw SysError
{
/* testing: change locale via command line
---------------------------------------
LC_TIME=en_DK.utf8 => Monday
LC_TIME=en_US.utf8 => Sunday */
const char* firstDay = ::nl_langinfo(_NL_TIME_FIRST_WEEKDAY); //[1-Sunday, 7-Saturday]
ASSERT_SYSERROR(firstDay && 1 <= *firstDay && *firstDay <= 7);
const int weekDayStartSunday = *firstDay; //[1-Sunday, 7-Saturday]
const int weekDayStartMonday = (weekDayStartSunday - 2 + 7) % 7; //[0-Monday, 6-Sunday] 7 == 0 in Z_7
return static_cast<WeekDay>(weekDayStartMonday);
}
WeekDay zen::getFirstDayOfWeek()
{
static const WeekDay weekDay = []
{
try
{
return impl::getFirstDayOfWeekImpl(); //throw SysError
}
catch (const SysError& e)
{
throw std::runtime_error(std::string(__FILE__) + '[' + numberTo<std::string>(__LINE__) + "] Failed to get first day of the week." + "\n\n" +
utfTo<std::string>(e.toString()));
}
}();
return weekDay;
}