mirror of
https://github.com/oliverbooth/X10D
synced 2024-11-22 23:58:48 +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 `Quaternion.ToVector3()`
|
||||||
- X10D: Added `ReadOnlySpan<char>.CountSubstring(char)`
|
- X10D: Added `ReadOnlySpan<char>.CountSubstring(char)`
|
||||||
- X10D: Added `ReadOnlySpan<char>.CountSubstring(ReadOnlySpan<char>[, StringComparison])`
|
- X10D: Added `ReadOnlySpan<char>.CountSubstring(ReadOnlySpan<char>[, StringComparison])`
|
||||||
|
- X10D: Added `ReadOnlySpan<char>.ToTimeSpan()`
|
||||||
- X10D: Added `ReadOnlySpan<T>.Split(T)`
|
- X10D: Added `ReadOnlySpan<T>.Split(T)`
|
||||||
- X10D: Added `ReadOnlySpan<T>.Split(ReadOnlySpan<T>)`
|
- X10D: Added `ReadOnlySpan<T>.Split(ReadOnlySpan<T>)`
|
||||||
- X10D: Added `RoundUpToPowerOf2()` for built-in integer types
|
- X10D: Added `RoundUpToPowerOf2()` for built-in integer types
|
||||||
@ -47,6 +48,7 @@
|
|||||||
- X10D: Added `Span<T>.Split(Span<T>)`
|
- X10D: Added `Span<T>.Split(Span<T>)`
|
||||||
- X10D: Added `string.CountSubstring(char)`
|
- X10D: Added `string.CountSubstring(char)`
|
||||||
- X10D: Added `string.CountSubstring(string[, StringComparison])`
|
- 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 `Quaternion.Multiply(Vector3)` - this functions as an equivalent to Unity's `Quaternion * Vector3` operator
|
||||||
- X10D: Added `Vector2.Deconstruct()`
|
- X10D: Added `Vector2.Deconstruct()`
|
||||||
- X10D: Added `Vector2.IsOnLine(LineF)`, `Vector2.IsOnLine(PointF, PointF)`, and `Vector2.IsOnLine(Vector2, Vector2)`
|
- 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;
|
namespace X10D.Time;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extension methods for <see cref="string" />.
|
/// Time-related extension methods for <see cref="string" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class StringExtensions
|
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" />.
|
/// Parses a shorthand time span string (e.g. 3w 2d 1h) and converts it to an instance of <see cref="TimeSpan" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">
|
/// <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">
|
/// <list type="table">
|
||||||
/// <listheader>
|
/// <listheader>
|
||||||
|
@ -7,6 +7,78 @@ namespace X10D.Time;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class TimeSpanParser
|
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>
|
/// <summary>
|
||||||
/// Attempts to parses a shorthand time span string (e.g. 3w 2d 1h), converting it to an instance of
|
/// 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.
|
/// <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)
|
public static bool TryParse([NotNullWhen(true)] string? value, out TimeSpan result)
|
||||||
{
|
{
|
||||||
result = TimeSpan.Zero;
|
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;
|
if (HandleSuffix(ref index, ref result, ref unitValue, current, next))
|
||||||
|
|
||||||
for (var index = 0; index < value.Length; index++)
|
|
||||||
{
|
{
|
||||||
char current = value[index];
|
return true;
|
||||||
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;
|
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