Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify and centralize time formatters #1457

Merged
merged 4 commits into from Aug 23, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions LiveSplit/LiveSplit.Core/LiveSplit.Core.csproj
Expand Up @@ -161,6 +161,8 @@
<Compile Include="Server\ScriptFactory.cs" />
<Compile Include="Server\CommandServer.cs" />
<Compile Include="TimeFormatters\AutomaticPrecisionTimeFormatter.cs" />
<Compile Include="TimeFormatters\NullFormat.cs" />
<Compile Include="TimeFormatters\GeneralTimeFormatter.cs" />
<Compile Include="TimeFormatters\PossibleTimeSaveFormatter.cs" />
<Compile Include="TimeFormatters\TimeFormat.cs" />
<Compile Include="TimeFormatters\TimeAccuracy.cs" />
Expand Down
36 changes: 3 additions & 33 deletions LiveSplit/LiveSplit.Core/TimeFormatters/DeltaTimeFormatter.cs
Expand Up @@ -3,44 +3,14 @@

namespace LiveSplit.TimeFormatters
{
public class DeltaTimeFormatter : ITimeFormatter
public class DeltaTimeFormatter : GeneralTimeFormatter
{
public TimeAccuracy Accuracy { get; set; }
public bool DropDecimals { get; set; }

public DeltaTimeFormatter()
public DeltaTimeFormatter() : base()
{
Accuracy = TimeAccuracy.Tenths;
DropDecimals = true;
}

public string Format(TimeSpan? time)
{
if (time.HasValue)
{
string minusString = "+";
var totalString = "";
if (time.Value < TimeSpan.Zero)
{
minusString = TimeFormatConstants.MINUS;
time = TimeSpan.Zero-time;
}
if (time.Value.TotalDays >= 1)
totalString = minusString + (int)(time.Value.TotalHours) + time.Value.ToString(@"\:mm\:ss\.ff", CultureInfo.InvariantCulture);
else if (time.Value.TotalHours >= 1)
totalString = minusString+time.Value.ToString(@"h\:mm\:ss\.ff", CultureInfo.InvariantCulture);
else if (time.Value.TotalMinutes >= 1)
totalString = minusString+time.Value.ToString(@"m\:ss\.ff", CultureInfo.InvariantCulture);
else
totalString = minusString+time.Value.ToString(@"s\.ff", CultureInfo.InvariantCulture);
if ((DropDecimals && time.Value.TotalMinutes >= 1) || Accuracy == TimeAccuracy.Seconds)
return totalString.Substring(0, totalString.Length - 3);
else if (Accuracy == TimeAccuracy.Tenths)
return totalString.Substring(0, totalString.Length - 1);
return totalString;
}

return TimeFormatConstants.DASH;
ShowPlus = true;
}
}
}
134 changes: 134 additions & 0 deletions LiveSplit/LiveSplit.Core/TimeFormatters/GeneralTimeFormatter.cs
@@ -0,0 +1,134 @@
using System;
using System.Globalization;

