TimeSpan.ParseExact 有超过 59 秒的解决方法吗?

Any workaround to TimeSpan.ParseExact with more than 59 seconds?

我正在开发一个跟踪时间的应用程序,用户必须能够根据需要设置应用程序时间格式,例如:

"ss:fff" (seconds:miliseconds)
"ss\s\. fff mils\." (seconds s. miliseconds mils.)
"dd:hh:mm" (days:hours:minutes)
etc...

我存储的时间尽可能长,所以通过简单的 TimeSpan 格式,我可以用用户配置的格式显示它们,简单且实际工作。

当我开始实现“手动”时间添加时出现了问题(用户在 TextBox 中键入一个新时间,它以配置的时间格式添加到时间列表中)。

就在用户引入新时间后,我必须将引入的时间从字符串转换为长整数,目的是存储它(TimeSpan.TryParseExact 提供配置的时间格式即可),除了一个问题: 如果我们有像 mm:ss 这样的格式并且解析时间是 90:32,解析失败,因为解析时间不应该 > 59 分钟。

我制作了一个小型控制台应用程序示例来帮助重现我的问题:

static void Main(string[] args)
    {
        string TimeFormat = @"ss\:fff";

        long[] SampleTimes = new long[] { 1000, 5000, 59666 };
        List<long> times = new List<long>(SampleTimes);

        string input;
        long aux;
        do
        {
            ShowTimes(times, TimeFormat);

            Console.Write(">");
            input = Console.ReadLine();

            if (TryParseTime(input, TimeFormat, out aux))
                times.Add(aux);
            else
                Console.WriteLine("Failed parsing");

        } while (input != "Exit");
    }

    static void ShowTimes(IEnumerable<long> times, string format)
    {
        Console.WriteLine("-----");
        foreach (long time in times)
            Console.WriteLine(TimeSpan.FromMilliseconds(time).ToString(format));
        Console.WriteLine("-----");
    }

    static bool TryParseTime(string time, string format, out long parsed)
    {
        TimeSpan result;
        bool ok = TimeSpan.TryParseExact(time, format, null, out result);
        parsed = ok ? (long)result.TotalMilliseconds : -1;
        return ok;
    }

在另一篇文章 [[=​​14=]] 中引用了相同的问题,他们解决了这个问题,将引入时间的各个部分分开并通过代码计算:

//From first post
var temp = "113388";
s_Time = DateTime.ParseExact(temp.Substring(0, 4
), "HHmm", null).AddSeconds(int.Parse(temp.Substring(4)));

//From second post
public static decimal Hours(string s)
{
    decimal r;
    if (decimal.TryParse(s, out r))
        return r;

    var parts = s.Split(':');
    return (decimal)new TimeSpan(int.Parse(parts[0]), int.Parse(parts[1]),0).TotalHours;
}

但是我无法按照这种方式进行,因为没有时间格式可以作为拆分介绍的时间格式的参考,它可以随时更改。

此时我唯一的想法是创建一个TimeSpan.TryParseExact扩展方法,通过正则表达式获取最大的时间单位并通过单独的解析...

有更好的方法吗?

我觉得很合理;让您的用户输入如下字符串:

T-{hour}h{min}m{sec}s [baby!]

用正则表达式转义它,然后用字符串替换它(例如"{hour}" -> "(?<h>\d+)")成为正则表达式:

T-(?<h>\d+)h(?<m>\d+)m(?<s>\d+)s \[baby!\]

还有字符串替换成时间跨度输出格式:

'T-'hh'h'mm'm'ss's [baby!]'

然后你有你的捕获组..他们可以输入他们奇怪的时间格式,你可以解析它,你可以输出它..

好的,我最终使用了这个自定义方法来完成这项工作。

不是连续执行很多次的方法,因为它会有巨大的性能问题,但是从前端解析引入的数据是可以接受的:

    /// <summary>
    /// Given a time and a format it creates a <see cref="TimeSpan"/> ignoring the format digit limitations.
    /// The format is not validated, so better ensure a correct one is provided ;)
    /// </summary>
    /// <param name="time"></param>
    /// <param name="format"></param>
    /// <param name="timeSpan"></param>
    /// <returns></returns>
    public static bool TryParseTime(string time, string format, out TimeSpan timeSpan)
    {
        // Regex to match the components of the time format (ss:fff matches ss and fff)
        var formatRegex = new Regex(@"(?<=(?<!\)(?:\{2})*)(%?([fFsmhd])(*))");
        var matches = formatRegex.Matches(format);
        if (matches.Count > 0)
        {
            // We build a big regex to extract the values from time
            string formatExtractionRegex = string.Empty;
            int pivot = 0;
            foreach (Match match in matches)
            {
                if (match.Success)
                {
                    char c = match.Value.ToLower()[0];
                    formatExtractionRegex += $@"{format.Substring(pivot, match.Index - pivot)}(?<{c}>\d+)";

                    pivot = match.Index + match.Length;
                }
            }

            var timeParts = new Regex(formatExtractionRegex).Match(time);
            int d, h, m, s, f;
            int.TryParse(timeParts.Groups["d"].ToString(), out d);
            int.TryParse(timeParts.Groups["h"].ToString(), out h);
            int.TryParse(timeParts.Groups["m"].ToString(), out m);
            int.TryParse(timeParts.Groups["s"].ToString(), out s);
            int.TryParse(timeParts.Groups["f"].ToString(), out f);
            timeSpan = new TimeSpan(d, h, m, s, f);
            return true;
        }

        timeSpan = default;
        return false;
    }

该方法通过构建一个替换正则表达式 \d+ 的数字类型的大正则表达式从时间中提取数据,因此我们 select 整个数字组比格式指定。

如果我们提供时间 100:100:5000 和格式 mm\:ss\:fff,生成的正则表达式将为 (?<m>\d+)\:(?<s>\d+)\:(?<f>\d+).

最后我们解析匹配的组,我们解析它们以提供给 TimeSpan Constructor