2009-05-20 16 views
9

Sto implementando un controllo Data fuzzy in C# per un'applicazione winforms. Il Fuzzy data deve essere in grado di prendere valori fuzzy comeFuzzy Data Time Picker Control in C# .NET?

  • scorso giugno
  • 2 Ore fa
  • 2 Mesi fa
  • scorsa settimana
  • Ieri
  • scorso anno

e simili

Esistono esempi di implementazioni di "Time-Pick" Time Pickers?

Tutte le idee per attuare un tale controllo sarebbe apprezzato

PS: Sono consapevole dell'algoritmo data sfocata parlato here e here, non vedo per tutte le idee e le ispirazioni per lo sviluppo di tale controllo

+1

Come una domanda a parte, supponendo che avessi il codice che copriva tutti i tuoi casi, in che modo l'utente avrebbe saputo cosa potevano digitare? In termini di tempo di completamento dell'attività, in che modo la digitazione ieri sarebbe più veloce rispetto all'utilizzo di un selettore di date? Sarei molto interessato a sapere perché pensi di aver bisogno di un tale controllo? – RichardOD

+0

Hai bisogno? - Bene, il controllo verrebbe utilizzato in scenari in cui l'input/valore verrebbe prelevato da entità basate su timer. Esempio banale: "Quando hai tenuto la torta nel microonde?" Sento che è molto più facile inserire "25 minuti fa" [25 è la lettura sul timer a microonde] rispetto al calcolo manuale di CurrentTime meno 25 minuti. Il selettore della data della data fuzzy verrà utilizzato in tali scenari quando sarebbe piuttosto semplice immettere valori sfocati rispetto al calcolo manuale dei valori di data e ora. Phew .. Quello era lungo .. – abhilash

risposta

21

L'analisi è abbastanza semplice. Può essere implementato come una serie di espressioni regolari e alcuni calcoli di date.

L'esempio di seguito può essere facilmente esteso in base alle proprie esigenze. Ho all'incirca provato e funziona, almeno per le seguenti stringhe:

  • il prossimo mese, il prossimo anno,
  • prossimi 4 mesi, 3 giorni successivi
  • tre giorni fa, 5 ore fa
  • domani, ieri
  • lo scorso anno, il mese scorso,
  • scorso mar, ven prossimo
  • lo scorso giugno, il prossimo maggio,
  • gennaio 2008, 1 gennaio 2009,
  • giugno 2019, 2009/01/01

La classe helper: esempio

class FuzzyDateTime 
{ 