namespace LiveSplit.TimeFormatters {
public class GeneralTimeFormatter : ITimeFormatter {
static readonly private CultureInfo ic = CultureInfo.InvariantCulture;

public TimeAccuracy Accuracy { get; set; }

//TODO: add enum: BigMinutes (for issue #1336)
//TODO: add enum: SingleMinutes (for RegularTimeFormatter, e.g. 0:12)
public TimeFormat TimeFormat { get; set; }

/// <summary>
/// How to display null times
/// </summary>
public NullFormat NullFormat { get; set; }

/// <summary>
/// If true, for example show "1d 23:59:10" instead of "47:59:10". For durations of 24 hours or more,
/// </summary>
public bool ShowDays { get; set; }

/// <summary>
/// If true, include a "+" for positive times (excluding zero)
/// </summary>
public bool ShowPlus { get; set; }

/// <summary>
/// If true, don't display decimals if absolute time is 1 minute or more
/// </summary>
public bool DropDecimals { get; set; }

/// <summary>
/// If true, don't display trailing zero demical places
/// </summary>
public bool AutomaticPrecision { get; set; } = false;

public GeneralTimeFormatter()
{
TimeFormat = TimeFormat.Seconds;
NullFormat = NullFormat.Dash;
}

public string Format(TimeSpan? timeNullable)
{
bool isNull = (!timeNullable.HasValue);
if (isNull) {
if (NullFormat == NullFormat.Dash) {
return TimeFormatConstants.DASH;

} else if (NullFormat == NullFormat.ZeroWithAccuracy) {
return ZeroWithAccuracy();

} else if (NullFormat == NullFormat.ZeroDotZeroZero) {
return "0.00";

} else if (NullFormat == NullFormat.ZeroValue || NullFormat == NullFormat.Dashes) {
timeNullable = TimeSpan.Zero;
}
}

TimeSpan time = timeNullable.Value;

string minusString;
if (time < TimeSpan.Zero)
{
minusString = TimeFormatConstants.MINUS;
time = -time;
}
else
{
minusString = (ShowPlus ? "+" : "");
}

string decimalFormat = "";
if (DropDecimals && time.TotalMinutes >= 1)
decimalFormat = "";
else if (Accuracy == TimeAccuracy.Seconds)
decimalFormat = "";
else if (Accuracy == TimeAccuracy.Tenths)
decimalFormat = @"\.f";
else if (Accuracy == TimeAccuracy.Hundredths)
decimalFormat = @"\.ff";

if (AutomaticPrecision)
decimalFormat.Replace('f', 'F');

string formatted;
if (time.TotalDays >= 1)
{
if (ShowDays)
formatted = minusString + time.ToString(@"d\d\ h\:mm\:ss" + decimalFormat, ic);
else
formatted = minusString + (int)time.TotalHours + time.ToString(@"\:mm\:ss" + decimalFormat, ic);
}
else if (TimeFormat == TimeFormat.TenHours)
{
formatted = minusString + time.ToString(@"hh\:mm\:ss" + decimalFormat, ic);
}
else if (time.TotalHours >= 1 || TimeFormat == TimeFormat.Hours)
{
formatted = minusString + time.ToString(@"h\:mm\:ss" + decimalFormat, ic);
}
else if (TimeFormat == TimeFormat.Minutes)
{
formatted = minusString + time.ToString(@"mm\:ss" + decimalFormat, ic);
}
else if (time.TotalMinutes >= 1 || TimeFormat == TimeFormat.SingleMinutes)
{
formatted = minusString + time.ToString(@"m\:ss" + decimalFormat, ic);
}
else
{
formatted = minusString + time.ToString(@"%s" + decimalFormat, ic);
}

if (isNull && NullFormat == NullFormat.Dashes)
formatted = formatted.Replace('0', '-');

return formatted;
}

private string ZeroWithAccuracy()
{
if (Accuracy == TimeAccuracy.Seconds)
return "0";
else if (Accuracy == TimeAccuracy.Tenths)
return "0.0";
else
return "0.00";
}
}
}
17 changes: 17 additions & 0 deletions LiveSplit/LiveSplit.Core/TimeFormatters/NullFormat.cs
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LiveSplit.TimeFormatters
{
public enum NullFormat
{
Dash, // "-"
ZeroWithAccuracy, // "0", "0.0" or "0.00" (dependant on TimeAccuracy; not affected by AutomaticPrecision nor TimeFormat)
ZeroDotZeroZero, // always "0.00" (used by ShortTimeFormatter)
ZeroValue, // format the null value the same as TimeSpan.Zero
Dashes // e.g. "-:--" (like ZeroValue but zeros replaced with dashes)
}
}
Expand Up @@ -2,27 +2,12 @@

