【问题标题】:What is the C# Syntax for something like date.IsWithIn(x months).Of(Comparison Date)?date.IsWithIn(x 个月).Of(Comparison Date) 之类的 C# 语法是什么?
【发布时间】:2015-02-27 21:52:53
【问题描述】:

标题有点古怪,但这就是问题所在。我正在使用 C#。我正在尝试提出几种 DateTime 扩展方法。在思考的同时,我想知道我编写如下代码需要什么语法:

DateTime comparisonDate = DateTime.Now.AddMonths(-3);

if( DateTime.Now.IsWithIn(3).Of(comparisonDate) ) ....

我以前写过扩展方法,但我不确定如何编写这样的东西。 “IsWithIn”将是一种方法......但这会返回一个表达式,而“Of”方法将是 Expression 类的扩展方法吗?

编辑 1

还在想这个。我想知道这种方法虽然可读,但是否过于复杂。我的第一个修订版只是将@Wai Ha Lee 的内容调整为 DateTimeExtensions 类。我将重构它并继续迭代。这尖叫策略模式,但我还没有适应它。现在也没有“Of”方法,方法名称似乎有意义……至少在今天。一个月后?我不确定。

我还想到了另一种编写此代码的方法。我想我还在做梦,但就是这样:

date.IsWithIn(1).Days().Of(comparisonDate);
date.IsWithIn(1).Months().Of(comparisonDate);
date.IsWithIn(1).Years().Of(comparisonDate);

但除此之外,这是我的修订版,它只是一个没有方法名称链接的 DateTime 扩展。

public class Program
{
    static void Main(string[] args)
    {
        DateTime now = DateTime.Now;
        DateTime past = new DateTime(2015, 1, 15);
        DateTime future = new DateTime(2015, 3, 15);
        DateTime comparison = now.AddDays(-2);
        int interval = 1;
        DateInterval di = DateInterval.Days;

        Console.WriteLine(
            string.Format("Now, {0}, is with in {1} {2} of {3} is {4}",
                now.ToShortDateString(),
                interval.ToString(),
                di.ToString(),
                comparison.ToShortDateString(),
                now.IsDateWithinXRangeOfAnotherDate(interval, di, comparison).ToString())
        );

        Console.ReadLine();
    }
}

  public enum DateInterval
    {
        Days,
        Months,
        Years
    }

public static class DateTimeExtensions
{
    public static bool IsDateWithinXRangeOfAnotherDate(this DateTime date, int interval, DateInterval dateInterval, DateTime comparisonDate)
    {
        DateTime _min = comparisonDate;
        DateTime _max = comparisonDate;

        switch(dateInterval)
        {
            case DateInterval.Days:
                _min = _min.AddDays(-interval);
                _max = _max.AddDays(interval); 
                Console.WriteLine(
                    string.Format("Min Date is {0} Max Date is {1}",
                        _min.ToShortDateString(),
                        _max.ToShortDateString()));
                break;
            case DateInterval.Months:
                _min = _min.AddMonths(-interval);
                _max = _max.AddMonths(interval);
                Console.WriteLine(
                    string.Format("Min Date is {0} Max Date is {1}",
                        _min.ToShortDateString(),
                        _max.ToShortDateString()));
                break;
            case DateInterval.Years:
                _min = _min.AddYears(-interval);
                _max = _max.AddYears(interval);
                Console.WriteLine(
                    string.Format("Min Date is {0} Max Date is {1}",
                        _min.ToShortDateString(),
                        _max.ToShortDateString()));
                break;
        }

        return _min <= date && date <= _max;
    }        
}

编辑 2

修订:

date.IsWithIn(1).Days().Of(comparisonDate);
date.IsWithIn(1).Months().Of(comparisonDate);
date.IsWithIn(1).Years().Of(comparisonDate);

date.IsWithIn(1.Days()).Of(comparisonDate);
date.IsWithIn(1.Months()).Of(comparisonDate);
date.IsWithIn(1.Years()).Of(comparisonDate);

