mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-03-26 13:20:18 +00:00
220 lines
8.4 KiB
C#
220 lines
8.4 KiB
C#
|
using System;
|
|||
|
using System.Globalization;
|
|||
|
using System.Linq;
|
|||
|
|
|||
|
namespace Ryujinx.Ui.Common.Helper
|
|||
|
{
|
|||
|
public static class ValueFormatUtils
|
|||
|
{
|
|||
|
private static readonly string[] _fileSizeUnitStrings =
|
|||
|
{
|
|||
|
"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", // Base 10 units, used for formatting and parsing
|
|||
|
"KB", "MB", "GB", "TB", "PB", "EB", // Base 2 units, used for parsing legacy values
|
|||
|
};
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Used by <see cref="FormatFileSize"/>.
|
|||
|
/// </summary>
|
|||
|
public enum FileSizeUnits
|
|||
|
{
|
|||
|
Auto = -1,
|
|||
|
Bytes = 0,
|
|||
|
Kibibytes = 1,
|
|||
|
Mebibytes = 2,
|
|||
|
Gibibytes = 3,
|
|||
|
Tebibytes = 4,
|
|||
|
Pebibytes = 5,
|
|||
|
Exbibytes = 6,
|
|||
|
Kilobytes = 7,
|
|||
|
Megabytes = 8,
|
|||
|
Gigabytes = 9,
|
|||
|
Terabytes = 10,
|
|||
|
Petabytes = 11,
|
|||
|
Exabytes = 12,
|
|||
|
}
|
|||
|
|
|||
|
private const double SizeBase10 = 1000;
|
|||
|
private const double SizeBase2 = 1024;
|
|||
|
private const int UnitEBIndex = 6;
|
|||
|
|
|||
|
#region Value formatters
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a human-readable string from a <see cref="TimeSpan"/>.
|
|||
|
/// </summary>
|
|||
|
/// <param name="timeSpan">The <see cref="TimeSpan"/> to be formatted.</param>
|
|||
|
/// <returns>A formatted string that can be displayed in the UI.</returns>
|
|||
|
public static string FormatTimeSpan(TimeSpan? timeSpan)
|
|||
|
{
|
|||
|
if (!timeSpan.HasValue || timeSpan.Value.TotalSeconds < 1)
|
|||
|
{
|
|||
|
// Game was never played
|
|||
|
return TimeSpan.Zero.ToString("c", CultureInfo.InvariantCulture);
|
|||
|
}
|
|||
|
|
|||
|
if (timeSpan.Value.TotalDays < 1)
|
|||
|
{
|
|||
|
// Game was played for less than a day
|
|||
|
return timeSpan.Value.ToString("c", CultureInfo.InvariantCulture);
|
|||
|
}
|
|||
|
|
|||
|
// Game was played for more than a day
|
|||
|
TimeSpan onlyTime = timeSpan.Value.Subtract(TimeSpan.FromDays(timeSpan.Value.Days));
|
|||
|
string onlyTimeString = onlyTime.ToString("c", CultureInfo.InvariantCulture);
|
|||
|
|
|||
|
return $"{timeSpan.Value.Days}d, {onlyTimeString}";
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a human-readable string from a <see cref="DateTime"/>.
|
|||
|
/// </summary>
|
|||
|
/// <param name="utcDateTime">The <see cref="DateTime"/> to be formatted. This is expected to be UTC-based.</param>
|
|||
|
/// <param name="culture">The <see cref="CultureInfo"/> that's used in formatting. Defaults to <see cref="CultureInfo.CurrentCulture"/>.</param>
|
|||
|
/// <returns>A formatted string that can be displayed in the UI.</returns>
|
|||
|
public static string FormatDateTime(DateTime? utcDateTime, CultureInfo culture = null)
|
|||
|
{
|
|||
|
culture ??= CultureInfo.CurrentCulture;
|
|||
|
|
|||
|
if (!utcDateTime.HasValue)
|
|||
|
{
|
|||
|
// In the Avalonia UI, this is turned into a localized version of "Never" by LocalizedNeverConverter.
|
|||
|
return "Never";
|
|||
|
}
|
|||
|
|
|||
|
return utcDateTime.Value.ToLocalTime().ToString(culture);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Creates a human-readable file size string.
|
|||
|
/// </summary>
|
|||
|
/// <param name="size">The file size in bytes.</param>
|
|||
|
/// <param name="forceUnit">Formats the passed size value as this unit, bypassing the automatic unit choice.</param>
|
|||
|
/// <returns>A human-readable file size string.</returns>
|
|||
|
public static string FormatFileSize(long size, FileSizeUnits forceUnit = FileSizeUnits.Auto)
|
|||
|
{
|
|||
|
if (size <= 0)
|
|||
|
{
|
|||
|
return $"0 {_fileSizeUnitStrings[0]}";
|
|||
|
}
|
|||
|
|
|||
|
int unitIndex = (int)forceUnit;
|
|||
|
if (forceUnit == FileSizeUnits.Auto)
|
|||
|
{
|
|||
|
unitIndex = Convert.ToInt32(Math.Floor(Math.Log(size, SizeBase10)));
|
|||
|
|
|||
|
// Apply an upper bound so that exabytes are the biggest unit used when formatting.
|
|||
|
if (unitIndex > UnitEBIndex)
|
|||
|
{
|
|||
|
unitIndex = UnitEBIndex;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
double sizeRounded;
|
|||
|
|
|||
|
if (unitIndex > UnitEBIndex)
|
|||
|
{
|
|||
|
sizeRounded = Math.Round(size / Math.Pow(SizeBase10, unitIndex - UnitEBIndex), 1);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
sizeRounded = Math.Round(size / Math.Pow(SizeBase2, unitIndex), 1);
|
|||
|
}
|
|||
|
|
|||
|
string sizeFormatted = sizeRounded.ToString(CultureInfo.InvariantCulture);
|
|||
|
|
|||
|
return $"{sizeFormatted} {_fileSizeUnitStrings[unitIndex]}";
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Value parsers
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Parses a string generated by <see cref="FormatTimeSpan"/> and returns the original <see cref="TimeSpan"/>.
|
|||
|
/// </summary>
|
|||
|
/// <param name="timeSpanString">A string representing a <see cref="TimeSpan"/>.</param>
|
|||
|
/// <returns>A <see cref="TimeSpan"/> object. If the input string couldn't been parsed, <see cref="TimeSpan.Zero"/> is returned.</returns>
|
|||
|
public static TimeSpan ParseTimeSpan(string timeSpanString)
|
|||
|
{
|
|||
|
TimeSpan returnTimeSpan = TimeSpan.Zero;
|
|||
|
|
|||
|
// An input string can either look like "01:23:45" or "1d, 01:23:45" if the timespan represents a duration of more than a day.
|
|||
|
// Here, we split the input string to check if it's the former or the latter.
|
|||
|
var valueSplit = timeSpanString.Split(", ");
|
|||
|
if (valueSplit.Length > 1)
|
|||
|
{
|
|||
|
var dayPart = valueSplit[0].Split("d")[0];
|
|||
|
if (int.TryParse(dayPart, out int days))
|
|||
|
{
|
|||
|
returnTimeSpan = returnTimeSpan.Add(TimeSpan.FromDays(days));
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (TimeSpan.TryParse(valueSplit.Last(), out TimeSpan parsedTimeSpan))
|
|||
|
{
|
|||
|
returnTimeSpan = returnTimeSpan.Add(parsedTimeSpan);
|
|||
|
}
|
|||
|
|
|||
|
return returnTimeSpan;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Parses a string generated by <see cref="FormatDateTime"/> and returns the original <see cref="DateTime"/>.
|
|||
|
/// </summary>
|
|||
|
/// <param name="dateTimeString">The string representing a <see cref="DateTime"/>.</param>
|
|||
|
/// <returns>A <see cref="DateTime"/> object. If the input string couldn't be parsed, <see cref="DateTime.UnixEpoch"/> is returned.</returns>
|
|||
|
public static DateTime ParseDateTime(string dateTimeString)
|
|||
|
{
|
|||
|
if (!DateTime.TryParse(dateTimeString, CultureInfo.CurrentCulture, out DateTime parsedDateTime))
|
|||
|
{
|
|||
|
// Games that were never played are supposed to appear before the oldest played games in the list,
|
|||
|
// so returning DateTime.UnixEpoch here makes sense.
|
|||
|
return DateTime.UnixEpoch;
|
|||
|
}
|
|||
|
|
|||
|
return parsedDateTime;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Parses a string generated by <see cref="FormatFileSize"/> and returns a <see cref="long"/> representing a number of bytes.
|
|||
|
/// </summary>
|
|||
|
/// <param name="sizeString">A string representing a file size formatted with <see cref="FormatFileSize"/>.</param>
|
|||
|
/// <returns>A <see cref="long"/> representing a number of bytes.</returns>
|
|||
|
public static long ParseFileSize(string sizeString)
|
|||
|
{
|
|||
|
// Enumerating over the units backwards because otherwise, sizeString.EndsWith("B") would exit the loop in the first iteration.
|
|||
|
for (int i = _fileSizeUnitStrings.Length - 1; i >= 0; i--)
|
|||
|
{
|
|||
|
string unit = _fileSizeUnitStrings[i];
|
|||
|
if (!sizeString.EndsWith(unit))
|
|||
|
{
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
string numberString = sizeString.Split(" ")[0];
|
|||
|
if (!double.TryParse(numberString, CultureInfo.InvariantCulture, out double number))
|
|||
|
{
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
double sizeBase = SizeBase2;
|
|||
|
|
|||
|
// If the unit index is one that points to a base 10 unit in the FileSizeUnitStrings array, subtract 6 to arrive at a usable power value.
|
|||
|
if (i > UnitEBIndex)
|
|||
|
{
|
|||
|
i -= UnitEBIndex;
|
|||
|
sizeBase = SizeBase10;
|
|||
|
}
|
|||
|
|
|||
|
number *= Math.Pow(sizeBase, i);
|
|||
|
|
|||
|
return Convert.ToInt64(number);
|
|||
|
}
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|