is there a formula to calculate recurrence - c#

I'm using ASP.Net MVC2. I would like to know if there is there a formula to calculate recurrence date? So from my client side I'm selecting dates and using ajax.post to send to the controller. My expecting result would be like so for example:
maxdate is September 30th
currentdate is today
duration is 3 days for every week
so output would be
aug12-aug14
aug19-aug21
aug26-28 until the end of september

Enumerable.Range(0, int.MaxValue)
.Select(i => new
{
start = DateTime.Today.AddDays(7*i),
end = DateTime.Today.AddDays(7*i + 2)
})
.TakeWhile(d => d.end <= new DateTime(2010, 9, 30))
Unless you're looking for the dates in between start and end inclusive:
Enumerable.Range(0, int.MaxValue)
.SelectMany(i => new[]
{
DateTime.Today.AddDays(7*i),
DateTime.Today.AddDays(7*i + 1),
DateTime.Today.AddDays(7*i + 2)
})
.TakeWhile(d => d <= new DateTime(2010, 9, 30))

Related

LINQ DateTimes between hours with duration

I'm trying to write a query that return dates that occur within a certain hour of the day plus duration. For example, any DateTimes that occur after (8PM + 8 hours), the hour and duration are variable. It is possible that hour + duration can be in the next day. A spike:
[Test]
public void should_find_dates_between_beginhour_plus_duration()
{
var dates = new []
{
new DateTime(2017, 1, 3, 12,0,0),
new DateTime(2017, 1, 4, 21,0,0),
new DateTime(2017, 1, 5, 2,0,0)
};
var beginHour = 20; //8pm
var duration = 8; //hours
var results = dates.Where(x => x.Hour >= beginHour && x <= x.???? + duration);
//should contain the last 2 dates
foreach (var date in results)
{
Console.WriteLine(date);
}
}
Thus winter/summer time shift does not important here, then you can calculate end hour before running your query. Filtering will be simple - you pick dates which have hour either bigger than begin hour (later in the evening), or smaller than end hour (i.e. earlier in the morning):
var endHour = DateTime.Today.AddHours(beginHour + duration).Hour;
var results = dates.Where(x => beginHour < endHour
? (beginHour <= x.Hour && x.Hour <= endHour)
: (beginHour <= x.Hour || x.Hour <= endHour));

DateTime List find last date that before current

I have a list of dates:
var dates = new List<DateTime>
{
new DateTime(2016, 01, 01),
new DateTime(2016, 02, 01),
new DateTime(2016, 03, 01),
new DateTime(2016, 04, 01),
new DateTime(2016, 05, 01)
};
Now given a certain date, a "StartDate". What is the easiest way to create a list of dates after the startdate, and the last date before?
I.E. - If I supply the date DateTime(2016, 03, 15), I need to return
DateTime(2016, 03, 01),
DateTime(2016, 04, 01),
DateTime(2016, 05, 01)
It could be as simple as finding the last "Active" date and then just using the where from that date. But I'm unsure on how to do this without making it really complicated.
If your list is already sorted, you can use a binary search:
var index = dates.BinarySearch(start);
// If the precise value isn't found, index will be the bitwise complement
// of the first index *later* than the target, so we need to subtract 1.
// But if there were no values earlier, we should start from 0.
if (index < 0)
{
index = Math.Max(~index - 1, 0);
}
return dates.Skip(index).ToList();
This assumes the dates are unique. If there are multiple dates the same as start, there's no guarantee that it will find the first one. If that's a concern, you'd need to search backwards until you found the first match.
You haven't specified whether if there's an exact match, you want to include the date before that or not. If you do, you'll need to adjust this code a bit.
Without making it complicated and if i understand your requirements correctly. You want all the dates after the StartDate and the last entry before the first matching (If Any). Then I find this the easiest most readable way of doing it:
var results = dates.FindAll(x => x >= StartDate);
int index = dates.FindLastIndex(x => x < StartDate);
// there might be no match, if all the list is resulted
if (index >= 0)
results.Insert(0, dates[index]);
If you prefer one query style, you can do the below (I find it not readable):
var results = dates.Where(x => x >= StartDate)
.Concat(dates.Where(x => x < StartDate)
.OrderByDescending(x => x).Take(1));
Last Alternative if you like fancy ways:
int startIndex = dates.FindLastIndex(x=> x < StartDate);
startIndex = Math.Max(0, startIndex);
var results = dates.Skip(startIndex).ToList();
var partialResult = dates.Where(x => x >= date).ToList();
partialResult.Add(dates.Where(x => x < date).Max());
IList<DateTime> result = partialResult.OrderBy(x => x).ToList();

