使用 NodaTime(或替代方法)比较期间的最佳方法
Best way to compare Periods in using NodaTime (or alternative)
我需要一个能够存储在数据库中的相对 min/max 日期验证,以便为每个客户定制应用程序。我认为 NodaTime.Period
是最佳选择,因为它能够指定年份。但是,NodaTime.Period
没有提供将自己与另一个时期进行比较的方法。
为此验证提供的示例数据:
- 最低年龄为 18 岁。
- 最大年龄 o 100 岁。
- 最短销售时间为 1 个月
- 最长销售期限为 3 个月
- 广告活动最少 7 天
(注:目前要求年/月/日不合并验证)
验证是:
public Period RelativeMinimum { get; set; }
public Period RelativeMaximum { get; set; }
给定用户输入的日期(和现在):
var now = new LocalDate(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
var userValue = new LocalDate(date.Year, date.Month, date.Day);
var difference = Period.Between(userValue, now);
我比较了:
if(RelativeMinimum != null && difference.IsLessThan(RelativeMinimum))))
{
response.IsValid = false;
response.Errors.Add(MinimumErrorMessage);
}
正在使用扩展 class:
public static class PeriodExtensions
{
public static bool IsLessThan(this Period p, Period p2)
{
return (p.Years < p2.Years) || (p.Years == p2.Years && p.Months < p2.Months) || (p.Years == p2.Years && p.Months == p2.Months && p.Days < p2.Days);
}
public static bool IsGreaterThan(this Period p, Period p2)
{
return (p.Years > p2.Years) || (p.Years == p2.Years && p.Months > p2.Months) || (p.Years == p2.Years && p.Months == p2.Months && p.Days > p2.Days);
}
}
虽然这种方法有效,但考虑到我的测试条件,我想知道为什么 @jon-skeet 没有实现这个,并且立即不得不担心我遗漏了什么以及我应该使用什么替代方案?
周期不可比的主要原因是它们可以包含可变长度的组件。
两个为期一个月的时间段的天数不一定相同。例如,哪个更大:1 个月还是 30 天?如果月份是一月,则超过 30 天。如果月份是二月,则少于 30 天。
年份也是如此。有的是365天,有的是366天。
当然,这一切都假设您使用的是公历。野田时间支持其他日历系统,这些系统也有类似的怪癖。
关于代码:
如果您想要 DateTime
中的 LocalDate
,请使用 LocalDateTime.FromDateTime(dt).Date
要获取当前日期,请使用 SystemClock.Instance.Now.InZone(tz).Date
- 如果您希望它与
DateTime.Now
相同,后者使用代码为 运行 的计算机的本地时区,则通过调用 [=] 获取 tz
18=]
为了比较您描述的问题类型,考虑定义最小和最大 天 而不是最小和最大 周期。那么你就不会有这样的单位变化。您可以像这样获得天数差异:
long days = Period.Between(d1, d2, PeriodUnits.Days).Days;
我相信这样的事情对你的用例很有效:
public static bool IsDifferenceLessThan(LocalDate d1, LocalDate d2, Period p)
{
if (p.HasTimeComponent)
throw new ArgumentException("Can only compare dates.", "p");
if (p.Years != 0)
{
if (p.Months != 0 || p.Weeks != 0 || p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var years = Period.Between(d1, d2, PeriodUnits.Years).Years;
return years < p.Years;
}
if (p.Months != 0)
{
if (p.Weeks != 0 || p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var months = Period.Between(d1, d2, PeriodUnits.Months).Months;
return months < p.Months;
}
if (p.Weeks != 0)
{
if (p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var weeks = Period.Between(d1, d2, PeriodUnits.Weeks).Weeks;
return weeks < p.Weeks;
}
var days = Period.Between(d1, d2, PeriodUnits.Days).Days;
return days < p.Days;
}
作为对 Matt 已经非常出色的答案的补充,我们提供了一个选项,用于创建具有特定锚点的 IComparer<Period>
,例如
var febComparer = Period.CreateComparer(new LocalDate(2015, 2, 1).AtMidnight());
var marchComparer = Period.CreateComparer(new LocalDate(2015, 3, 1).AtMidnight());
var oneMonth = Period.FromMonths(1);
var twentyNineDays = Period.FromDays(29);
// -1: Feb 1st + 1 month is earlier than Feb 1st + 29 days
Console.WriteLine(febComparer.Compare(oneMonth, twentyNineDays));
// 1: March 1st + 1 month is later than March 1st + 29 days
Console.WriteLine(marchComparer.Compare(oneMonth, twentyNineDays));
我需要一个能够存储在数据库中的相对 min/max 日期验证,以便为每个客户定制应用程序。我认为 NodaTime.Period
是最佳选择,因为它能够指定年份。但是,NodaTime.Period
没有提供将自己与另一个时期进行比较的方法。
为此验证提供的示例数据:
- 最低年龄为 18 岁。
- 最大年龄 o 100 岁。
- 最短销售时间为 1 个月
- 最长销售期限为 3 个月
- 广告活动最少 7 天
(注:目前要求年/月/日不合并验证)
验证是:
public Period RelativeMinimum { get; set; }
public Period RelativeMaximum { get; set; }
给定用户输入的日期(和现在):
var now = new LocalDate(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
var userValue = new LocalDate(date.Year, date.Month, date.Day);
var difference = Period.Between(userValue, now);
我比较了:
if(RelativeMinimum != null && difference.IsLessThan(RelativeMinimum))))
{
response.IsValid = false;
response.Errors.Add(MinimumErrorMessage);
}
正在使用扩展 class:
public static class PeriodExtensions
{
public static bool IsLessThan(this Period p, Period p2)
{
return (p.Years < p2.Years) || (p.Years == p2.Years && p.Months < p2.Months) || (p.Years == p2.Years && p.Months == p2.Months && p.Days < p2.Days);
}
public static bool IsGreaterThan(this Period p, Period p2)
{
return (p.Years > p2.Years) || (p.Years == p2.Years && p.Months > p2.Months) || (p.Years == p2.Years && p.Months == p2.Months && p.Days > p2.Days);
}
}
虽然这种方法有效,但考虑到我的测试条件,我想知道为什么 @jon-skeet 没有实现这个,并且立即不得不担心我遗漏了什么以及我应该使用什么替代方案?
周期不可比的主要原因是它们可以包含可变长度的组件。
两个为期一个月的时间段的天数不一定相同。例如,哪个更大:1 个月还是 30 天?如果月份是一月,则超过 30 天。如果月份是二月,则少于 30 天。
年份也是如此。有的是365天,有的是366天。
当然,这一切都假设您使用的是公历。野田时间支持其他日历系统,这些系统也有类似的怪癖。
关于代码:
如果您想要
DateTime
中的LocalDate
,请使用LocalDateTime.FromDateTime(dt).Date
要获取当前日期,请使用
SystemClock.Instance.Now.InZone(tz).Date
- 如果您希望它与
DateTime.Now
相同,后者使用代码为 运行 的计算机的本地时区,则通过调用 [=] 获取tz
18=]
- 如果您希望它与
为了比较您描述的问题类型,考虑定义最小和最大 天 而不是最小和最大 周期。那么你就不会有这样的单位变化。您可以像这样获得天数差异:
long days = Period.Between(d1, d2, PeriodUnits.Days).Days;
我相信这样的事情对你的用例很有效:
public static bool IsDifferenceLessThan(LocalDate d1, LocalDate d2, Period p)
{
if (p.HasTimeComponent)
throw new ArgumentException("Can only compare dates.", "p");
if (p.Years != 0)
{
if (p.Months != 0 || p.Weeks != 0 || p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var years = Period.Between(d1, d2, PeriodUnits.Years).Years;
return years < p.Years;
}
if (p.Months != 0)
{
if (p.Weeks != 0 || p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var months = Period.Between(d1, d2, PeriodUnits.Months).Months;
return months < p.Months;
}
if (p.Weeks != 0)
{
if (p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var weeks = Period.Between(d1, d2, PeriodUnits.Weeks).Weeks;
return weeks < p.Weeks;
}
var days = Period.Between(d1, d2, PeriodUnits.Days).Days;
return days < p.Days;
}
作为对 Matt 已经非常出色的答案的补充,我们提供了一个选项,用于创建具有特定锚点的 IComparer<Period>
,例如
var febComparer = Period.CreateComparer(new LocalDate(2015, 2, 1).AtMidnight());
var marchComparer = Period.CreateComparer(new LocalDate(2015, 3, 1).AtMidnight());
var oneMonth = Period.FromMonths(1);
var twentyNineDays = Period.FromDays(29);
// -1: Feb 1st + 1 month is earlier than Feb 1st + 29 days
Console.WriteLine(febComparer.Compare(oneMonth, twentyNineDays));
// 1: March 1st + 1 month is later than March 1st + 29 days
Console.WriteLine(marchComparer.Compare(oneMonth, twentyNineDays));