namespace LiveSplit.TimeFormatters
{
public class PossibleTimeSaveFormatter : ITimeFormatter
public class PossibleTimeSaveFormatter : GeneralTimeFormatter
{
public TimeAccuracy Accuracy { get; set; }

public string Format(TimeSpan? time)
public PossibleTimeSaveFormatter() : base()
pengowray marked this conversation as resolved.
Show resolved Hide resolved
{
var formatter = new ShortTimeFormatter();
if (time == null)
return TimeFormatConstants.DASH;
else
{
var timeString = formatter.Format(time);
if (Accuracy == TimeAccuracy.Hundredths)
return timeString;
else if (Accuracy == TimeAccuracy.Tenths)
return timeString.Substring(0, timeString.Length - 1);
else
return timeString.Substring(0, timeString.Length - 3);

}

Accuracy = TimeAccuracy.Hundredths;
NullFormat = NullFormat.Dash;
}
}
}
42 changes: 3 additions & 39 deletions LiveSplit/LiveSplit.Core/TimeFormatters/RegularTimeFormatter.cs
Expand Up @@ -3,49 +3,13 @@

namespace LiveSplit.TimeFormatters
{
public class RegularTimeFormatter : ITimeFormatter
public class RegularTimeFormatter : GeneralTimeFormatter
{
public TimeAccuracy Accuracy { get; set; }

public RegularTimeFormatter(TimeAccuracy accuracy = TimeAccuracy.Seconds)
{
Accuracy = accuracy;
}

public string Format(TimeSpan? time)
{
if (time.HasValue)
{
if (Accuracy == TimeAccuracy.Hundredths)
{
if (time.Value.TotalDays >= 1)
return (int)(time.Value.TotalHours) + time.Value.ToString(@"\:mm\:ss\.ff", CultureInfo.InvariantCulture);
else if (time.Value.TotalHours >= 1)
return time.Value.ToString(@"h\:mm\:ss\.ff", CultureInfo.InvariantCulture);
return time.Value.ToString(@"m\:ss\.ff", CultureInfo.InvariantCulture);
}
else if (Accuracy == TimeAccuracy.Seconds)
{
if (time.Value.TotalDays >= 1)
return (int)(time.Value.TotalHours) + time.Value.ToString(@"\:mm\:ss", CultureInfo.InvariantCulture);
else if (time.Value.TotalHours >= 1)
return time.Value.ToString(@"h\:mm\:ss", CultureInfo.InvariantCulture);
return time.Value.ToString(@"m\:ss", CultureInfo.InvariantCulture);
}
else
{
if (time.Value.TotalDays >= 1)
return (int)(time.Value.TotalHours) + time.Value.ToString(@"\:mm\:ss\.f", CultureInfo.InvariantCulture);
else if (time.Value.TotalHours >= 1)
return time.Value.ToString(@"h\:mm\:ss\.f", CultureInfo.InvariantCulture);
return time.Value.ToString(@"m\:ss\.f", CultureInfo.InvariantCulture);
}
}
if (Accuracy == TimeAccuracy.Seconds)
return "0";
if (Accuracy == TimeAccuracy.Tenths)
return "0.0";
return "0.00";
NullFormat = NullFormat.ZeroWithAccuracy;
TimeFormat = TimeFormat.SingleMinutes;
}
}
}
62 changes: 12 additions & 50 deletions LiveSplit/LiveSplit.Core/TimeFormatters/ShortTimeFormatter.cs
Expand Up @@ -3,62 +3,24 @@

