给定从StartingDate
到EndingDate
的句号。
我希望从StartingMonth
和EndingMonth
开始获取该时间段内的时间间隔。
示例:
StartingMonth = april (4)
EndingMonth = november (11)
期间:
Period A : StartingDate = (2014, 03, 01); EndingDate = (2015, 02, 28);
Period B : StartingDate = (2014, 07, 01); EndingDate = (2015, 06, 30);
Period C : StartingDate = (2014, 01, 01); EndingDate = (2015, 12, 31);
会回来:
Period A : 1 sub-period = (2014, 4, 1) - (2014, 11, 30)
Period B : 2 sub-periods = (2014, 7, 1) - (2014, 11, 30) ; (2015, 4, 1) - (2015, 6, 30)
Period C : 2 sub-periods = (2014, 4, 1) - (2014, 11, 30) ; (2015, 4, 1) - (2015, 11, 30)
我试过这个(似乎很难,并且没有管理多个子时段): 使用LINQ可能更简单吗?
if (StartingDate.Month < startingMonth && EndingDate.Month < endingMonth)
{
periods.Add(new PeriodInterval
{
StartDate = new DateTime(StartingDate.Year, startingMonth, 1),
EndDate = new DateTime(StartingDate.Year, endingMonth, EndingDate.Day)
});
}
if (StartingDate.Month > startingMonth && EndingDate.Month > endingMonth)
{
periods.Add(new PeriodInterval
{
StartDate = new DateTime(StartingDate.Year, startingMonth, 1),
EndDate = new DateTime(StartingDate.Year, endingMonth, EndingDate.Day)
});
}
if (StartingDate.Month < startingMonth && EndingDate.Month > endingMonth)
{
periods.Add(new PeriodInterval
{
StartDate = new DateTime(StartingDate.Year, startingMonth, 1),
EndDate = new DateTime(StartingDate.Year, endingMonth, EndingDate.Day)
});
}
if (StartingDate.Month > startingMonth && EndingDate.Month < endingMonth)
{
periods.Add(new PeriodInterval
{
StartDate = new DateTime(StartingDate.Year, startingMonth, 1),
EndDate = new DateTime(StartingDate.Year, endingMonth, EndingDate.Day)
});
}
想法是返回红色时段内的蓝色时段:
答案 0 :(得分:1)
class Discount
{
public int DiscountID { get; set; } //You will need some Key field if you are storing these in a database.
public DateTime issueDate { get; set; }
public DateTime expirationDate { get; set; }
public List<PeriodInterval> intervals { get; set; }
public Discount(DateTime IssueDate, DateTime ExpirationDate)
{
issueDate = IssueDate;
expirationDate = ExpirationDate;
intervals = new List<PeriodInterval>();
}
public void AddInterval(DateTime StartDate, DateTime EndDate)
{
intervals.Add(new PeriodInterval() {
StartMonth=StartDate.Month,
StartDay=StartDate.Day,
EndMonth=EndDate.Month,
EndDay=EndDate.Day
});
}
public List<Period> GetPeriods()
{
List<Period> periods=new List<Period>();
int yearCount = expirationDate.Year-issueDate.Year+1; //+1: Run at least one year against the periods.
for (int i = 0; i < yearCount; i++)
{
//Loop through all the years and add 'Periods' from all the PeriodInterval info.
foreach (PeriodInterval pi in intervals)
{
var period = pi.GetPeriod(issueDate, expirationDate, i);
if (period != null)
periods.Add(period);
}
}
return periods;
}
}
class Period
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
class PeriodInterval
{
public int PeriodIntervalID { get; set; } //You will need some Key field if you are storing these in a database.
public int DiscountID { get; set; } //Foreign Key to Discount. This is alsof for database storage.
public int StartMonth { get; set; }
public int StartDay { get; set; }
public int EndMonth { get; set; }
public int EndDay { get; set; }
public Period GetPeriod(DateTime issueDate, DateTime expirationDate, int Year)
{
DateTime PeriodStart = new DateTime(issueDate.AddYears(Year).Year, StartMonth, StartDay);
DateTime PeriodEnd = new DateTime(issueDate.AddYears(Year).Year, EndMonth, EndDay);
PeriodStart=new DateTime(Math.Max(PeriodStart.Ticks, issueDate.Ticks)); //Limit period to the max of the two start dates.
PeriodEnd = new DateTime(Math.Min(PeriodEnd.Ticks, expirationDate.Ticks)); //Limit period to the min of the two end dates.
if(PeriodEnd>PeriodStart) //If a valid period
{
return new Period()
{
StartDate = PeriodStart,
EndDate = PeriodEnd
};
}
//Default Return Null
return null;
}
}
我构建了一个控制台应用程序来测试它:
static void Main(string[] args)
{
List<Discount> Discounts = new List<Discount>();
Discount d1 = new Discount(new DateTime(2014, 3, 1), new DateTime(2015, 02, 28));
Discount d2 = new Discount(new DateTime(2014, 7, 1), new DateTime(2015, 06, 30));
Discount d3 = new Discount(new DateTime(2014, 01, 1), new DateTime(2015, 12, 31));
Discounts.Add(d1);
Discounts.Add(d2);
Discounts.Add(d3);
foreach (Discount d in Discounts)
{
d.AddInterval(new DateTime(2014, 4, 1), new DateTime(2014, 11, 30));
Console.WriteLine("IssueDate:{0} ExpirationDate:{1}", d.issueDate, d.expirationDate);
foreach (Period p in d.GetPeriods())
{
Console.WriteLine("Start:{0} End:{1}", p.StartDate, p.EndDate);
}
}
Console.ReadLine();
}
这是打印出来的内容:
答案 1 :(得分:1)
您可以使用Time Period Library for .NET:
// ----------------------------------------------------------------------
public void ExtractSubPeriods()
{
foreach ( ITimePeriod subPeriod in GetSubPeriods(
new TimeRange( new DateTime( 2014, 4, 1 ), new DateTime( 2015, 2, 28 ) ) ) )
{
Console.WriteLine( "SubPeriods 1: {0}", subPeriod );
foreach ( ITimePeriod subPeriod in GetSubPeriods(
new TimeRange( new DateTime( 2014, 7, 1 ), new DateTime( 2015, 6, 30 ) ) ) )
{
Console.WriteLine( "SubPeriods 2: {0}", subPeriod );
}
foreach ( ITimePeriod subPeriod in GetSubPeriods(
new TimeRange( new DateTime( 2014, 4, 1 ), new DateTime( 2015, 12, 31 ) ) ) )
{
Console.WriteLine( "SubPeriods 3: {0}", subPeriod );
}
} // ExtractSubPeriods
// ----------------------------------------------------------------------
public ITimePeriodCollection GetSubPeriods( ITimeRange timeRange )
{
ITimePeriodCollection periods = new TimePeriodCollection();
periods.Add( timeRange );
int startYear = periods.Start.Year;
int endYear = periods.End.Year + 1;
for ( int year = startYear; year <= endYear; year++ )
{
periods.Add( new TimeRange( new DateTime( year, 4, 1 ), new DateTime( year, 12, 1 ) ) );
}
TimePeriodIntersector<TimeRange> intersector = new TimePeriodIntersector<TimeRange>();
return intersector.IntersectPeriods( periods );
} // GetSubPeriods
答案 2 :(得分:0)
需要考虑的一些事项:
作为人类,我们通常使用完全包含范围作为仅日期值,而我们使用半开时间间隔仅用于时间或日期+时间值。 思考:从1月1日到1月2日2天,但从1:00到2:00 1小时,或从1月1日午夜到1月2日午夜。
.Net的内置DateTime
类型,是日期+时间类型。省略时间时,它会使用午夜。您无法删除时间部分。
如果您使用DateTime
的午夜日期范围,您可以做的最好是选择忽略时间部分。这会产生一些棘手的代码,因为在与范围进行比较之前,您必须将输入规范化为午夜。我不推荐这种方法,因为它容易出错。边缘情况会很快堆积起来。
因此,我建议您使用DateTime
切换到半开时间间隔,或者如果您需要继续使用完全包含范围,请考虑使用{{3}中的LocalDate
类型}}。我将向您展示两者的例子。
因为您接受月份数作为输入,请考虑您还应该处理它们不按顺序排列的情况。也就是说,两个月的子期间可能是从一年的12月到下一年的1月。
除非保证外部时段在整月的起点和终点完全 ,否则您需要修剪结果。例如,如果您的期间从2014年1月3日到2016年3月9日,则2015年的子期间将持续整个月,但2014年将在开始时进行调整,2016年将在结束时进行调整。
以下是使用DateTime
和午夜半开的日期间隔来实现此目的的方法:
public class DateTimeInterval
{
/// <summary>
/// The date and time that the interval starts.
/// The interval includes this exact value.
/// </summary>
public DateTime StartDate { get; private set; }
/// <summary>
/// The date and time that the interval is over.
/// The interval excludes this exact value.
/// </summary>
public DateTime EndDate { get; private set; }
public DateTimeInterval(DateTime startDate, DateTime endDate)
{
StartDate = startDate;
EndDate = endDate;
}
public IEnumerable<DateTimeInterval> GetSubIntervals(int startingMonth,
int endingMonth)
{
// Determine the possible ranges based on the year of this interval
// and the months provided
var ranges = Enumerable.Range(StartDate.Year,
EndDate.Year - StartDate.Year + 1)
.Select(year => new DateTimeInterval(
new DateTime(year, startingMonth, 1),
new DateTime(
startingMonth > endingMonth ? year + 1 : year,
endingMonth, 1)
.AddMonths(1)));
// Get the ranges that are overlapping with this interval
var results = ranges.Where(p => p.StartDate < this.EndDate &&
p.EndDate > this.StartDate)
.ToArray();
// Trim the edges to constrain the results to this interval
if (results.Length > 0)
{
if (results[0].StartDate < this.StartDate)
{
results[0] = new DateTimeInterval(
this.StartDate,
results[0].EndDate);
}
if (results[results.Length - 1].EndDate > this.EndDate)
{
results[results.Length - 1] = new DateTimeInterval(
results[results.Length - 1].StartDate,
this.EndDate);
}
}
return results;
}
}
使用上面的代码:
var interval = new DateTimeInterval(new DateTime(2014, 3, 1), // inclusive
new DateTime(2015, 3, 1)); // exclusive
var subIntervals = interval.GetSubIntervals(4, 11);
以下是使用NodaTime.LocalDate
和完全包含的仅限日期间隔实现相同目标的方法:
using NodaTime;
public class LocalDateInterval
{
/// <summary>
/// The date that the interval starts.
/// The interval includes this exact value.
/// </summary>
public LocalDate StartDate { get; private set; }
/// <summary>
/// The date that the interval ends.
/// The interval includes this exact value.
/// </summary>
public LocalDate EndDate { get; private set; }
public LocalDateInterval(LocalDate startDate, LocalDate endDate)
{
StartDate = startDate;
EndDate = endDate;
}
public IEnumerable<LocalDateInterval> GetSubIntervals(int startingMonth,
int endingMonth)
{
// Determine the possible ranges based on the year of this interval
// and the months provided
var ranges = Enumerable.Range(StartDate.Year,
EndDate.Year - StartDate.Year + 1)
.Select(year => new LocalDateInterval(
new LocalDate(year, startingMonth, 1),
new LocalDate(
startingMonth > endingMonth ? year + 1 : year,
endingMonth, 1)
.PlusMonths(1).PlusDays(-1)));
// Get the ranges that are overlapping with this interval
var results = ranges.Where(p => p.StartDate <= this.EndDate &&
p.EndDate >= this.StartDate)
.ToArray();
// Trim the edges to constrain the results to this interval
if (results.Length > 0)
{
if (results[0].StartDate < this.StartDate)
{
results[0] = new LocalDateInterval(
this.StartDate,
results[0].EndDate);
}
if (results[results.Length - 1].EndDate > this.EndDate)
{
results[results.Length - 1] = new LocalDateInterval(
results[results.Length - 1].StartDate,
this.EndDate);
}
}
return results;
}
}
使用上面的代码:
var interval = new LocalDateInterval(new LocalDate(2014, 3, 1), // inclusive
new LocalDate(2015, 2, 28)); // inclusive
var subIntervals = interval.GetSubIntervals(4, 11);
答案 3 :(得分:0)
这应该有效:
var periods = Periods
.Select(p => new {
p = p,
a = p.StartingDate.Year*12 + p.StartingDate.Month - 1,
b = p.EndingDate.Year*12 + p.EndingDate.Month
}
)
.Select(x => new {
period = x.p,
subperiods =
Enumerable
.Range(x.a, x.b - x.a)
.Select(e => new DateTime(e/12, e%12 + 1, 1))
.Where(d => StartingMonth <= d.Month && d.Month <= EndingMonth)
.GroupBy(i => i.Year)
.Where(g => g.Count() > 1)
.Select(g => new Period {
StartingDate = g.Min(),
EndingDate = g.Max()
})
.Select(p => new Period {
StartingDate = p.StartingDate < x.p.StartingDate ? x.p.StartingDate : p.StartingDate,
EndingDate = (p.EndingDate > x.p.EndingDate ? x.p.EndingDate : p.EndingDate)
.AddMonths(1)
.AddDays(-1)
})
});
<强>更新强>
根据你的形象,这可以解决问题:
var periods = Periods
.Select(p => new {
p = p,
a = p.StartingDate.Year*12 + p.StartingDate.Month - 1,
b = p.EndingDate.Year*12 + p.EndingDate.Month
}
)
.Select(x => new {
period = x.p,
subperiods =
Enumerable
.Range(x.a, x.b - x.a)
.Select(e => new DateTime(e/12, e%12 + 1, 1))
.Where(d => StartingMonth <= d.Month && d.Month <= EndingMonth)
.GroupBy(i => i.Year)
.Where(g => g.Count() > 1)
.Select(g => g.Select(i => i))
});