6

私は本質的にxmlファイルを読み込もうとしています。値の1つに接尾辞があります(例: " 30d")。これは「30日」を意味します。だから私はこれをに変換しようとしていDateTime.Now.AddDays(30)ます。XMLでこのフィールドを読み取るために、列挙型を使用することにしました。

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

今、私はこれに効率的にどのように正確にアプローチするか正確にはわかりません(列挙型に関しては少し頭がおかしいです)。最初に文字列から接尾辞(この場合は「d」)を分離してから、enumusingswitchステートメントでそれを一致させる必要がありますか?

あなたが私の質問をばかげているとしたら、それは次のようになるでしょう:から30d、へ行くための最良の方法は何DateTime.Now.AddDays(30)ですか?

4

8 に答える 8

6

ExtensionMethodを作成して文字列を解析し、必要な日時を返すことができます

何かのようなもの:

    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桁の制限を設定しました。これにより、入力が少なくとも1桁から最大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);
        }

編集:これをもう1つ実行するために、日、時間、分、秒、ミリ秒を処理する別のバージョンを以下に追加しました。それぞれにさまざまな略語が付いています。読みやすくするために、正規表現を複数の行に分割しました。また、各コンポーネントの最後で使用して式を調整する必要がありました(\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とワイリー博士の見習いの答えを見てください。

最初に文字列から接尾辞(この場合は「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()Replacementfor.NETで確認できます。

于 2013-02-26T00:49:25.733 に答える
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;

列挙型整数値を使用することはハックと見なされる場合がありますが、この例では、少し調整するだけですべての列挙型(switchケースなどの他の用途)を解析できます。

于 2013-02-26T01:25:42.800 に答える
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 に答える
0

列挙型は数値以外の型でバックアップできないため、文字列ベースの列挙型は使用できません。あなたがそれを考えすぎている可能性があります。問題についてこれ以上知らなくても、最も簡単な解決策は、最後の文字を分割し、残りをintに変換してから、最後の各文字を個別のケースとして処理することです。

于 2013-02-26T00:38:40.437 に答える
0

正規表現を使用して最初に数値を削除してから、 Enum.Parseメソッドを実行して列挙型の値を評価することをお勧めします。スイッチ(Coryluluの回答を参照)を使用して、解析された数値と列挙値に基づいて適切なオフセットを取得できます。

于 2013-02-26T00:43:45.653 に答える