namespace LiveSplit.TimeFormatters
{
public class ShortTimeFormatter : ITimeFormatter
public class ShortTimeFormatter : GeneralTimeFormatter // ITimeFormatter
{
public string Format(TimeSpan? time)

public ShortTimeFormatter() : base()
{
if (time.HasValue)
{
string minusString = "";
if (time.Value < TimeSpan.Zero)
{
minusString = TimeFormatConstants.MINUS;
time = TimeSpan.Zero - time;
}
if (time.Value.TotalDays >= 1)
return minusString + (int)(time.Value.TotalHours) + time.Value.ToString(@"\:mm\:ss\.ff", CultureInfo.InvariantCulture);
else if (time.Value.TotalHours >= 1)
return minusString+time.Value.ToString(@"h\:mm\:ss\.ff", CultureInfo.InvariantCulture);
else if (time.Value.Minutes >= 1)
return minusString+time.Value.ToString(@"m\:ss\.ff", CultureInfo.InvariantCulture);
return minusString+time.Value.ToString(@"s\.ff", CultureInfo.InvariantCulture);
}
return "0.00";
Accuracy = TimeAccuracy.Hundredths;
NullFormat = NullFormat.ZeroWithAccuracy;
}

public string Format(TimeSpan? time, TimeFormat format)
{
if (time.HasValue)
{
string minusString = "";
if (time.Value < TimeSpan.Zero)
{
minusString = TimeFormatConstants.MINUS;
time = TimeSpan.Zero - time;
}
if (time.Value.TotalDays >= 1)
{
return minusString + (int)(time.Value.TotalHours) + time.Value.ToString(@"\:mm\:ss\.ff", CultureInfo.InvariantCulture);
}
else if (format == TimeFormat.TenHours)
{
return minusString + time.Value.ToString(@"hh\:mm\:ss\.ff", CultureInfo.InvariantCulture);
}
else if (time.Value.TotalHours >= 1 || format == TimeFormat.Hours)
{
return minusString + time.Value.ToString(@"h\:mm\:ss\.ff", CultureInfo.InvariantCulture);
}
else if (format == TimeFormat.Minutes)
{
return minusString + time.Value.ToString(@"mm\:ss\.ff", CultureInfo.InvariantCulture);
}
else if (time.Value.Minutes >= 1)
{
return minusString + time.Value.ToString(@"m\:ss\.ff", CultureInfo.InvariantCulture);
}
return minusString + time.Value.ToString(@"s\.ff", CultureInfo.InvariantCulture);
}
return "0.00";
var formatRequest = new GeneralTimeFormatter {
Accuracy = TimeAccuracy.Hundredths,
NullFormat = NullFormat.ZeroWithAccuracy,
TimeFormat = format
};

return formatRequest.Format(time);
}
}
}
2 changes: 1 addition & 1 deletion LiveSplit/LiveSplit.Core/TimeFormatters/TimeFormat.cs
Expand Up @@ -2,6 +2,6 @@
{
public enum TimeFormat
{
TenHours, Hours, Minutes, Seconds
TenHours, Hours, Minutes, Seconds, SingleMinutes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to unify the Time Formatting as much as possible across the original LiveSplit and LiveSplit One / livesplit-core. So I'll likely apply the changes in this branch to that codebase in some form. One thing I'd somewhat like backported, which you already slightly started here, is all the other variants as well: https://github.com/LiveSplit/livesplit-core/blob/master/src/timing/formatter/digits_format.rs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, cool. Yeah, backporting that that could be done.

Copy link
Contributor Author

@pengowray pengowray Aug 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed "SingleMinutes" again from TimeFormat and added a whole new DigitsFormat enum which includes "SingleDigitMinutes" and everything else which is in core's. It's basically a replacement for TimeFormat but TimeFormat is still there there as there are too many components / settings files / etc that would need to be updated or might potentially break if it was changed to match core's enum. So TimeFormat is still an option for formatting, but it's marked as deprecated, and using it with GeneralTimeFormatter automatically converts over to the new core-like DigitsFormat.

Also added Milliseconds accuracy (0.000). Also a bunch of unit tests for formatting with this new TimeAccuracy, as well as all the new DigitsFormat values.

Also discovered I hadn't tested "AutomaticPrecision" and my implementation was broken, so copied back some original code for handling that.

Note you can choose between hours or days as the largest unit to include, but still not minutes (#1336) or seconds. Is there anything like that in core?

}
}