Excluding dates from period using Nodatime

I'm trying to workout the amount of time between two LocalDateTime values and exclude specific dates (in this example, it's bank holidays).
var bankHolidays = new[] { new LocalDate(2013, 12, 25), new LocalDate(2013, 12, 26) };
var localDateTime1 = new LocalDateTime(2013, 11, 18, 10, 30);
var localDateTime2 = new LocalDateTime(2013, 12, 29, 10, 15);
var differenceBetween = Period.Between(localDateTime1, localDateTime2, PeriodUnits.Days | PeriodUnits.HourMinuteSecond);
The differenceBetween value shows the number of days/hours/minutes/seconds between the two dates, as you would expect.
I could check every single day from the start date and see if the bankHolidays collection contains that date e.g.
var bankHolidays = new[] { new LocalDate(2013, 12, 25), new LocalDate(2013, 12, 26) };
var localDateTime1 = new LocalDateTime(2013, 11, 18, 10, 30);
var localDateTime2 = new LocalDateTime(2013, 12, 29, 10, 15);
var differenceBetween = Period.Between(localDateTime1, localDateTime2, PeriodUnits.Days | PeriodUnits.HourMinuteSecond);
var london = DateTimeZoneProviders.Tzdb["Europe/London"];
for (var i = 1; i < differenceBetween.Days; ++i)
{
var x = localDateTime1.InZoneStrictly(london) + Duration.FromStandardDays(i);
if (bankHolidays.Any(date => date == x.Date))
{
//subtract one day for the period.
}
}
I feel like I'm missing some obvious and there should be an easier method, is there a simpler way to find a period between two dates whilst excluding certain dates?
I also need to include weekends in this exclusion too, the obvious way seems to be to check the day of the week for weekends whilst checking bank holidays, this just doesn't seem like the best/correct way of handling it though.
I feel like I'm missing some obvious and there should be an easier method, is there a simpler way to find a period between two dates whilst excluding certain dates?
Well, it's relatively easy to count the number of bank holidays included in a date-to-date range:
Sort all the bank holidays in chronological order
Use a binary search to find out where the start date would come in the collection
Use a binary search to find out where the end date would come in the collection
Subtract one index from another to find how many entries are within that range
Work out the whole period using Period.Between as you're already doing
Subtract the number of entries in the range from the total number of days in the range
The fiddly bit is taking into account that the start and/or end dates may be bank holidays. There's a lot of potential for off-by-one errors, but with a good set of unit tests it should be okay.
Alternatively, if you've got relatively few bank holidays, you can just use:
var period = Period.Between(start, end,
PeriodUnits.Days | PeriodUnits.HourMinuteSecond);
var holidayCount = holidays.Count(x => x >= start && x <= end);
period = period - Period.FromDays(holidayCount);
Just use TimeSpan to get the difference, all times are in your current local time zone:
var bankHolidays = new[] { new DateTime(2013, 12, 25), new DateTime(2013, 12, 26) };
var localDateTime1 = new DateTime(2013, 11, 18, 10, 30, 0);
var localDateTime2 = new DateTime(2013, 12, 29, 10, 15, 0);
var span = localDateTime2 - localDateTime1;
var holidays = bankHolidays[1] - bankHolidays[0];
var duration = span-holidays;
Now duration is your time elapsed between localDateTime1 and localDateTime2.
If you want to exlude two dates via the bankHolidays you can easiely modify the operations above.
You might use an extra method for this operation:
public static TimeSpan GetPeriod(DateTime start, DateTime end, params DateTime[] exclude)
{
var span = end - start;
if (exclude == null) return span;
span = exclude.Where(d => d >= start && d <= end)
.Aggregate(span, (current, date) => current.Subtract(new TimeSpan(1, 0, 0, 0)));
return span;
}
Now you can just use this:
var duration = GetPeriod(localDateTime1, localDateTime2, bankHolidays);

