6

我本质上是在尝试读取 xml 文件。其中一个值具有后缀,例如“ 30d”。这意味着“30 天”。所以我正在尝试将其转换为DateTime.Now.AddDays(30). 为了在 XML 中读取这个字段,我决定使用 Enum:

enum DurationType { Min = "m", Hours = "h", Days = "d" }

现在我不完全确定如何有效地解决这个问题(在枚举方面我有点愚蠢)。我是否应该先将后缀(在本例中为“d”)从字符串中分离出来,然后尝试在enumusingswitch语句中匹配它?

我想如果你把我的问题弄糊涂了,那就是:从30d, 到的最佳方式是什么DateTime.Now.AddDays(30)

4

8 回答 8

6

您可以制作一个 ExtensionMethod 来解析字符串并返回您想要的 DateTime

就像是:

    public static DateTime AddDuration(this DateTime datetime, string str)
    {
        int value = 0;
        int mutiplier = str.EndsWith("d") ? 1440 : str.EndsWith("h") ?  60 : 1;
        if (int.TryParse(str.TrimEnd(new char[]{'m','h','d'}), out value))
        {
            return datetime.AddMinutes(value * mutiplier);
        }
        return datetime;
    }

用法:

 var date = DateTime.Now.AddDuration("2d");
于 2013-02-26T00:48:45.557 回答
5

这似乎是使用正则表达式的好地方;具体来说,捕获组。

下面是一个工作示例:

using System;
using System.Text.RegularExpressions;

namespace RegexCaptureGroups
{
    class Program
    {
        // Below is a breakdown of this regular expression:
        // First, one or more digits followed by "d" or "D" to represent days.
        // Second, one or more digits followed by "h" or "H" to represent hours.
        // Third, one or more digits followed by "m" or "M" to represent minutes.
        // Each component can be separated by any number of spaces, or none.
        private static readonly Regex DurationRegex = new Regex(@"((?<Days>\d+)d)?\s*((?<Hours>\d+)h)?\s*((?<Minutes>\d+)m)?", RegexOptions.IgnoreCase);

        public static TimeSpan ParseDuration(string input)
        {
            var match = DurationRegex.Match(input);

            var days = match.Groups["Days"].Value;
            var hours = match.Groups["Hours"].Value;
            var minutes = match.Groups["Minutes"].Value;

            int daysAsInt32, hoursAsInt32, minutesAsInt32;

            if (!int.TryParse(days, out daysAsInt32))
                daysAsInt32 = 0;

            if (!int.TryParse(hours, out hoursAsInt32))
                hoursAsInt32 = 0;

            if (!int.TryParse(minutes, out minutesAsInt32))
                minutesAsInt32 = 0;

            return new TimeSpan(daysAsInt32, hoursAsInt32, minutesAsInt32, 0);
        }

        static void Main(string[] args)
        {
            Console.WriteLine(ParseDuration("30d"));
            Console.WriteLine(ParseDuration("12h"));
            Console.WriteLine(ParseDuration("20m"));
            Console.WriteLine(ParseDuration("1d 12h"));
            Console.WriteLine(ParseDuration("5d 30m"));
            Console.WriteLine(ParseDuration("1d 12h 20m"));

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
}

编辑:下面是上面的另一种更精简的版本,尽管我不确定我更喜欢哪一个。我通常不喜欢过于密集的代码。我调整了正则表达式,将每个数字限制为 10 位。这使我可以安全地使用该int.Parse函数,因为我知道输入由至少一个数字和最多十个数字组成(除非它根本没有捕获,在这种情况下它将是空字符串:因此, ParseInt32ZeroIfNullOrEmpty 方法)。

        // Below is a breakdown of this regular expression:
        // First, one to ten digits followed by "d" or "D" to represent days.
        // Second, one to ten digits followed by "h" or "H" to represent hours.
        // Third, one to ten digits followed by "m" or "M" to represent minutes.
        // Each component can be separated by any number of spaces, or none.
        private static readonly Regex DurationRegex = new Regex(@"((?<Days>\d{1,10})d)?\s*((?<Hours>\d{1,10})h)?\s*((?<Minutes>\d{1,10})m)?", RegexOptions.IgnoreCase);

