2009-05-20 8 views
9

C#에서 winforms 응용 프로그램에 퍼지 날짜 컨트롤을 구현하고 있습니다. 퍼지 날짜C# .NET에서 퍼지 날짜 시간 선택 컨트롤?

  • 작년 6 월
  • 2 시간 전
  • 2 개월 전
  • 지난 주
  • 어제
  • 작년
처럼 퍼지 값을 가질 수 있어야한다

"퍼지"날짜 시간 선택기의 샘플 구현이 있습니까? 이러한 제어를 구현하는

모든 아이디어를

PS

을 감상 할 수있다 : 나는 약 herehere, 정말 개발에 대한 아이디어와 영감을 찾고 퍼지 날짜 알고리즘을 알고 말씀입니다 등 컨트롤

+1

부작용으로 모든 사례를 다루는 코드가 있다고 가정하고 사용자가 입력 할 수있는 내용을 어떻게 알 수 있습니까? 작업 완료 시간 측면에서 어제 타이핑을 사용하는 것보다 어떨까요? 나는 왜 그런 통제가 필요하다고 생각하는지 알고 싶습니다. – RichardOD

+0

필요하십니까? - 컨트롤은 입력/값이 타이머 기반 엔티티에서 가져 오는 시나리오에서 사용됩니다. 간단한 예 : "언제 케이크를 전자 레인지에 보관 했습니까?" CurrentTime에서 25 분을 수동으로 계산하는 것보다 "25 분 전"(25 분은 전자 레인지 타이머에서 읽음)으로 입력하는 것이 훨씬 쉽습니다. 퍼지 날짜 시간 선택기는 수동으로 날짜 시간 값을 계산하는 것보다 퍼지 값을 입력하는 것이 더 쉬운 경우와 같은 시나리오에 사용됩니다. Phew .. 그게 길었습니다. – abhilash

답변

21

구문 분석은 매우 쉽습니다. regexps와 날짜 계산의 무리로 구현 될 수 있습니다.

아래 샘플은 필요에 따라 쉽게 확장 할 수 있습니다. 나는 약을 테스트 한 및 다음과 같은 문자열을 적어도 작동합니다,

  • 다음 달, 내년
  • 다음 4 월, 다음 삼일
  • 6 일 전 5 시간 전
  • 내일, 어제
  • 지난 해, 지난 달,
  • 지난 다음, 화 금
  • 작년 6 월, 내년 5 월,
  • ,
  • 2008년 1월 01 2009 년 1 월
  • 2019년 6월, 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

'@ "내일의''에 대한 버그가 있는데, 오늘''',''DateTime.Now.AddDays (')를 반환해야합니다. –

+0

@FreshCode 해당 버그를 발견해 주셔서 감사합니다. (나는 목록을 바로 잡았습니다) –

2

우리는 비슷한 컨트롤을 가지고 있습니다. 콤보 상자 목록을 추가하면됩니다.

PeriodSelector : [날짜 선택기]에서

  • [numericupdown] 개월 전
  • [numericupdown] 시간 전
  • 지난 주
  • 어제
  • 주 [[날짜 선택기]까지 날짜 픽커]
  • 일 [datepicker]
  • ...

그리고 용도에 맞는 선택을하십시오.

텍스트를 구문 분석 한 다음 구현하는 것이 훨씬 쉽습니다. 계산은 오히려 간단합니다.

기간을 선택하는 것이 중요합니다. 작년은 2008 년 1 월에서> 2008 년 12 월까지를 의미합니다. 2 시간 전부터 지금까지 - 2 시간. 기타

3

하나 사용자가 사용하는 시스템에서 다음과 같이 날짜를 입력 할 수 있습니다.

  • T // 오늘
  • T + 1 // 오늘 플러스/마이너스 일 수
  • T + 1w // 오늘 플러스/마이너스 몇 주
  • T + 1m // 오늘 플러스/마이너스 개월 수
  • T + (1Y) // 오늘 플러스/마이너스 년의 수는

그들은 그것을 좋아하는 것, 그리고 우리의 응용 프로그램에 요청, 그래서 나는 다음과 같은 코드를 내놓았다. ParseDateToString은 위 양식 중 하나의 문자열과 몇 가지 다른 문자열을 취하여 날짜를 계산하고 "MM/DD/YYYY"형식으로 반환합니다. 실제 DateTime 객체를 반환하고 몇 시간, 몇 분, 초 또는 원하는대로 지원을 추가하는 것은 쉽습니다.

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

"작년 6 월"이 될 것입니다 어려울 수 있습니다,하지만 당신은 그냥 지난 6 월 이후되었습니다 몇 개월 파악에 의해 전달하는 문자열을 계산할 수있는 목록에있는 유일한 예.

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

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

은 물론, 그 날짜 시간의 AddMonths 기능의 정확성에 의존하는 것이고, 난 정말 그것에 대해 가장자리 케이스를 테스트하지 않았습니다. 그것은 지난 6 월 DateTime을 제공해야하며, 그 달의 첫 번째와 마지막을 찾기 위해 사용할 수 있습니다.

다른 모든 것은 정규 표현식으로 매핑하거나 구문 분석하는 것이 매우 쉬워야합니다.예를 들면 : "- 1w t"

  • 어제 => "t - 1D"

    • 지난 주 =>
    • 지난해 => "t - 1Y"=> "t
    • 다음 주 + 1w "
    • 내일 =>"t + 1D "
    • 내년 =>"t + 1Y "
    • 표트르 Czapla의 대답에 버그가
  • 0

    :

    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 일을 제거하는 이유를 이미 디버깅하는 데 시간을 낭비했습니다.