我在C#中为winforms应用程序实现了一个模糊日期控件。模糊日期应该能够采用像
这样的模糊值之类的
“模糊”日期时间选择器是否有任何示例实现?
任何实现此类控制的想法都将受到赞赏
答案 0 :(得分:22)
解析非常简单。它可以实现为一堆正则表达式和一些日期计算。
以下示例可以轻松扩展以满足您的需求。 我已经粗略测试了它,它至少适用于以下字符串:
助手类:
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";
答案 1 :(得分:3)
我们的用户使用的系统之一允许他们输入日期:
他们似乎喜欢它,并在我们的应用程序中请求它,所以我想出了以下代码。 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);
}
}
}
列表中唯一可能很困难的例子是“去年六月”,但您可以通过计算自去年六月以来的月数来计算要传入的字符串。
int monthDiff = (DateTime.Now.Month + 6) % 12;
if(monthDiff == 0) monthDiff = 12;
string lastJuneCode = string.Format("T - {0}m", monthDiff);
当然,这取决于DateTime的AddMonths函数的准确性,我还没有真正测试边缘情况。它应该在去年六月给你一个DateTime,你可以用它来查找当月的第一个和最后一个。
其他所有内容都应该相当容易使用正则表达式进行映射或解析。例如:
答案 2 :(得分:2)
我们有类似的控制。我们只需添加一个组合框列表 - 控件来选择您的选择。
PeriodSelector:
只需选择合适的选择。
实现它然后解析文本要容易得多。计算相当简单。
重要的是要看到你正在选择期间。去年意味着从2008年1月起&gt; 2008年12月。两个小时前从现在开始直到现在 - 2个小时。等
答案 3 :(得分:0)
Piotr Czapla的答案中有一个错误:
let myFn = () => {
return new Promise((res, rej) => {
this.http.get('myurl')
.subscribe((success) => {
// do something here
res(success);
}, (error) => {
// show error here
rej(error);
});
});
}
使用AddMonths而不是AddHours()。
PS:由于论坛积分较低,我无法对他的答案发表评论。我已经浪费时间调试它为什么它在5小时后用“5小时前”进行删除。