    static List<string> dayList = new List<string>() { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; 
    static List<IDateTimePattern> parsers = new List<IDateTimePattern>() 
    { 
     new RegexDateTimePattern (
      @"next +([2-9]\d*) +months", 
      delegate (Match m) { 
       var val = int.Parse(m.Groups[1].Value); 
       return DateTime.Now.AddMonths(val); 
      } 
     ), 
     new RegexDateTimePattern (
      @"next +month", 
      delegate (Match m) { 
       return DateTime.Now.AddMonths(1); 
      } 
     ),   
     new RegexDateTimePattern (
      @"next +([2-9]\d*) +days", 
      delegate (Match m) { 
       var val = int.Parse(m.Groups[1].Value); 
       return DateTime.Now.AddDays(val); 
      } 
     ), 

     new RegexDateTimePattern (
      @"([2-9]\d*) +months +ago", 
      delegate (Match m) { 
       var val = int.Parse(m.Groups[1].Value); 
       return DateTime.Now.AddMonths(-val); 
      } 
     ), 
     new RegexDateTimePattern (
      @"([2-9]\d*) days +ago", 
      delegate (Match m) { 
       var val = int.Parse(m.Groups[1].Value); 
       return DateTime.Now.AddDays(-val); 
      } 
     ), 
     new RegexDateTimePattern (
      @"([2-9]\d*) *h(ours)? +ago", 
      delegate (Match m) { 
       var val = int.Parse(m.Groups[1].Value); 
       return DateTime.Now.AddMonths(-val); 
      } 
     ), 
     new RegexDateTimePattern (
      @"tomorrow", 
      delegate (Match m) { 
       return DateTime.Now.AddDays(1); 
      } 
     ), 
     new RegexDateTimePattern (
      @"today", 
      delegate (Match m) { 
       return DateTime.Now; 
      } 
     ), 
     new RegexDateTimePattern (
      @"yesterday", 
      delegate (Match m) { 
       return DateTime.Now.AddDays(-1); 
      } 
     ), 
     new RegexDateTimePattern (
      @"(last|next) *(year|month)", 
      delegate (Match m) { 
       int direction = (m.Groups[1].Value == "last")? -1 :1; 
       switch(m.Groups[2].Value) 
       { 
        case "year": 
         return new DateTime(DateTime.Now.Year+direction, 1,1); 
        case "month": 
         return new DateTime(DateTime.Now.Year, DateTime.Now.Month+direction, 1); 
       } 
       return DateTime.MinValue; 
      } 
     ), 
     new RegexDateTimePattern (
      String.Format(@"(last|next) *({0}).*", String.Join("|", dayList.ToArray())), //handle weekdays 
      delegate (Match m) { 
       var val = m.Groups[2].Value; 
       var direction = (m.Groups[1].Value == "last")? -1 :1; 
       var dayOfWeek = dayList.IndexOf(val.Substring(0,3)); 
       if (dayOfWeek >= 0) { 
        var diff = direction*(dayOfWeek - (int)DateTime.Today.DayOfWeek); 
        if (diff <= 0) { 
         diff = 7 + diff; 
        } 
        return DateTime.Today.AddDays(direction * diff); 
       } 
       return DateTime.MinValue; 
      } 
     ), 

     new RegexDateTimePattern (
      @"(last|next) *(.+)", // to parse months using DateTime.TryParse 
      delegate (Match m) { 
       DateTime dt; 
       int direction = (m.Groups[1].Value == "last")? -1 :1; 
       var s = String.Format("{0} {1}",m.Groups[2].Value, DateTime.Now.Year + direction); 
       if (DateTime.TryParse(s, out dt)) { 
        return dt; 
       } else { 
        return DateTime.MinValue; 
       } 
      } 
     ), 
     new RegexDateTimePattern (
      @".*", //as final resort parse using DateTime.TryParse 
      delegate (Match m) { 
       DateTime dt; 
       var s = m.Groups[0].Value; 
       if (DateTime.TryParse(s, out dt)) { 
        return dt; 
       } else { 
        return DateTime.MinValue; 
       } 
      } 
     ), 
    }; 

    public static DateTime Parse(string text) 
    { 
     text = text.Trim().ToLower(); 
     var dt = DateTime.Now; 
     foreach (var parser in parsers) 
     { 
      dt = parser.Parse(text); 
      if (dt != DateTime.MinValue) 
       break; 
     } 
     return dt; 
    } 
} 
interface IDateTimePattern 
{ 
    DateTime Parse(string text); 
} 

class RegexDateTimePattern : IDateTimePattern 
{ 
    public delegate DateTime Interpreter(Match m); 
    protected Regex regEx; 
    protected Interpreter inter; 
    public RegexDateTimePattern(string re, Interpreter inter) 
    { 
     this.regEx = new Regex(re); 
     this.inter = inter; 
    } 
    public DateTime Parse(string text) 
    { 
     var m = regEx.Match(text); 

     if (m.Success) 
     { 
      return inter(m); 
     } 
     return DateTime.MinValue; 
    } 
} 

Usage:

var val = FuzzyDateTime.Parse(textBox1.Text); 
if (val != DateTime.MinValue) 
    label1.Text = val.ToString(); 
else 
    label1.Text = "unknown value"; 
+0

Puntelli a te, buon signore! –

+0

Sembra che ci sia un bug per '@" domani "', che è lo stesso di '@" oggi "', dove dovrebbe essere 'return DateTime.Now.AddDays (1)'. –

+0

@FreshCode Grazie per aver individuato quell'errore. (Ho corretto l'elenco) –

2

Abbiamo un controllo simile. Aggiungiamo solo un elenco di caselle combinate - controlli per scegliere la tua scelta.

PeriodSelector:

  • Da [datepicker] Fino al [datepicker]
  • [NumericUpDown] mesi fa
  • [NumericUpDown] ore fa
  • scorsa settimana
  • Ieri
  • settimana [ datepicker]
  • Giorno [datapicker]
  • ...

e basta prendere le scelte che hanno senso per il vostro scopo.

È molto più semplice implementarlo e quindi analizzare il testo. I calcoli sono piuttosto semplici.

È importante verificare che si stia selezionando un periodo. L'anno scorso significa gennaio 2008> dicembre 2008. Due ore fa da oggi fino ad ora - 2 ore. Etc.

3

Uno dei i sistemi utilizzati dai nostri utenti consentono loro di inserire date come:

  • T // Oggi
  • T + 1 // Oggi più/meno un numero di giorni
  • T + 1W // Oggi più/meno un numero di settimane
  • T + 1 m // Oggi più/meno un certo numero di mesi
  • T + 1A // Oggi più/meno un certo numero di anni