Get a list of weeks for a year - with dates

I've been racking my brains over this, but it's late on a Friday and I'm going round in circles.
I need to create a list of working weeks for a drop down list, with the week number as the value. So the code would output:
Monday 22nd August - Friday 26th September
Monday 29th August - Friday 2 September
Monday 5th September - Friday 9 September
etc..
For the whole year. Any ideas how I would achieve this?
Thanks.
I think the code below complies with ISO 8601:
var jan1 = new DateTime(DateTime.Today.Year , 1, 1);
//beware different cultures, see other answers
var startOfFirstWeek = jan1.AddDays(1 - (int)(jan1.DayOfWeek));
var weeks=
Enumerable
.Range(0,54)
.Select(i => new {
weekStart = startOfFirstWeek.AddDays(i * 7)
})
.TakeWhile(x => x.weekStart.Year <= jan1.Year)
.Select(x => new {
x.weekStart,
weekFinish=x.weekStart.AddDays(4)
})
.SkipWhile(x => x.weekFinish < jan1.AddDays(1) )
.Select((x,i) => new {
x.weekStart,
x.weekFinish,
weekNum=i+1
});
Bear in mind, that week calculations are done differently in different cultures and there is not a bug if you see week number 53!
using System.Globalization;
CultureInfo cultInfo = CultureInfo.CurrentCulture;
int weekNumNow = cultInfo.Calendar.GetWeekOfYear(DateTime.Now,
cultInfo.DateTimeFormat.CalendarWeekRule,
cultInfo.DateTimeFormat.FirstDayOfWeek);
Just updating what Spender put, because I wanted to make the output of your Datetimes more towards what you wanted.
DateTime jan1 = new DateTime(DateTime.Today.Year, 1, 1);
//beware different cultures, see other answers
DateTime startOfFirstWeek = jan1.AddDays(1 - (int)(jan1.DayOfWeek));
var weeks=
Enumerable
.Range(0,54)
.Select(i => new {
weekStart = startOfFirstWeek.AddDays(i * 7)
})
.TakeWhile(x => x.weekStart.Year <= jan1.Year)
.Select(x => new {
x.weekStart,
weekFinish=x.weekStart.AddDays(4)
})
.SkipWhile(x => x.weekFinish.Year < jan1.Year)
.Select((x,i) => new {
WeekStart = x.weekStart.ToString("dddd, d, MMMM"),
WeekFinish = x.weekFinish.ToString("dddd, d, MMMM"),
weekNum=i+1
});
The change to correct the formatting to what you wanted is in the last select of the anonymous object.
You can use the Week class of the Time Period Library for .NET:
DateTime start = DateTime.Now.Date;
DateTime end = start.AddYears( 1 );
Week week = new Week( start );
while ( week.Start < end )
{
Console.WriteLine( "week " + week );
week = week.GetNextWeek();
}
You may need to tweak this a bit, but it should get you what you need:
static void Main(string[] args)
{
List<DateTime[]> weeks = new List<DateTime[]>();
DateTime beginDate = new DateTime(2011, 01, 01);
DateTime endDate = new DateTime(2012, 01, 01);
DateTime monday = DateTime.Today;
DateTime friday = DateTime.Today;
while (beginDate < endDate)
{
beginDate = beginDate.AddDays(1);
if (beginDate.DayOfWeek == DayOfWeek.Monday)
{
monday = beginDate;
}
else if (beginDate.DayOfWeek == DayOfWeek.Friday)
{
friday = beginDate;
}
else if (beginDate.DayOfWeek == DayOfWeek.Saturday)
{
weeks.Add(new DateTime[] { monday, friday });
}
}
for (int x = 0; x < weeks.Count; x++)
{
Console.WriteLine(weeks[x][0].Date.ToShortDateString() + " - " + weeks[x][1].Date.ToShortDateString());
}
Console.ReadLine();
}

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 within 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);

Categories

Resources