mirror of
https://github.com/oliverbooth/X10D
synced 2024-11-09 23:45:42 +00:00
Add ReadOnlySpan<char> overload for TimeSpanParser
Also tidies up the code here to reduce complexity
This commit is contained in:
parent
8ff3447b85
commit
35591b05e2
@ -35,6 +35,7 @@
|
||||
- X10D: Added `Quaternion.ToVector3()`
|
||||
- X10D: Added `ReadOnlySpan<char>.CountSubstring(char)`
|
||||
- X10D: Added `ReadOnlySpan<char>.CountSubstring(ReadOnlySpan<char>[, StringComparison])`
|
||||
- X10D: Added `ReadOnlySpan<char>.ToTimeSpan()`
|
||||
- X10D: Added `ReadOnlySpan<T>.Split(T)`
|
||||
- X10D: Added `ReadOnlySpan<T>.Split(ReadOnlySpan<T>)`
|
||||
- X10D: Added `RoundUpToPowerOf2()` for built-in integer types
|
||||
@ -47,6 +48,7 @@
|
||||
- X10D: Added `Span<T>.Split(Span<T>)`
|
||||
- X10D: Added `string.CountSubstring(char)`
|
||||
- X10D: Added `string.CountSubstring(string[, StringComparison])`
|
||||
- X10D: Added `TimeSpan.TryParse(ReadOnlySpan<char>, out TimeSpan)`
|
||||
- X10D: Added `Quaternion.Multiply(Vector3)` - this functions as an equivalent to Unity's `Quaternion * Vector3` operator
|
||||
- X10D: Added `Vector2.Deconstruct()`
|
||||
- X10D: Added `Vector2.IsOnLine(LineF)`, `Vector2.IsOnLine(PointF, PointF)`, and `Vector2.IsOnLine(Vector2, Vector2)`
|
||||
|
31
X10D.Tests/src/Time/CharSpanTests.cs
Normal file
31
X10D.Tests/src/Time/CharSpanTests.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using X10D.Time;
|
||||
|
||||
namespace X10D.Tests.Time;
|
||||
|
||||
[TestClass]
|
||||
public class CharSpanTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ToTimeSpan_ShouldReturnCorrectTimeSpan_GivenSpanOfCharacters()
|
||||
{
|
||||
ReadOnlySpan<char> value = "1y 1mo 1w 1d 1h 1m 1s 1ms".AsSpan();
|
||||
|
||||
TimeSpan expected = TimeSpan.FromMilliseconds(1);
|
||||
expected += TimeSpan.FromSeconds(1);
|
||||
expected += TimeSpan.FromMinutes(1);
|
||||
expected += TimeSpan.FromHours(1);
|
||||
expected += TimeSpan.FromDays(1);
|
||||
expected += TimeSpan.FromDays(7);
|
||||
expected += TimeSpan.FromDays(30);
|
||||
expected += TimeSpan.FromDays(365);
|
||||
|
||||
Assert.AreEqual(expected, value.ToTimeSpan());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ToTimeSpan_ShouldReturnZero_GivenInvalidSpanOfCharacters()
|
||||
{
|
||||
Assert.AreEqual(TimeSpan.Zero, "Hello World".AsSpan().ToTimeSpan());
|
||||
}
|
||||
}
|
69
X10D/src/Time/CharSpanExtensions.cs
Normal file
69
X10D/src/Time/CharSpanExtensions.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace X10D.Time;
|
||||
|
||||
/// <summary>
|
||||
/// Time-related extension methods for <see cref="ReadOnlySpan{T}" /> of <see cref="char" />.
|
||||
/// </summary>
|
||||
public static class CharSpanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses this span of characters as a shorthand time span (e.g. 3w 2d 1h) and converts it to an instance of
|
||||
/// <see cref="TimeSpan" />.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The input span of characters. Floating point is not supported, but integers with the following units are supported:
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Suffix</term>
|
||||
/// <description>Meaning</description>
|
||||
/// </listheader>
|
||||
///
|
||||
/// <item>
|
||||
/// <term>ms</term>
|
||||
/// <description>Milliseconds</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>s</term>
|
||||
/// <description>Seconds</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>m</term>
|
||||
/// <description>Minutes</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>h</term>
|
||||
/// <description>Hours</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>d</term>
|
||||
/// <description>Days</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>w</term>
|
||||
/// <description>Weeks</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>mo</term>
|
||||
/// <description>Months</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>y</term>
|
||||
/// <description>Years</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </param>
|
||||
/// <returns>A new instance of <see cref="TimeSpan" />.</returns>
|
||||
[Pure]
|
||||
#if NETSTANDARD2_1
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
#else
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
#endif
|
||||
public static TimeSpan ToTimeSpan(this ReadOnlySpan<char> input)
|
||||
{
|
||||
return TimeSpanParser.TryParse(input, out TimeSpan result) ? result : default;
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ using System.Runtime.CompilerServices;
|
||||
namespace X10D.Time;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="string" />.
|
||||
/// Time-related extension methods for <see cref="string" />.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
@ -12,7 +12,7 @@ public static class StringExtensions
|
||||
/// Parses a shorthand time span string (e.g. 3w 2d 1h) and converts it to an instance of <see cref="TimeSpan" />.
|
||||
/// </summary>
|
||||
/// <param name="input">
|
||||
/// The input string. Floating point is not supported, but range the following units are supported:
|
||||
/// The input string. Floating point is not supported, but integers with the following units are supported:
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
|
@ -7,6 +7,78 @@ namespace X10D.Time;
|
||||
/// </summary>
|
||||
public static class TimeSpanParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to parses a shorthand time span (e.g. 3w 2d 1h) as a span of characters, converting it to an instance of
|
||||
/// <see cref="TimeSpan" /> which represents that duration of time.
|
||||
/// </summary>
|
||||
/// <param name="value">
|
||||
/// The input span of characters. Floating point is not supported, but range the following units are supported:
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Suffix</term>
|
||||
/// <description>Meaning</description>
|
||||
/// </listheader>
|
||||
///
|
||||
/// <item>
|
||||
/// <term>ms</term>
|
||||
/// <description>Milliseconds</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>s</term>
|
||||
/// <description>Seconds</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>m</term>
|
||||
/// <description>Minutes</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>h</term>
|
||||
/// <description>Hours</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>d</term>
|
||||
/// <description>Days</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>w</term>
|
||||
/// <description>Weeks</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>mo</term>
|
||||
/// <description>Months</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>y</term>
|
||||
/// <description>Years</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </param>
|
||||
/// <param name="result">When this method returns, contains the parsed result.</param>
|
||||
/// <returns><see langword="true" /> if the parse was successful, <see langword="false" /> otherwise.</returns>
|
||||
public static bool TryParse(ReadOnlySpan<char> value, out TimeSpan result)
|
||||
{
|
||||
result = TimeSpan.Zero;
|
||||
|
||||
if (value.Length == 0 || value.IsWhiteSpace())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var unitValue = 0;
|
||||
|
||||
for (var index = 0; index < value.Length; index++)
|
||||
{
|
||||
char current = value[index];
|
||||
if (!HandleCharacter(value, ref result, current, ref unitValue, ref index))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to parses a shorthand time span string (e.g. 3w 2d 1h), converting it to an instance of
|
||||
/// <see cref="TimeSpan" /> which represents that duration of time.
|
||||
@ -59,77 +131,95 @@ public static class TimeSpanParser
|
||||
public static bool TryParse([NotNullWhen(true)] string? value, out TimeSpan result)
|
||||
{
|
||||
result = TimeSpan.Zero;
|
||||
return !string.IsNullOrWhiteSpace(value) && TryParse(value.AsSpan(), out result);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
private static bool HandleCharacter(
|
||||
ReadOnlySpan<char> value,
|
||||
ref TimeSpan result,
|
||||
char current,
|
||||
ref int unitValue,
|
||||
ref int index
|
||||
)
|
||||
{
|
||||
char next = index < value.Length - 1 ? value[index + 1] : '\0';
|
||||
if (HandleSpecial(ref unitValue, current))
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var unitValue = 0;
|
||||
|
||||
for (var index = 0; index < value.Length; index++)
|
||||
if (HandleSuffix(ref index, ref result, ref unitValue, current, next))
|
||||
{
|
||||
char current = value[index];
|
||||
switch (current)
|
||||
{
|
||||
case var digitChar when char.IsDigit(digitChar):
|
||||
var digit = (int)char.GetNumericValue(digitChar);
|
||||
unitValue = unitValue * 10 + digit;
|
||||
break;
|
||||
|
||||
case 'y':
|
||||
result += TimeSpan.FromDays(unitValue * 365);
|
||||
unitValue = 0;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
if (index < value.Length - 1 && value[index + 1] == 'o')
|
||||
{
|
||||
index++;
|
||||
result += TimeSpan.FromDays(unitValue * 30);
|
||||
}
|
||||
else if (index < value.Length - 1 && value[index + 1] == 's')
|
||||
{
|
||||
index++;
|
||||
result += TimeSpan.FromMilliseconds(unitValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
result += TimeSpan.FromMinutes(unitValue);
|
||||
}
|
||||
|
||||
unitValue = 0;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
result += TimeSpan.FromDays(unitValue * 7);
|
||||
unitValue = 0;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
result += TimeSpan.FromDays(unitValue);
|
||||
unitValue = 0;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
result += TimeSpan.FromHours(unitValue);
|
||||
unitValue = 0;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
result += TimeSpan.FromSeconds(unitValue);
|
||||
unitValue = 0;
|
||||
break;
|
||||
|
||||
case var space when char.IsWhiteSpace(space):
|
||||
break;
|
||||
|
||||
default:
|
||||
result = TimeSpan.Zero;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
result = TimeSpan.Zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HandleSuffix(ref int index, ref TimeSpan result, ref int unitValue, char current, char next)
|
||||
{
|
||||
switch (current)
|
||||
{
|
||||
case 'm' when next == 'o':
|
||||
index++;
|
||||
result += TimeSpan.FromDays(unitValue * 30);
|
||||
unitValue = 0;
|
||||
return true;
|
||||
|
||||
case 'm' when next == 's':
|
||||
index++;
|
||||
result += TimeSpan.FromMilliseconds(unitValue);
|
||||
unitValue = 0;
|
||||
return true;
|
||||
|
||||
case 'm':
|
||||
result += TimeSpan.FromMinutes(unitValue);
|
||||
unitValue = 0;
|
||||
return true;
|
||||
|
||||
case 'y':
|
||||
result += TimeSpan.FromDays(unitValue * 365);
|
||||
unitValue = 0;
|
||||
return true;
|
||||
|
||||
case 'w':
|
||||
result += TimeSpan.FromDays(unitValue * 7);
|
||||
unitValue = 0;
|
||||
return true;
|
||||
|
||||
case 'd':
|
||||
result += TimeSpan.FromDays(unitValue);
|
||||
unitValue = 0;
|
||||
return true;
|
||||
|
||||
case 'h':
|
||||
result += TimeSpan.FromHours(unitValue);
|
||||
unitValue = 0;
|
||||
return true;
|
||||
|
||||
case 's':
|
||||
result += TimeSpan.FromSeconds(unitValue);
|
||||
unitValue = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HandleSpecial(ref int unitValue, char current)
|
||||
{
|
||||
switch (current)
|
||||
{
|
||||
case var _ when char.IsDigit(current):
|
||||
var digit = (int)char.GetNumericValue(current);
|
||||
unitValue = unitValue * 10 + digit;
|
||||
return true;
|
||||
|
||||
case var _ when char.IsWhiteSpace(current):
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user