sembra che piace, e ha chiesto che nella nostra applicazione, così mi si avvicinò con il seguente codice. ParseDateToString prenderà una stringa di uno dei moduli sopra, più alcuni altri, calcolerà la data e la restituirà nel formato "MM/GG/AAAA". È abbastanza facile cambiarlo per restituire l'oggetto DateTime effettivo e aggiungere il supporto per ore, minuti, secondi o qualsiasi cosa tu voglia.

using System; 
using System.Text.RegularExpressions; 

namespace Utils 
{ 
    class DateParser 
    { 
     private static readonly DateTime sqlMinDate = DateTime.Parse("01/01/1753"); 
     private static readonly DateTime sqlMaxDate = DateTime.Parse("12/31/9999"); 
     private static readonly Regex todayPlusOrMinus = new Regex(@"^\s*t(\s*[\-\+]\s*\d{1,4}([dwmy])?)?\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); // T +/- number of days 
     private static readonly Regex dateWithoutSlashies = new Regex(@"^\s*(\d{6}|\d{8})\s*$", RegexOptions.Compiled); // Date in MMDDYY or MMDDYYYY format 

     private const string DATE_FORMAT = "MM/dd/yyyy"; 

     private const string ERROR_INVALID_SQL_DATE_FORMAT = "Date must be between {0} and {1}!"; 
     private const string ERROR_DATE_ABOVE_MAX_FORMAT = "Date must be on or before {0}!"; 
     private const string ERROR_USAGE = @"Unable to determine date! Please enter a valid date as either: 
    MMDDYY 
    MMDDYYYY 
    MM/DD/YY 
    MM/DD/YYYY 

You may also use the following: 
    T (Today's date) 
    T + 1 (Today plus/minus a number of days) 
    T + 1w (Today plus/minus a number of weeks) 
    T + 1m (Today plus/minus a number of months) 
    T + 1y (Today plus/minus a number of years)"; 

     public static DateTime SqlMinDate 
     { 
      get { return sqlMinDate; } 
     } 

     public static DateTime SqlMaxDate 
     { 
      get { return sqlMaxDate; } 
     } 

     /// <summary> 
     /// Determine if user input string can become a valid date, and if so, returns it as a short date (MM/dd/yyyy) string. 
     /// </summary> 
     /// <param name="dateString"></param> 
     /// <returns></returns> 
     public static string ParseDateToString(string dateString) 
     { 
      return ParseDateToString(dateString, sqlMaxDate); 
     } 

     /// <summary> 
     /// Determine if user input string can become a valid date, and if so, returns it as a short date (MM/dd/yyyy) string. Date must be on or before maxDate. 
     /// </summary> 
     /// <param name="dateString"></param> 
     /// <param name="maxDate"></param> 
     /// <returns></returns> 
     public static string ParseDateToString(string dateString, DateTime maxDate) 
     { 
      if (null == dateString || 0 == dateString.Trim().Length) 
      { 
       return null; 
      } 

      dateString = dateString.ToLower(); 

      DateTime dateToReturn; 

      if (todayPlusOrMinus.IsMatch(dateString)) 
      { 
       dateToReturn = DateTime.Today; 

       int amountToAdd; 
       string unitsToAdd; 

       GetAmountAndUnitsToModifyDate(dateString, out amountToAdd, out unitsToAdd); 

       switch (unitsToAdd) 
       { 
        case "y": 
         { 
          dateToReturn = dateToReturn.AddYears(amountToAdd); 
          break; 
         } 
        case "m": 
         { 
          dateToReturn = dateToReturn.AddMonths(amountToAdd); 
          break; 
         } 
        case "w": 
         { 
          dateToReturn = dateToReturn.AddDays(7 * amountToAdd); 
          break; 
         } 
        default: 
         { 
          dateToReturn = dateToReturn.AddDays(amountToAdd); 
          break; 
         } 
       } 
      } 
      else 
      { 
       if (dateWithoutSlashies.IsMatch(dateString)) 
       { 
        /* 
        * It was too hard to deal with 3, 4, 5, and 7 digit date strings without slashes, 
        * so I limited it to 6 (MMDDYY) or 8 (MMDDYYYY) to avoid ambiguity. 
        * For example, 12101 could be: 
        *  1/21/01 => Jan 21, 2001 
        *  12/1/01 => Dec 01, 2001 
        *  12/10/1 => Dec 10, 2001 
        * 
        * Limiting it to 6 or 8 digits is much easier to deal with. Boo hoo if they have to 
        * enter leading zeroes. 
        */ 

        // All should parse without problems, since we ensured it was a string of digits 
        dateString = dateString.Insert(4, "/").Insert(2, "/"); 
       } 

       try 
       { 
        dateToReturn = DateTime.Parse(dateString); 
       } 
       catch 
       { 
        throw new FormatException(ERROR_USAGE); 
       } 
      } 

      if (IsDateSQLValid(dateToReturn)) 
      { 
       if (dateToReturn <= maxDate) 
       { 
        return dateToReturn.ToString(DATE_FORMAT); 
       } 

       throw new ApplicationException(string.Format(ERROR_DATE_ABOVE_MAX_FORMAT, maxDate.ToString(DATE_FORMAT))); 
      } 

      throw new ApplicationException(String.Format(ERROR_INVALID_SQL_DATE_FORMAT, SqlMinDate.ToString(DATE_FORMAT), SqlMaxDate.ToString(DATE_FORMAT))); 
     } 

     /// <summary> 
     /// Converts a string of the form: 
     /// 
     /// "T [+-] \d{1,4}[dwmy]" (spaces optional, case insensitive) 
     /// 
     /// to a number of days/weeks/months/years to add/subtract from the current date. 
     /// </summary> 
     /// <param name="dateString"></param> 
     /// <param name="amountToAdd"></param> 
     /// <param name="unitsToAdd"></param> 
     private static void GetAmountAndUnitsToModifyDate(string dateString, out int amountToAdd, out string unitsToAdd) 
     { 
      GroupCollection groups = todayPlusOrMinus.Match(dateString).Groups; 

      amountToAdd = 0; 
      unitsToAdd = "d"; 

      string amountWithPossibleUnits = groups[1].Value; 
      string possibleUnits = groups[2].Value; 

      if (null == amountWithPossibleUnits || 
       0 == amountWithPossibleUnits.Trim().Length) 
      { 
       return; 
      } 

      // Strip out the whitespace 
      string stripped = Regex.Replace(amountWithPossibleUnits, @"\s", ""); 

      if (null == possibleUnits || 
       0 == possibleUnits.Trim().Length) 
      { 
       amountToAdd = Int32.Parse(stripped); 
       return; 
      } 

      // Should have a parseable integer followed by a units indicator (d/w/m/y) 
      // Remove the units indicator from the end, so we have a parseable integer. 
      stripped = stripped.Remove(stripped.LastIndexOf(possibleUnits)); 

      amountToAdd = Int32.Parse(stripped); 
      unitsToAdd = possibleUnits; 
     } 

     public static bool IsDateSQLValid(string dt) { return IsDateSQLValid(DateTime.Parse(dt)); } 

     /// <summary> 
     /// Make sure the range of dates is valid for SQL Server 
     /// </summary> 
     /// <param name="dt"></param> 
     /// <returns></returns> 
     public static bool IsDateSQLValid(DateTime dt) 
     { 
      return (dt >= SqlMinDate && dt <= SqlMaxDate); 
     } 
    } 
} 

L'unico esempio nella vostra lista che potrebbe essere difficile sarebbe "Lo scorso giugno", ma si può solo calcolare la stringa da passare da capire quanti mesi sono passati dal giugno scorso.

int monthDiff = (DateTime.Now.Month + 6) % 12; 

if(monthDiff == 0) monthDiff = 12; 
string lastJuneCode = string.Format("T - {0}m", monthDiff); 

Naturalmente, che ti dipenderà dalla precisione della funzione AddMonths di DateTime, e non ho davvero provato casi limite per questo. Dovrebbe darti un DateTime lo scorso giugno e potresti usarlo per trovare il primo e l'ultimo del mese.

Tutto il resto dovrebbe essere abbastanza facile da mappare o analizzare con espressioni regolari.Per esempio:

  • scorsa settimana => "t - 1W"
  • Ieri => "t - 1d"
  • scorso anno => "t - 1A"
  • settimana successiva => "t + 1W"
  • domani => "t + 1d"
  • anno successivo => "t + 1A"
0

C'è un bug nella risposta di Piotr Czapla:

012.
new RegexDateTimePattern (
      @"([2-9]\d*) *h(ours)? +ago", 
      delegate (Match m) { 
       var val = int.Parse(m.Groups[1].Value); 
       return DateTime.Now.AddMonths(-val); 
      } 
     ), 

AddMonths viene utilizzato al posto di AddHours().

PS: Non posso commentare la sua risposta a causa dei bassi punti del forum. Ho già perso tempo nel debugging del motivo per cui rimuove 5 giorni quando provo con "5 ore fa".