看了一些 FluentTime 之后,我注意到作者使用了几个我什至不知道存在的方法和类。一方面,他使用了 TimeSpan.FromDays 方法。他可能重载了 + 符号,因为在代码的另一点,他只是将时间跨度添加到日期。鉴于 TimeSpan 的工作方式,我可能只能实现 1.Days() 部分......我认为这就是我真正需要的。

我会一直玩弄这一切,直到我弄明白为止。我可以只使用 FluentTime 库,但是对于我需要它的东西来说,它是矫枉过正的,因为库也处理时间。我对日期范围比较非常感兴趣。 After()、Before()、IsBetween()、IsWithIn 等方法。我已经实现了前 3 个。这个问题的重点是回答最后一个问题。

编辑 3 - 已解决!

这个问题更像是一个代码练习而不是实用性。最终,Jon Skeet 关于必须创建自定义类型是正确的。解决方案分解为以下摘要:

自定义类:FluentDateTime 已创建 3 个 int 扩展方法 - 天、月、年。这些每个都返回一个 FluentDateTime 类。 1 DateTime 扩展方法 - IsWithIn 采用 FluentDateTime 参数

我想强调的是,这是一笔不小的费用……但是,无论如何,这里是代码。

public class FluentDateTime
    {

        public enum DateInterval
        {
            Days,
            Months,
            Years
        }

        private DateTime _lowDate;
        private DateTime _highDate;
        public DateTime BaseDate { get; set; }
        public DateInterval Interval { get; set; }
        public int Increment { get; set; }


        public bool Of(DateTime dt)
        {
            _lowDate = dt;
            _highDate = dt;

            if(this.Interval == DateInterval.Days)
            {
                _lowDate = _lowDate.AddDays(-this.Increment);
                _highDate = _highDate.AddDays(this.Increment);
            }
            else if (this.Interval == DateInterval.Months)
            {
                _lowDate = _lowDate.AddMonths(-this.Increment);
                _highDate = _highDate.AddMonths(this.Increment);
            }
            else
            {
                _lowDate = _lowDate.AddYears(-this.Increment);
                _highDate = _highDate.AddYears(this.Increment);
            }

            Console.WriteLine(
                string.Format("{0} <= {1} <= {2}", _lowDate.ToShortDateString(), BaseDate.ToShortDateString(), _highDate.ToShortDateString()
                ));

            return (_lowDate < BaseDate && BaseDate < _highDate) || (_lowDate.Equals(BaseDate) || _highDate.Equals(BaseDate) );            
        }

    }

// 日期时间扩展

public static FluentDateTime IsWithIn(this DateTime date, FluentDateTime fdtParams)
{
    fdtParams.BaseDate = date;
    return fdtParams;
}

//INT 扩展

 public static FluentDateTime Days(this int inc)
        {
            FluentDateTime fdt = new FluentDateTime();
            fdt.Interval = FluentDateTime.DateInterval.Days;
            fdt.Increment = inc;
            return fdt;
        }

        public static FluentDateTime Months(this int inc)
        {
            FluentDateTime fdt = new FluentDateTime();
            fdt.Interval = FluentDateTime.DateInterval.Months;
            fdt.Increment = inc;
            return fdt;
        }

        public static FluentDateTime Years(this int inc)
        {
            FluentDateTime fdt = new FluentDateTime();
            fdt.Interval = FluentDateTime.DateInterval.Years;
            fdt.Increment = inc;
            return fdt;
        }

//测试程序

