2009-05-20 9 views
9

मैं Winforms एप्लिकेशन के लिए सी # में एक फ़ज़ी डेट नियंत्रण लागू कर रहा हूं। फजी दिनांकसी # .NET में फ़ज़ी डेट टाइम पिकर कंट्रोल?

  • पिछले साल जून में
  • 2 घंटे पहले
  • 2 महीने पहले
  • पिछले हफ्ते
  • कल
  • पिछले साल
तरह फजी मान लेने के लिए सक्षम होना चाहिए

और

क्या "फ़ज़ी" दिनांक समय पिकर्स का कोई नमूना कार्यान्वयन है?

किसी भी तरह के एक नियंत्रण लागू करने के लिए विचारों

सराहना की जाएगी

पुनश्च: मैं बोली जाने वाली फजी तारीख एल्गोरिथ्म के बारे में पता कर रहा हूँ के बारे में here और here, मैं वास्तव में विकसित करने के लिए किसी भी विचार और प्रेरणा रहा हूँ इस तरह के एक नियंत्रण

+1

एक साइड सवाल के रूप में, माना जाता है कि आपके पास आपके सभी मामलों को शामिल करने वाला कोड था, उपयोगकर्ता को पता चलेगा कि वे क्या टाइप कर सकते हैं? कार्य पूरा करने के समय के मामले में, कल टाइपिंग पिकर का उपयोग करने से जल्दी कैसे टाइप किया जाएगा? मुझे यह जानने में बहुत दिलचस्पी होगी कि आपको ऐसा क्यों लगता है कि आपको इस तरह के नियंत्रण की आवश्यकता है? – RichardOD

+0

आवश्यकता है? - वैसे नियंत्रण उन परिदृश्यों में उपयोग किया जाएगा जहां इनपुट/मूल्य टाइमर आधारित इकाइयों से प्राप्त किया जाएगा। मामूली उदाहरण: "आपने माइक्रोवेव में केक कब रखा?" मुझे लगता है कि 25 मिनट पहले "25 मिनट पहले" [25 माइक्रोवेव टाइमर पर पढ़ना] दर्ज करना बहुत आसान है। फ़ज़ी डेट टाइम पिकर ऐसे परिदृश्यों का उपयोग किया जाएगा जब मैन्युअल रूप से दिनांक समय मानों की गणना करने से फ़ज़ी मानों को दर्ज करना अधिक आसान होगा। पुhew .. वह लंबा था .. – abhilash

उत्तर

21

पार्सिंग काफी आसान है। इसे regexps और कुछ तारीख गणना के गुच्छा के रूप में लागू किया जा सकता है।

नीचे दिए गए नमूने को आपकी आवश्यकताओं के अनुरूप आसानी से बढ़ाया जा सकता है। मैं मोटे तौर पर यह परीक्षण किया है और यह निम्नलिखित स्ट्रिंग्स के लिए कम से कम काम करता है:,

  • अगले महीने, अगले साल
  • अगले 4 महीने, अगले 3 दिनों
  • 3 दिन पहले, 5 घंटे पहले
  • कल, कल
  • पिछले साल, पिछले माह,
  • पिछले मंगल, शुक्र अगले
  • पिछले जून, अगले मई,
  • जनवरी 2008, 01 जनवरी 2009,
  • जून 2019, 2009/01/01

सहायक वर्ग:

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

प्रयोग उदाहरण:

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

आपको प्रॉप्स, अच्छा महोदय! –

+0

कल "@" के लिए एक बग प्रतीत होता है, जो आज '' "जैसा ही है, जहां यह' वापसी दिनांक होना चाहिए.अब। एडडेज़ (1) '। –

+0

@FreshCode उस बग को खोजने के लिए धन्यवाद। (मैंने लिस्टिंग को सही किया है) –

2

हमारे पास एक समान नियंत्रण है। हम सिर्फ अपनी पसंद चुनने के लिए कॉम्बो बॉक्स की एक सूची जोड़ते हैं।

PeriodSelector:

  • [datepicker] से [datepicker]
  • [NumericUpDown] महीने पहले
  • [NumericUpDown] घंटे पहले
  • पिछले हफ्ते
  • कल
  • सप्ताह [तक डेटपिकर]
  • दिन [डेटपिकर]
  • ...

और केवल अपने उद्देश्य के लिए समझने वाले विकल्पों को लें।

टेक्स्ट को पार्स करने के बाद इसे कार्यान्वित करना बहुत आसान है। गणना अपेक्षाकृत सरल है।

यह देखना महत्वपूर्ण है कि आप अवधि चुन रहे हैं। पिछले साल जनवरी 2008 से> दिसंबर 2008 का मतलब है। दो घंटे पहले अब तक - 2 घंटे। आदि

3

में से एक हमारे उपयोगकर्ता उपयोग करने वाले सिस्टम उन्हें इस तरह की तिथियां दर्ज करने की अनुमति देते हैं:

  • टी // आज
  • टी + 1 // आज प्लस/शून्य से दिनों की संख्या
  • टी + 1 माह // आज प्लस/शून्य से कुछ सप्ताह में
  • टी + 1 मी // आज प्लस/माइनस महीनों के एक नंबर
  • टी + 1 वर्ष // आज प्लस/शून्य से कई वर्षों से

वे इसे पसंद करने लगते हैं, और हमारे अनुप्रयोग में यह अनुरोध किया है, इसलिए मैं निम्नलिखित कोड के साथ आया था। ParseDateToString उपरोक्त रूपों में से एक की एक स्ट्रिंग लेगा, साथ ही कुछ अन्य, तिथि की गणना करें, और इसे "एमएम/डीडी/वाई वाईवायवाई" प्रारूप में वापस कर दें। वास्तविक डेटटाइम ऑब्जेक्ट को वापस करने के साथ-साथ घंटों, मिनटों, सेकंड, या जो भी आप चाहते हैं, के लिए समर्थन जोड़ने के लिए इसे बदलने में काफी आसान है।

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

अपनी सूची में केवल उदाहरण है कि मुश्किल "पिछले साल जून में होगा" हो सकता है, लेकिन आप केवल स्ट्रिंग की गणना पता लगाना कि कितने महीने यह पिछले साल जून के बाद से किया गया है द्वारा में पारित करने के लिए कर सकता है।

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

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

बेशक, कि दिनांक समय के AddMonths समारोह की सटीकता पर निर्भर करेंगे, और मैं वास्तव में उस के लिए किनारे मामलों का परीक्षण नहीं किया। यह आपको पिछले जून में डेटटाइम देना चाहिए, और आप इसका उपयोग केवल पहले और आखिरी महीने को खोजने के लिए कर सकते हैं।

बाकी सब कुछ नियमित अभिव्यक्तियों के साथ मानचित्र या पार्स करने के लिए काफी आसान होना चाहिए।उदाहरण के लिए: "- 1 माह टी"

  • कल => "टी - 1 दिन"

    • पिछले हफ्ते =>
    • पिछले साल => "टी - 1 वर्ष"
    • अगले सप्ताह => "टी + 1 माह "
    • कल =>" टी +1 "
    • अगले साल =>" t + 1 वर्ष "
  • 0

    वहाँ पायोत्र 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 को AddHours() के बजाय उपयोग किया जाता है।

    पीएस: मैं कम मंच बिंदुओं के कारण उनके उत्तर पर टिप्पणी नहीं कर सकता। मैं पहले से ही "5 घंटे पहले" कोशिश करते समय 5 दिनों को हटा देता हूं, इसे डीबग करने पर समय बर्बाद कर चुका हूं।

    संबंधित मुद्दे