【问题标题】:How can I manually parse a custom DateTime format with optional fields in C#如何在 C# 中手动解析带有可选字段的自定义 DateTime 格式
【发布时间】:2011-11-07 10:30:43
【问题描述】:

假设我有以下日期/时间,采用某种内部几乎类似于 ISO 的格式:

  • "2011-11-07T11:17"
  • "--T11:17"(上午 11:17,没有日期,只有时间)
  • "-11-07"(11月7日,没有年份,没有时间)

分隔符是强制性的,让我知道是否存在数据。数据将被设置为如下结构:

struct MyDate
{
    int? Year ;
    int? Month ;
    int? Day ;
    int? Hour ;
    int? Minute ;
}

“最简单”的方法是逐个字符循环,并提取数据(如果存在)。

但我有一种挥之不去的印象,必须有某种 API 来提取,例如,一个整数并返回第一个非整数字符的索引(类似于 C 的strtol)。

C# 中是否有类似strtol 的函数,或者更高级的函数来提取类型化数据,而不是逐个字符地解析字符串?

【问题讨论】:

    标签: c# api strtol


    【解决方案1】:

    您可以使用DateTime.ParseExact。查看文档here

    【讨论】:

    • 我认为这在这种情况下没有多大帮助 - 或者至少,您需要指定所有可能存在/不存在的值。
    • 好吧,我更喜欢它而不是手动解析字符串,特别是如果@paercebal 需要这三种初始格式。
    • 如果只有这三个,那我同意。如果这 6 个值中的每一个都是可选的,那么就有 64 种可能的格式,我想我会以不同的方式处理它。
    • 同意。如果需要所有可能的组合,这可能会变得相当复杂。
    【解决方案2】:

    我会这样处理:

    编辑 2

    现在包括:

    • MyDate 结构中的静态 ParseTryParse 方法:)
    • 一个镜像 ToString() 以便于测试
    • 最少的单元测试测试用例
    • 奖励演示:从字符串到 MyDate 的隐式转换运算符(因此您可以在需要 MyDate 的地方传递字符串)

    再次,在 http://ideone.com/rukw4

    上观看直播
    using System;
    using System.Text;
    using System.Text.RegularExpressions;
    
    struct MyDate 
    { 
        public int? Year, Month, Day, Hour, Minute; 
    
        private static readonly Regex dtRegex = new Regex(
            @"^(?<year>\d{4})?-(?<month>\d\d)?-(?<day>\d\d)?"
        +   @"(?:T(?<hour>\d\d)?:(?<minute>\d\d)?)?$",
            RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
    
        public static bool TryParse(string input, out MyDate result)
        {
            Match match = dtRegex.Match(input);
            result = default(MyDate);
    
            if (match.Success)
            {
                if (match.Groups["year"].Success)
                    result.Year = Int32.Parse(match.Groups["year"].Value);
                if (match.Groups["month"].Success)
                    result.Month = Int32.Parse(match.Groups["month"].Value);
                if (match.Groups["day"].Success)
                    result.Day = Int32.Parse(match.Groups["day"].Value);
                if (match.Groups["hour"].Success)
                    result.Hour = Int32.Parse(match.Groups["hour"].Value);
                if (match.Groups["minute"].Success)
                    result.Minute = Int32.Parse(match.Groups["minute"].Value);
            }
    
            return match.Success;
        }
    
        public static MyDate Parse(string input)
        {
            MyDate result;
            if (!TryParse(input, out result))
                throw new ArgumentException(string.Format("Unable to parse MyDate: '{0}'", input));
    
            return result;
        }
    
        public override string ToString()
        {
            return string.Format("{0:0000}-{1:00}-{2:00}T{3:00}:{4:00}", Year, Month, Day, Hour, Minute);
        }
    
        public static implicit operator MyDate(string input)
        {
            return Parse(input);
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var testcase in new [] { 
                    "2011-11-07T11:17",
                    "-11-07T11:17",
                    "2011--07T11:17",
                    "2011-11-T11:17",
                    "2011-11-07T:17",
                    "2011-11-07T11:",
                    // extra:
                    "--T11:17", // (11:17 am, no date, only time)
                    "-11-07", // (november the 7th, no year, no time)
                    // failures:
                    "2011/11/07 T 11:17",
                    "no match" })
            {
                MyDate parsed;
                if (MyDate.TryParse(testcase, out parsed))
                    Console.WriteLine("'{0}' -> Parsed into '{1}'", testcase, parsed);
                else
                    Console.WriteLine("'{0}' -> Parse failure", testcase);
            }
        }
    }
    

    输出:

    '2011-11-07T11:17' -> Parsed into '2011-11-07T11:17'
    '-11-07T11:17' -> Parsed into '-11-07T11:17'
    '2011--07T11:17' -> Parsed into '2011--07T11:17'
    '2011-11-T11:17' -> Parsed into '2011-11-T11:17'
    '2011-11-07T:17' -> Parsed into '2011-11-07T:17'
    '2011-11-07T11:' -> Parsed into '2011-11-07T11:'
    '--T11:17' -> Parsed into '--T11:17'
    '-11-07' -> Parsed into '-11-07T:'
    '2011/11/07 T 11:17' -> Parse failure
    'no match' -> Parse failure
    

    【讨论】:

    • 呃。我想我也会从 OP 中添加测试用例。也修复了一个错误
    【解决方案3】:

    如果性能不是问题,我会为您的任务使用正则表达式。

    使用(\d*)-(\d*)-(\d*)T(\d*):(\d*)作为表达式,然后将每个捕获组解析成你结构的对应字段,将空字符串转换为null值:

    var match = Regex.Match(str, @"(\d*)-(\d*)-(\d*)T(\d*):(\d*)");
    var date = new MyDate();
    
    if (match.Groups[1].Value != "") date.Year = int.Parse(match.Groups[1].Value);
    if (match.Groups[2].Value != "") date.Month = int.Parse(match.Groups[2].Value);
    if (match.Groups[3].Value != "") date.Day = int.Parse(match.Groups[3].Value);
    if (match.Groups[4].Value != "") date.Hour = int.Parse(match.Groups[4].Value);
    if (match.Groups[5].Value != "") date.Minute = int.Parse(match.Groups[5].Value);
    

    编辑: 'T' 和 ':' 分隔符在此代码中是强制性的。如果您不希望这样做,请参阅 @sehe 的答案。

    【讨论】:

    • 你的正则表达式有点草率(“2011-99-231T:”会匹配,“Some bogus --T9991238: text”也会匹配),你可以预编译它并提高性能
    • 我假设整个示例中的数据都是正确的,并试图使示例尽可能短。当然可以改进。
    • 好吧...也许然后尝试解析 OP 中发布的三个示例?
    • 呵呵,他们与他自己关于强制分隔符的声明相矛盾 :) 好吧,我的解决方案有缺陷。我不会修改它,因为你的回答做得很好
    【解决方案4】:

    如果您知道日期格式,例如可以使用DateTime.ParseExact...如果您需要类似于strtol 的内容,则可以使用Convert.ToInt32

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-09
      • 1970-01-01
      相关资源
      最近更新 更多