C#.NET中的模糊日期时间选择器控件?

我正在C#中为winforms应用程序实现模糊日期控件。 模糊日期应该能够采用类似的模糊值

  • 去年六月
  • 2小时前
  • 2个月前
  • 上个星期
  • 昨天
  • 去年

等等

是否有“模糊”日期时间选择器的任何示例实现?

任何实现这种控制的想法都将受到赞赏

PS :我知道这里和这里所说的模糊日期算法,我真的在寻找开发这种控制的任何想法和灵感

解析非常简单。 它可以实现为一堆regexp和一些日期计算。

以下示例可以轻松扩展以满足您的需求。 我已经粗略测试了它,它至少适用于以下字符串:

  • 下个月,明年,
  • 接下来的4个月,接下来的3天
  • 3天前,5小时前
  • 明天,昨天
  • 去年,上个月,
  • 最后一个星期二,下一个星期五
  • 去年6月,明年5月
  • 2008年1月1日,2009年1月1日,
  • 2019年6月,2009年1月1日

助手类:

class FuzzyDateTime { static List dayList = new List() { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; static List parsers = new List() { 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"; 

我们的用户使用的系统之一允许他们输入如下日期:

  • T //今天
  • T + 1 //今天加/减几天
  • T + 1w //今天加/减数周
  • T + 1m //今天加/减几个月
  • T + 1y //今天加/减若干年

他们似乎喜欢它,并在我们的应用程序中请求它,所以我想出了以下代码。 ParseDateToString将采用上面一种forms的字符串,加上其他几种forms,计算日期,并以“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; } } ///  /// Determine if user input string can become a valid date, and if so, returns it as a short date (MM/dd/yyyy) string. ///  ///  ///  public static string ParseDateToString(string dateString) { return ParseDateToString(dateString, sqlMaxDate); } ///  /// 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. ///  ///  ///  ///  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))); } ///  /// 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. ///  ///  ///  ///  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)); } ///  /// Make sure the range of dates is valid for SQL Server ///  ///  ///  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); 

当然,这取决于DateTime的AddMonths函数的准确性,我还没有真正测试边缘情况。 它应该在去年六月给你一个DateTime,你可以用它来查找当月的第一个和最后一个。

其他所有内容都应该相当容易使用正则表达式进行映射或解析。 例如:

  • 上周=>“t - 1w”
  • 昨天=>“t - 1d”
  • 去年=>“t - 1y”
  • 下周=>“t + 1w”
  • 明天=>“t + 1d”
  • 明年=>“t + 1y”

我们有类似的控制。 我们只需添加一个combobox列表 – 控件来选择您的选择。

PeriodSelector:

  • 从[datepicker]到[datepicker]
  • [numericupdown]几个月前
  • [numericupdown]小时前
  • 上个星期
  • 昨天
  • 周[datepicker]
  • 日[datepicker]

只需采取对您的目的有意义的选择。

实现它然后解析文本要容易得多。 计算相当简单。

重要的是要看到你正在选择一个时期 。 去年意味着从2008年1月> 2008年12月。两个小时前从现在开始直到现在 – 2个小时。 等等。

Piotr Czapla的答案中有一个错误:

 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()。

PS:由于论坛积分较低,我无法对他的答案发表评论。 我已经浪费时间调试它,为什么它在5小时后用“5小时前”尝试删除。