2013-02-26 36 views
6

私は本質的にxmlファイルを読み込もうとしています。値の1つに接尾辞が付いています。 "30d"。これは「30日」を意味するものです。そこで、これをDateTime.Now.AddDays(30)に変換しようとしています。 列挙型と文字列の一致

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

は今、私は(それが列挙型に来るとき、私は少しばかだ)これを効率的にアプローチする方法を正確に、正確にはわからない:XMLでこのフィールドを読み取るには、私は、列挙型を使用することにしました。接尾辞(この場合は「d」)を最初に文字列から分離し、 enumswitch文で試してみてください。

私の質問に愚かな場合は、30dからDateTime.Now.AddDays(30)になる最良の方法は何ですか?

+0

"1d 12h"のような値が混在する可能性はありますか? –

+0

30d5h6mまたは25d7mのような接尾辞を使用できますか? –

+0

これはXXyだけです:X = int、y = suffix:d/m/h – jzm

答えて

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"); 
+0

私はこれが好きです。正規表現はなく、列挙型ではなく、ほんの数行のコードです。シンプルでポイントまで。よくやった。 – NotMe

+2

ここで最も簡単な答えは間違いありません。私が変更するのは、文字列を ""分割してforeachループで実行することで、DateTime.Now.AddDuration( "12d 2h 30m")のような値を受け入れることができます。 – Corylulu

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); 
    } 
} 
+0

はい、あなたはそれに私を打ち負かす 'スイッチ'。そしてその1つの答えは!!! –

+0

私はこの質問を引用して、最小文字「フィーチャー」を超えなければなりませんでした。 ;) – NotMe

+0

私の答えを削除して、あなたは私の前でそれを終えました。私はRegExがおそらく最も明白な答えだと思いますが、これは彼が行っていたことに近いです。ただし、通常は例外をスローするのを避けるために、Enum.TryParseを使用する必要があります。 – Corylulu

0

列挙型は、非数値型で裏打ちすることができないので、文字列ベースの列挙型が出ています。あなたはそれを思慮深く考えている可能性があります。問題についてこれ以上知りませんでしたが、最も簡単な解決策は最後の文字を分割し、残りの部分をintに変換し、最後の各文字を別のケースとして扱うようです。

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."); 
} 
0

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

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にあります。

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(); 
     } 
    } 
} 

EDIT:以下

は実施例である私は、私はより多くのを好むどちらかわからないけれども以下の代替、上記の少しより多くの要約版は、です。私は通常、過度に密なコードのファンではありません。 正規表現を調整して、各数字に10桁の制限を設けました。これは私が安全にint.Parse関数を使用することを可能にします。なぜなら、入力が少なくとも1桁から10桁までであることがわかっているからです(まったくキャプチャしない限り、空の文字列になります)。 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」ユニットとして捕捉されたためです。 "[^ a-z]"で使用される "?="という特殊な構文は、文字に一致するが、それを "消費"しないことを示します。コンソールアプリケーションの例/チュートリアルの

// 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)); 
    } 
+1

これは間違いなく最も拡張可能なアプローチであり、おそらくOPが求めていることを実行するより普遍的に受け入れられる方法です。 – Corylulu

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; 

をこれは、列挙型の整数値を使用するハックと見なすことができるが、この例では、まだあなたは少し微調整して(スイッチケースのような他の使用のために)すべての列挙型を解析できるようになります。