        private static int ParseInt32ZeroIfNullOrEmpty(string input)
        {
            return string.IsNullOrEmpty(input) ? 0 : int.Parse(input);
        }

        public static TimeSpan ParseDuration(string input)
        {
            var match = DurationRegex.Match(input);

            return new TimeSpan(
                ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value),
                ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value),
                ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value),
                0);
        }

编辑:为了再迈出这一步,我在下面添加了另一个版本,它处理天、小时、分钟、秒和毫秒,每个都有各种缩写。为了便于阅读,我将正则表达式拆分为多行。请注意,我还必须通过(\b|(?=[^a-z]))在每个组件的末尾使用来调整表达式:这是因为“ms”单位被捕获为“m”单位。与“[^az]”一起使用的“?=”的特殊语法表示匹配字符,而不是“使用”它。

    // Below is a breakdown of this regular expression:
    // First, one to ten digits followed by "d", "dy", "dys", "day", or "days".
    // Second, one to ten digits followed by "h", "hr", "hrs", "hour", or "hours".
    // Third, one to ten digits followed by "m", "min", "minute", or "minutes".
    // Fourth, one to ten digits followed by "s", "sec", "second", or "seconds".
    // Fifth, one to ten digits followed by "ms", "msec", "millisec", "millisecond", or "milliseconds".
    // Each component may be separated by any number of spaces, or none.
    // The expression is case-insensitive.
    private static readonly Regex DurationRegex = new Regex(@"
        ((?<Days>\d{1,10})(d|dy|dys|day|days)(\b|(?=[^a-z])))?\s*
        ((?<Hours>\d{1,10})(h|hr|hrs|hour|hours)(\b|(?=[^a-z])))?\s*
        ((?<Minutes>\d{1,10})(m|min|minute|minutes)(\b|(?=[^a-z])))?\s*
        ((?<Seconds>\d{1,10})(s|sec|second|seconds)(\b|(?=[^a-z])))?\s*
        ((?<Milliseconds>\d{1,10})(ms|msec|millisec|millisecond|milliseconds)(\b|(?=[^a-z])))?",
        RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

    private static int ParseInt32ZeroIfNullOrEmpty(string input)
    {
        return string.IsNullOrEmpty(input) ? 0 : int.Parse(input);
    }

    public static TimeSpan ParseDuration(string input)
    {
        var match = DurationRegex.Match(input);

        return new TimeSpan(
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value),
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value),
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value),
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Seconds"].Value),
            ParseInt32ZeroIfNullOrEmpty(match.Groups["Milliseconds"].Value));
    }
于 2013-02-26T00:50:49.167 回答
3

更新: 不要为此投票。我离开它只是因为它是一种替代方法。相反,请查看 sa_ddam213 和 Dr. Wily's Apprentice 的答案。

我是否应该先将后缀(在这种情况下为“d”)从字符串中分离出来,然后尝试使用 switch 语句在枚举中匹配它?

是的。

对于一个完整的工作示例:

private void button1_Click( object sender, EventArgs e ) {
    String value = "30d";

    Duration d = (Duration)Enum.Parse(typeof(Duration), value.Substring(value.Length - 1, 1).ToUpper());
    DateTime result = d.From(new DateTime(), value);

    MessageBox.Show(result.ToString());
}



enum Duration { D, W, M, Y };

static class DurationExtensions {
    public static DateTime From( this Duration duration, DateTime dateTime, Int32 period ) {
        switch (duration)
        {
          case Duration.D: return dateTime.AddDays(period);
          case Duration.W: return dateTime.AddDays((period*7));
          case Duration.M: return dateTime.AddMonths(period);
          case Duration.Y: return dateTime.AddYears(period);

          default: throw new ArgumentOutOfRangeException("duration");
        }
     }
    public static DateTime From( this Duration duration, DateTime dateTime, String fullValue ) {
        Int32 period = Convert.ToInt32(fullValue.ToUpper().Replace(duration.ToString(), String.Empty));
        return From(duration, dateTime, period);
    }
}
于 2013-02-26T00:37:38.317 回答
2

我真的不明白如何enum在这里使用帮助。

以下是我可能会如何处理它。

string s = "30d";

