2013-02-26 14 views
6

Sto essenzialmente cercando di leggere un file xml. Uno dei valori ha un suffisso, ad es. "30d". Questo significa "30 giorni". Quindi sto cercando di convertire questo in un DateTime.Now.AddDays(30). Per leggere questo campo in XML, ho deciso di utilizzare un Enum:Enum e stringa di corrispondenza

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

Ora io non sono esattamente sicuro di come esattamente per avvicinarsi a questo in modo efficiente (io sono un po 'sciocca quando si tratta di enumerazioni). Devo separare il suffisso, in questo caso "d", prima dalla stringa, quindi provare a farlo corrispondere allo enum usando una dichiarazione switch?

Immagino che se non risponderesti alla mia domanda, sarebbe: qual è il modo migliore per ottenere da 30d, a DateTime.Now.AddDays(30)?

+0

È possibile che il valore abbia una miscela, ad esempio "1d 12h"? –

+0

puoi avere un suffisso come 30d5h6m o 25d7m? –

+0

Sarà solo XXy: dove X = int e y = suffisso: d/m/h – jzm

risposta

6

Si potrebbe fare un ExtensionMethod per analizzare la stringa e restituire il DateTime si desidera

Qualcosa di simile:

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

Usage:

var date = DateTime.Now.AddDuration("2d"); 
+0

Mi piace. Nessuna regex, nessuna enumerazione e solo un paio di righe di codice. Semplice e al punto. Buon lavoro. – NotMe

+2

Sicuramente la risposta più semplice qui. L'unica cosa che cambierei è dividere la stringa con un "" ed eseguirla attraverso un ciclo foreach in modo che possa accettare valori come DateTime.Now.AddDuration ("12d 2h 30m"); – Corylulu

3

aggiornamento: Non votare per questo. Lo sto lasciando semplicemente perché è un approccio alternativo. Guardate invece le risposte di sa_ddam213 e del dottor Wily's Apprentice.

Devo separare il suffisso, in questo caso "d", fuori della stringa prima, quindi provare e abbinare nella enum utilizzando un'istruzione switch?

Sì.

Per un esempio completamente funzionante:

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

Sì, lo scambio mi ha battuto. Ed è una risposta una sola parola !!! –

+0

Ho dovuto citare la domanda per superare il carattere minimo "caratteristica". ;) – NotMe

+0

Rimosso la mia risposta, l'hai terminata prima di me. Penso che un RegEx sia probabilmente la risposta più ovvia, ma questo è più vicino a quello che stava cercando. Di norma, tuttavia, è consigliabile utilizzare Enum.TryParse per evitare di generare un'eccezione. – Corylulu

0

Enums non possono essere rimosse con i tipi non numerici, così enums basati su stringhe sono fuori. È possibile che tu stia pensando troppo. Senza saperne di più sul problema, la soluzione più semplice sembra essere la scomposizione dell'ultimo carattere, la conversione del resto in un int e la gestione di ogni carattere finale come caso separato.

1

Provare il seguente codice, assumendo che valori come "30d" siano in una stringa "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

Io suggerirei di usare espressioni regolari per mettere a nudo il numero primo e di eseguire Enum.Parse Method per valutare il valore della enum. Quindi puoi usare un interruttore (vedi la risposta di Corylulu) per ottenere l'offset corretto, in base al numero analizzato e al valore enum.

2

Io davvero non vedo come utilizzare un enum aiuta qui.

Ecco come potrei avvicinarmi.

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

seconda della affidabilità dei dati in ingresso, potrebbe essere necessario utilizzare int.TryParse() invece di int.Parse(). Altrimenti, questo dovrebbe essere tutto ciò di cui hai bisogno.

Nota: Ho anche scritto un sostituto sscanf() per.NET che gestirà questo abbastanza facilmente. Puoi vedere il codice per questo nell'articolo A sscanf() Replacement for .NET.

5

Questo sembra un buon posto per usare le espressioni regolari; in particolare, cattura i gruppi.

Di seguito è riportato un esempio di lavoro:

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: Qui di seguito è un'alternativa, un po 'più versione condensata di quanto sopra, anche se non sono sicuro che quello che preferisco di più. Di solito non sono un fan del codice troppo denso. Ho regolato l'espressione regolare per inserire un limite di 10 cifre su ciascun numero. Ciò mi consente di utilizzare in tutta sicurezza la funzione int.Parse, perché so che l'input è composto da almeno una cifra e al massimo dieci (a meno che non sia stato catturato affatto, nel qual caso sarebbe una stringa vuota: quindi, lo scopo di il metodo 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); 
     } 

EDIT: Giusto per prendere questo un passo in più, ho aggiunto un'altra versione di sotto, che gestisce giorni, ore, minuti, secondi e millesimi di secondo, con una varietà di abbreviazioni per ciascuna. Ho diviso l'espressione regolare in più righe per essere leggibile. Nota, ho anche dovuto regolare l'espressione usando (\b|(?=[^a-z])) alla fine di ogni componente: questo perché l'unità "ms" veniva catturata come unità "m". La sintassi speciale di "? =" Usata con "[^ a-z]" indica di far corrispondere il carattere ma non di "consumarlo".

// 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

Questo è sicuramente l'approccio più espandibile e probabilmente un modo più universale e accettabile per fare quello che l'OP sta chiedendo. – Corylulu

1

Esempio di un'applicazione console esempio/tutorial:

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

Utilizza un attributo per definire il suffisso, utilizza il valore enum per definire il offset.

Richiede:

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

Esso può essere considerato un hack per utilizzare il valore intero enum, ma questo esempio sarà ancora permetterà di analizzare fuori tutte le enumerazioni (per qualsiasi altro uso come il caso switch) con piccoli ritocchi.