代码之家  ›  专栏  ›  技术社区  ›  abhilash

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

  •  9
  • abhilash  · 技术社区  · 16 年前

    我正在为WinForms应用程序在C中实现模糊日期控制。模糊日期应该能够采用模糊值,比如

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

    诸如此类

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

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

    聚苯乙烯 : 我知道所说的模糊日期算法 here here 我真的在寻找发展这种控制的任何想法和灵感。

    4 回复  |  直到 9 年前
        1
  •  22
  •   Jim Liddell    14 年前

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

    下面的示例可以很容易地扩展以满足您的需要。 我粗略地测试过它,它至少适用于以下字符串:

    • 下个月,下一年,
    • 接下来4个月,接下来3天
    • 3天前,5小时前
    • 明天,昨天
    • 去年,上个月,
    • 上周二,下周五
    • 去年六月,明年五月,
    • 2008年1月,2009年1月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";
    
        2
  •  3
  •   Chris Doggett    16 年前

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

    • 今日/今日
    • T+1//今天加/减几天
    • T+1W//今天加/减几周
    • T+100万//今天加/减几个月
    • T+1Y//今天加/减几年

    他们似乎喜欢它,并在我们的应用程序中请求它,所以我想出了以下代码。ParseDateToString将获取上面其中一个表单的字符串,再加上其他一些表单,计算日期,并以“mm/dd/yyyy”格式返回。更改它以返回实际的日期时间对象,以及添加对小时、分钟、秒或任何您想要的内容的支持,这已经足够简单了。

    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函数的准确性,我还没有真正测试过边缘情况。它应该给你一个去年六月的日期时间,你可以用它来查找这个月的第一个和最后一个。

    其他的东西应该很容易用正则表达式进行映射或解析。例如:

    • 上周=>“T-1W”
    • 昨天=>“T-1d”
    • 去年=>“T-1Y”
    • 下周=>“T+1W”
    • 明天=>“T+1d”
    • 明年=>“T+1Y”
        3
  •  2
  •   Carra    16 年前

    我们也有类似的控制权。我们只需添加组合框列表-控件来选择您的选项。

    PeriodSelector:

    • 从[datepicker]到[datepicker]
    • [NumericUpDown]个月前
    • [NumericUpDown]小时前
    • 上周
    • 昨天
    • 周[日期选取器]
    • 天[日期选取器]

    只需做出对你的目标有意义的选择。

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

    重要的是你要选择 时期 . 去年是指2008年1月至2008年12月。两小时前从现在到现在-2小时。等。

        4
  •  0
  •   valentinvs    9 年前

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

    附言:我不能对他的回答发表评论,因为论坛分数很低。我已经把时间浪费在调试上了,为什么当我尝试“5小时前”时它会删除5天。