DateTime testDate1 = new DateTime(2015, 3, 3);
            DateTime testDate2 = new DateTime(2015, 3, 4);
            Console.WriteLine(
                string.Format("{0} is within 5 days of {1}? {2} (should be true)",
                    testDate1.ToShortDateString(), testDate2.ToShortDateString(), testDate1.IsWithIn(5.Days()).Of(testDate2)
                ));

            testDate1 = new DateTime(2015, 3, 1);
            testDate2 = new DateTime(2015, 3, 7);
            Console.WriteLine(
                string.Format("{0} is within 3 days of {1}? {2} (should be false)",
                    testDate1.ToShortDateString(), testDate2.ToShortDateString(), testDate1.IsWithIn(3.Days()).Of(testDate2)
                ));

            testDate1 = new DateTime(2015, 3, 3);
            testDate2 = new DateTime(2015, 4, 1);
            Console.WriteLine(
                 string.Format("{0} is within 1 month of {1}? {2} (should be true)",
                     testDate1.ToShortDateString(), testDate2.ToShortDateString(), testDate1.IsWithIn(1.Months()).Of(testDate2)
                 ));


            testDate1 = new DateTime(2015, 3, 3);
            testDate2 = new DateTime(2015, 6, 1);
            Console.WriteLine(
                string.Format("{0} is within 2 month of {1}? {2} (should be false)",
                    testDate1.ToShortDateString(), testDate2.ToShortDateString(), testDate1.IsWithIn(2.Months()).Of(testDate2)
                ));

【问题讨论】:

  • 是的,您返回具有后续方法的自定义类型。
  • 您可以检查现有框架作为示例 - github.com/duelinmarkers/FluentTime
  • 如何使用扩展方法即时生成TimeSpan,以便您可以键入3.Months() 并让IsWithin 接受时间跨度作为参数?
  • @mbx 我认为这是一个明智的想法,尽管我想您仍然有不满意的属性 date.IsWithin(3.Months()) 是一个具有荒谬结果的有效表达式(直到您添加部分,我的意思是)。
  • @mbx - 正如@emodendroket 和@ScottChamberlain 指出的那样,没有参考点,3 个月是未定义的。我的方法基于参考点计算范围,以便明确定义 x 个月。

标签: c# datetime lambda extension-methods


【解决方案1】:

IsWithin 必须返回某种表示值范围的类型,记住“中心”和范围大小。现在Of 可能是它的扩展方法,或者很容易成为普通的实例方法,因为您将自己编写类型。

请注意,3 在 3 天、3 小时或其他方面并不清楚。你应该弄清楚你想如何指定它。您可以使用 TimeSpan 而不仅仅是 int,或者使用单独的 IsWithinDaysIsWithinHours 等方法。

【讨论】:

  • 我没有这样做。有点怪异的 Skeet 这么快就跳了起来。
  • @jason:这是一个 C# 语言问题和一个日期/时间问题的混合体——有什么不喜欢的? :) 另一方面,你在 DateTime 上而不是在 Noda Time 上做这件事,我感到很受伤......
【解决方案2】:

