2023-11-04 09:01:18 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System ;
2023-11-20 20:27:24 +08:00
using System.Diagnostics.CodeAnalysis ;
2023-11-04 09:01:18 +08:00
using System.Text.RegularExpressions ;
2023-11-07 08:36:58 +08:00
namespace osu.Game.Rulesets.Edit
2023-11-04 09:01:18 +08:00
{
public static class EditorTimestampParser
{
2024-06-18 19:00:43 +08:00
/// <summary>
/// Used for parsing in contexts where we don't want e.g. normal times of day to be parsed as timestamps (e.g. chat)
/// Original osu-web regex: https://github.com/ppy/osu-web/blob/3b1698639244cfdaf0b41c68bfd651ea729ec2e3/resources/js/utils/beatmapset-discussion-helper.ts#L78
/// </summary>
/// <example>
/// 00:00:000 (...) - test
/// </example>
public static readonly Regex TIME_REGEX_STRICT = new Regex ( @"\b(((?<minutes>\d{2,}):(?<seconds>[0-5]\d)[:.](?<milliseconds>\d{3}))(?<selection>\s\([^)]+\))?)" , RegexOptions . Compiled ) ;
/// <summary>
/// Used for editor-specific context wherein we want to try as hard as we can to process user input as a timestamp.
/// </summary>
/// <example>
/// <list type="bullet">
/// <item>1 - parses to 01:00:000</item>
/// <item>1:2 - parses to 01:02:000</item>
/// <item>1:02 - parses to 01:02:000</item>
/// <item>1:92 - does not parse</item>
/// <item>1:02:3 - parses to 01:02:003</item>
/// <item>1:02:300 - parses to 01:02:300</item>
/// <item>1:02:300 (1,2,3) - parses to 01:02:300 with selection</item>
/// </list>
/// </example>
2024-06-18 22:46:47 +08:00
private static readonly Regex time_regex_lenient = new Regex ( @"^(((?<minutes>\d{1,3}):(?<seconds>([0-5]?\d))([:.](?<milliseconds>\d{0,3}))?)(?<selection>\s\([^)]+\))?)$" , RegexOptions . Compiled ) ;
2023-11-04 09:01:18 +08:00
2023-11-20 20:27:24 +08:00
public static bool TryParse ( string timestamp , [ NotNullWhen ( true ) ] out TimeSpan ? parsedTime , out string? parsedSelection )
2023-11-04 09:01:18 +08:00
{
2024-06-18 22:46:47 +08:00
if ( double . TryParse ( timestamp , out double msec ) )
{
parsedTime = TimeSpan . FromMilliseconds ( msec ) ;
parsedSelection = null ;
return true ;
}
2024-06-18 19:00:43 +08:00
Match match = time_regex_lenient . Match ( timestamp ) ;
2023-11-12 22:09:15 +08:00
2023-11-20 20:27:24 +08:00
if ( ! match . Success )
{
parsedTime = null ;
parsedSelection = null ;
return false ;
}
2024-06-18 19:00:43 +08:00
int timeMin , timeSec , timeMsec ;
2023-11-20 20:27:24 +08:00
2024-06-18 19:00:43 +08:00
int . TryParse ( match . Groups [ @"minutes" ] . Value , out timeMin ) ;
int . TryParse ( match . Groups [ @"seconds" ] . Value , out timeSec ) ;
int . TryParse ( match . Groups [ @"milliseconds" ] . Value , out timeMsec ) ;
2023-11-20 20:27:24 +08:00
// somewhat sane limit for timestamp duration (10 hours).
2024-06-18 19:00:43 +08:00
if ( timeMin > = 600 )
2023-11-20 20:27:24 +08:00
{
parsedTime = null ;
parsedSelection = null ;
return false ;
}
parsedTime = TimeSpan . FromMinutes ( timeMin ) + TimeSpan . FromSeconds ( timeSec ) + TimeSpan . FromMilliseconds ( timeMsec ) ;
parsedSelection = match . Groups [ @"selection" ] . Value . Trim ( ) ;
2024-06-18 19:00:43 +08:00
parsedSelection = ! string . IsNullOrEmpty ( parsedSelection ) ? parsedSelection [ 1. . ^ 1 ] : null ;
2023-11-20 20:27:24 +08:00
return true ;
2023-11-04 09:01:18 +08:00
}
}
}