int typeIndex = s.IndexOfAny(new char[] { 'd', 'w', 'm' });
if (typeIndex > 0)
{
    int value = int.Parse(s.Substring(0, typeIndex));
    switch (s[typeIndex])
    {
        case 'd':
            result = DateTime.Now.AddDays(value);
            break;
        case 'w':
            result = DateTime.Now.AddDays(value * 7);
            break;
        case 'm':
            result = DateTime.Now.AddMonths(value);
            break;
    }
}

根据输入数据的可靠性,您可能需要使用int.TryParse()而不是int.Parse(). 否则,这应该就是您所需要的。

注意:我还sscanf()为 .NET 编写了一个替代品,可以很容易地处理这个问题。您可以在文章A sscanf() Replacement for .NET中查看代码。

于 2013-02-26T00:49:25.733 回答
1

试试下面的代码,假设像“30d”这样的值在字符串“val”中。

DateTime ConvertValue(string val) {
    if (val.Length > 0) {
        int prefix = Convert.ToInt32(val.Length.Remove(val.Length-1));
        switch (val[val.Length-1]) {
        case 'd': return DateTime.Now.AddDays(prefix);
        case 'm': return DateTime.Now.AddMonths(prefix);
        // etc.
    }
    throw new ArgumentException("string in unexpected format.");
}
于 2013-02-26T00:42:52.883 回答
1

控制台应用程序示例/教程示例:

enum DurationType
{
    [DisplayName("m")]
    Min = 1,
    [DisplayName("h")]
    Hours = 1 * 60,
    [DisplayName("d")]
    Days = 1 * 60 * 24
}

internal class Program
{
    private static void Main(string[] args)
    {

        string input1 = "10h";
        string input2 = "1d10h3m";

        var x = GetOffsetFromDate(DateTime.Now, input1);
        var y = GetOffsetFromDate(DateTime.Now, input2);

    }

    private static Dictionary<string, DurationType> suffixDictionary
    {
        get
        {
            return Enum
                .GetValues(typeof (DurationType))
                .Cast<DurationType>()
                .ToDictionary(duration => duration.GetDisplayName(), duration => duration);
        }
    }

    public static DateTime GetOffsetFromDate(DateTime date, string input)
    {
        MatchCollection matches = Regex.Matches(input, @"(\d+)([a-zA-Z]+)");
        foreach (Match match in matches)
        {
            int numberPart = Int32.Parse(match.Groups[1].Value);
            string suffix = match.Groups[2].Value;
            date = date.AddMinutes((int)suffixDictionary[suffix]);
        }
        return date;
    }


}


[AttributeUsage(AttributeTargets.Field)]
public class DisplayNameAttribute : Attribute
{
    public DisplayNameAttribute(String name)
    {
        this.name = name;
    }
    protected String name;
    public String Name { get { return this.name; } }
}

public static class ExtensionClass
{
    public static string GetDisplayName<TValue>(this TValue value) where TValue : struct, IConvertible
    {
        FieldInfo fi = typeof(TValue).GetField(value.ToString());
        DisplayNameAttribute attribute = (DisplayNameAttribute)fi.GetCustomAttributes(typeof(DisplayNameAttribute), false).FirstOrDefault();
        if (attribute != null)
            return attribute.Name;
        return value.ToString();
    }
}

使用属性来定义你的后缀,使用枚举值来定义你的偏移量。

要求:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;

使用枚举整数值可能被认为是一种 hack,但是这个示例仍然可以让您解析所有枚举(用于任何其他用途,如 switch case),只需稍作调整。

于 2013-02-26T01:25:42.800 回答
0

我建议先使用正则表达式去除数字,然后执行Enum.Parse 方法来评估枚举的值。您可以使用开关(请参阅 Corylulu 的答案)根据解析的数字和枚举值获得正确的偏移量。

于 2013-02-26T00:43:45.653 回答
0

枚举不能支持非数字类型,因此基于字符串的枚举不可用。你可能想多了。在不了解问题的情况下,最直接的解决方案似乎是将最后一个字符拆分,将其余字符转换为 int,然后将每个最终字符作为单独的案例处理。

于 2013-02-26T00:38:40.437 回答