Get next date within a quarter
I need to create a function to return the next processing date for a given item. An item has a number that represents the month within a quarter that it is processed, as well as a number that represents the week within that month when it is processed. So, given a particular item's create date, I need to get the next processing date for that item, which will be the first day of it's assigned week and month wi开发者_JAVA百科thin a quarter.
Note that weeks are broken out by 7 days from the start of the month, regardless of what day of the week. So the first day of the first week could start on Tuesday or any other day for the purposes of this calculation.
Example:
Let's say I have an item with a completed date of 1/8/2010. That item has a monthWithinQuarter value of 2. It has a weekWithinMonth value of 3. So for this item that resolves to the third week of February, so I would want the function to return a date of 2/15/2010.The function should look something like this:
var nextProcessingDate = GetNextProcessingDate(
itemCompletedDate,
monthWithinQuarter,
weekWithinMonth);
This calculation has to be pretty fast as this calculation is going to be happening a lot, both in real time to display on a web site as well as in batch mode when processing items.
Thanks,
~ Justin
Okay, this should do it for you:
static DateTime GetNextProcessingDate(
DateTime itemCompletedDate,
int monthWithinQuarter,
int weekWithinMonth
) {
if (monthWithinQuarter < 1 || monthWithinQuarter > 3) {
throw new ArgumentOutOfRangeException("monthWithinQuarter");
}
if (weekWithinMonth < 1 || weekWithinMonth > 5) {
throw new ArgumentOutOfRangeException("weekWithinMonth");
}
int year = itemCompletedDate.Year;
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
DateTime startOfQuarter = startOfQuarters.Where(d => d <= itemCompletedDate)
.OrderBy(d => d)
.Last();
int month = startOfQuarter.Month + monthWithinQuarter - 1;
int day = (weekWithinMonth - 1) * 7 + 1;
if (day > DateTime.DaysInMonth(year, month)) {
throw new ArgumentOutOfRangeException("weekWithinMonth");
}
DateTime candidate = new DateTime(year, month, day);
if (candidate < itemCompletedDate) {
month += 3;
if(month > 12) {
year++;
month -= 12;
}
}
return new DateTime(year, month, day);
}
As far as efficiency, the place where I see the most room for improvement is repeatedly creating the array
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
So let's offload that to a method and memoize it:
static Dictionary<int, DateTime[]> cache = new Dictionary<int, DateTime[]>();
public static DateTime[] StartOfQuarters(DateTime date) {
int year = date.Year;
DateTime[] startOfQuarters;
if(!cache.TryGetValue(year, out startOfQuarters)) {
startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
cache.Add(year, startOfQuarters);
}
return startOfQuarters;
}
If you don't need the flexibility of quarters possibly starting on unusual days, you could replace
DateTime[] startOfQuarters = new[] {
new DateTime(year, 1, 1),
new DateTime(year, 4, 1),
new DateTime(year, 7, 1),
new DateTime(year, 10, 1)
};
DateTime startOfQuarter = startOfQuarters.Where(d => d <= itemCompletedDate).OrderBy(d => d).Last();
int month = startOfQuarter.Month + monthWithinQuarter - 1;
with
int month = 3 * ((itemCompletedDate.Month - 1) / 3) + monthWithinQuarter;
From what I understand, this should do the job:
public static DateTime GetNextProcessingDate(DateTime itemCreationDate, int monthWithinQuarter,
int weekWithinMonth)
{
var quarter = (itemCreationDate.Month - 1) / 4; // Assumes quarters are divided by calendar year.
var month = quarter * 4 + monthWithinQuarter; // First quarter of month plus month within quarter
var dayInMonth = (weekWithinMonth - 1) * 7 + 1; // Weeks are counted from first day, regardless of day of week (as you mention).
return new DateTime(itemCreationDate.Year, month, dayInMonth);
}
Let me know if any of it isn't clear.
Your calculation, I suppose should be reduced at
DateTime dt;
dt.AddDays(daysToAdd);
dt.AddMonths(monthsToAdd);
dt.AddHours(hoursToAdd);
dt.AddYears(yearsToAdd);
精彩评论