一些事情:

  • 我假设 within {some time} of 表示{some time} 任一侧 - 如果它只是指之后,则代码要简单得多。保持我的假设意味着.IsWithin(x).Of 是可交换的,即a.IsWithin(x).Of(b) == b.IsWithin(x).Of(a)
  • DateTimeRange 采用 Func&lt;DateTime, int, DateTime&gt; 以避免重复代码(尽管如果我的假设是,则不会有一个范围
  • 我不使用 TimeSpan because

    TimeSpan 结构用来衡量持续时间的最大时间单位是一天。

  • 毫不夸张地说,这是有史以来最糟糕的事情,我有点讨厌自己发布此消息。我永远不会在专业环境中编写这样的代码。我这样做只是为了(几乎)满足@jason 的要求(我将IsWithIn 重命名为IsWithin)。
用法
public static void Main()
{
    var now = DateTime.Now;
    var comparisonDate = now.AddMonths(-2);

    bool within1Month = now.IsWithin(months: 1).Of(comparisonDate); // false
    bool within2Months = now.IsWithin(months: 2).Of(comparisonDate); // true
    bool within3Months = now.IsWithin(months: 3).Of(comparisonDate); // true
}

日期时间扩展:

public static class DateTimeExtensions
{
    /// <summary>
    /// <para>Specify exactly one of milliseconds, seconds, minutes, hours, days, months, or years.</para>
    /// <para>Uses the first nonzero argument in the order specified.</para>
    /// </summary>
    public static DateTimeRange IsWithin(
        this DateTime dateTime,
        int milliseconds = 0, int seconds = 0, int minutes = 0, int hours = 0,
        int days = 0, int months = 0, int years = 0)
    {
        if ( milliseconds != 0 )
            return new DateTimeRange(dateTime, (_dateTime, _value) => _dateTime.AddMilliseconds(_value), milliseconds);
        if ( seconds != 0 )
            return new DateTimeRange(dateTime, (_dateTime, _value) => _dateTime.AddSeconds(_value), seconds);
        if ( minutes != 0 )
            return new DateTimeRange(dateTime, (_dateTime, _value) => _dateTime.AddMinutes(_value), minutes);
        if ( hours != 0 )
            return new DateTimeRange(dateTime, (_dateTime, _value) => _dateTime.AddHours(_value), hours);
        if ( days != 0 )
            return new DateTimeRange(dateTime, (_dateTime, _value) => _dateTime.AddDays(_value), days);
        if ( months != 0 )
            return new DateTimeRange(dateTime, (_dateTime, _value) => _dateTime.AddMonths(_value), months);
        if ( years != 0 )
            return new DateTimeRange(dateTime, (_dateTime, _value) => _dateTime.AddYears(_value), years);

        throw new ArgumentException("At least one value must be nonzero");
    }
}

日期时间范围:

/// <summary>
/// Represents a range between two DateTime values
/// </summary>
public struct DateTimeRange
{
    private DateTime _min;
    private DateTime _max;

    public DateTime Min { get { return _min; } }
    public DateTime Max { get { return _max; } }

    /// <summary>
    /// Uses generator to get the start and end dates of this range.
    /// </summary>
    /// <param name="middle">The midpoint of this DateTimeRange</param>
    /// <param name="generator">Generates the min and max dates from the midpoint and a parameter</param>
    public DateTimeRange(DateTime middle, Func<DateTime, int, DateTime> generator, int value)
    {
        _min = generator(middle, -value);
        _max = generator(middle, +value);
    }

    public bool Of(DateTime dateTime)
    {
        return _min <= dateTime && dateTime <= _max;
    }
}

我觉得有点惭愧,因为这是@jonskeet 回答的问题的唯一其他答案。

【讨论】:

  • TimeSpan 可以做的最大度量单位是一天的原因是你不能说一个月有多大,除非你知道你在哪个月份。否则你必须有一个@ 987654333@ 方法签名可靠地计算出 13 个月内有多少秒。
  • @ScottChamberlain:我想 - 1 个月 TimeSpan 在没有参考点的情况下定义不明确(例如,从今天(2 月 27 日)开始的 1 个月(向前)是 28 天;从 1 个月(向后)开始今天是 31 天,等等)。
  • @WaiHaLee 感谢您发布此信息。在我看来,此代码的唯一问题是您指出的 DateTimeExtensions 方法。我将对这个问题进行编辑,这可能会给我们一些其他想法。
  • @jason - 我绝对同意这一点。我想不出更好的方法来让用户指定他们传入的数字类型。我确实考虑过添加enum TimeIntervalType { Milliseconds, Seconds, ... Months, Years },但你仍然不得不从 修改DateTime 的枚举(除非您使用反射来获取DateTime 中的方法,然后手动调用它——这会更糟)。
猜你喜欢
  • 2015-05-08
  • 1970-01-01
  • 2019-08-07
  • 2010-12-30
  • 1970-01-01
  • 2014-03-15
  • 2019-09-08
  • 1970-01-01
  • 2012-08-15
相关资源
最近更新 更多