Periodicity of events 2 [closed] - c#

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 11 years ago.
My previous question got a lot cons. I will try again, with a code and why it not work.
For example, we have event that will go every 3 month. Event have DateStart, DataEnd and Periodicity.
For example, we have a record:
start = 02/23/2012 22:00:00;
end = 12/31/2012 23:30:00;
periodicity = 3;
Method must return true when current month = February, May, August, November.
Code:
public bool MonthPeriodicityChecker (DateTime start, DateTime end, DateTime dateToCheck, int periodicity)
{
var monthDiff = dateToCheck.Month - startDate.Month;
return (monthDiff-1) % periodicity == 0;
}
This code return true if month in dateToCheck equals April, July, October. It's skip first month.
UPDATE:
Damn, sorry to all. In the beyond, I have loop that sumulated dates. And this loop start with 1 and add this 1 to start. And next date was 24 February. Therefore, the February not prints( there are checks for number(23) of month too) in my program. Sorry and thanks.

If you want this to trigger in February, May, August and November, you shouldn't be subtracting one from the difference. Just use:
return (monthDiff % periodicity) == 0;
When the months are equal (February), 0 % 3 == 0. When you're a multiple of three months out (such as August), 6 % 3 == 0.
And I'm not sure about C# but some languages don't act as you may expect when taking the modulus of a negative number, so I'd be doubly safe with:
public bool MonthPeriodicityChecker (DateTime start, DateTime end,
DateTime dateToCheck, int periodicity)
{
var monthDiff = dateToCheck.Month - startDate.Month + 12; // make positive
return (monthDiff % periodicity) == 0;
}
However, keep in mind that, because you're only using the month of the year in your calculations, this probably won't work as expected if you run more than 12 months and your periodicity is not something that divides into 12 cleanly.
Example with starting December 2010 for every five months.
December 2010 is okay since the difference is zero: 0 % 5 == 0.
May 2011 is okay since the difference between May and December is five months: 5 % 5 == 0.
October 2011 is okay since the difference between October and December is ten months: 10 % 5 == 0.
March 2012 is not okay. The difference between March and December is three months, not fifteen as has happened in reality: 3 % 5 == 3.
The upshot of that is that you'll fire in May, October and December in every year.
You can fix that little problem (if it is a problem for you) by ensuring the years are taken into account, something like:
public bool MonthPeriodicityChecker (DateTime start, DateTime end,
DateTime dateToCheck, int periodicity)
{
// assuming Month returns 1-12 here which is the case for C# I think.
var monthDiff = (dateToCheck.Year * 12 + dateToCheck.Month - 1)
- (startDate.Year * 12 + startDate.Month - 1);
return (monthDiff % periodicity) == 0;
}
That also gets rid of the need for adding 12 to get a positive number since the advancing years will ensure that (a jump from December to January will now give you 1 rather than -11), assuming that dateToCheck.Month will always be greater than or equal to startDate.Month.
If there's the possibility that dateToCheck may be less startDate, you probably want to check that first and return false as the first step in the above function.

You can do this by generating all of the dates in the range, then see if any of them fall in the same month as dateToCheck. First, get the months:
private IEnumerable<DateTime> GetDatesInRange(DateTime start, DateTime end, int periodicity)
{
var current = start;
do
{
yield return current;
current += TimeSpan.FromMonths(periodicity);
}
while(current <= end)
}
Then, see if any are in the same month:
public bool MonthPeriodicityChecker(DateTime start, DateTime end, DateTime dateToCheck, int periodicity)
{
return GetDatesInRange(start, end, periodicity).Any(date => date.Month == dateToCheck.Month);
}

Your month subtraction is not good. What happens if you change from December to January?
How about this approach:
public bool monthPeriodicityChecker(DateTime start, DateTime end, dateToCheck, periodicity)
{
if ((dateToCheck < start) || (dateToCheck > start))
return false;
int monthDiff = 0;
while (startDate.AddMonths(1) < dateToCheck)
{
monthDiff++
// i'm sure there is a speedier way to calculate the month difference, but this should do for the purpose of this example
}
return (monthDiff) % periodicity == 0;
};

Related

Calculate exact no of week for date range

A person is engaged in different works in different time duration which will start from Monday and end on friday as follows.Monday to Friday will be considered as 1 week.Any overlapping weeks be considered as 1 week.
Below are the scenario
"AssignedEngagementdate":[
{"Startdate":"01/03/2022","Enddate":"01/07/2022"},
{"Startdate":"01/10/2022","Enddate":"01/14/2022"},
{"Startdate":"01/10/2022","Enddate":"01/21/2022"},
{"Startdate":"02/14/2022","Enddate":"02/18/2022"}
]
Here I need to find the no of weeks assigned by this person and it should be 4 since one week is from 10th Jan to 14 Feb is overlapping in 2 engagement.
How I can do this in C# using linq. I was trying to fetch min start date and max end date from list and find the difference and converting in no of weeks but it has not given the actual result since date assigned is not consistent.
The trick is to convert each date range into an enumerated week range, or at least a list of distinct dates (such as Sundays) corresponding to each week or partial week. SelectMany() lets you gather them all together, after which a distinct count gives you an answer.
Try something like:
int numberOfWeeks = AssignedEngagementdate
.SelectMany(a => {
DateTime firstSunday = a.Startdate.AddDays(-(int)a.Startdate.DayOfWeek);
DateTime lastSunday = a.Enddate.AddDays(-(int)a.Enddate.DayOfWeek); // May be same as firstSunday
int weeks = (lastSunday - firstSunday).Days / 7 + 1;
// Enumerate one Sunday per week
return Enumerable.Range(0, weeks).Select(i => firstSunday.AddDays(7 * i));
})
.Distinct()
.Count();
You may still need to consider and test cases where assignments start or end on days other than Monday and Friday. The above should handle most such cases.
Without linq, you can try this Datetime extension :
public static int NumberOfWeeksBetween(this DateTime start, DateTime end)
{
TimeSpan span = end.Subtract(start);
if (span.Days <= 7)
{
if (start.DayOfWeek > end.DayOfWeek) return 2;
else return 1;
}
int days = span.Days - 7 + (int)start.DayOfWeek;
int weekCount = 1;
int dayCount = 0;
for (weekCount = 1; dayCount < days; weekCount++) dayCount += 7;
return weekCount;
}

How do I know Daylight Saving Time just went off when it JUST went back an hour? [duplicate]

This question already has answers here:
Timer callback raised every 24 hours - is DST handled correctly?
(3 answers)
Closed 5 years ago.
I'm not in front of my code right now, but don't really think I need it to ask this question. So I have a countdown timer that goes off every 18 hours and then resets. The timer checks the current DateTime.Now and adjusts the countdown timer as needed. I am having an issue trying to account for when daylight savings when it goes back an hour because; for example this past event on November 5th 2017 at 2am it goes back to 1am but when I do DateTime.IsDaylightSavingTime() it tells me that it's false even though Daylight Saving Time just went off. This makes my timer go back an extra hour because it thinks that Daylight Saving Time still hasn't happen for that one hour period. How would I get around this?
If you realy need to use local time for some reason, than you should count for DST changes in advance (prior scheduling next event).
TimeZoneInfo.GetAdjustmentRules() should help you to get the time and delta of next DST adjustment.
Following is the code for getting upcomming adjustment:
public static DateTime? GetNextAdjustmentDate(TimeZoneInfo timeZoneInfo)
{
var adjustments = timeZoneInfo.GetAdjustmentRules();
if (adjustments.Length == 0)
{
return null;
}
int year = DateTime.UtcNow.Year;
TimeZoneInfo.AdjustmentRule adjustment = null;
foreach (TimeZoneInfo.AdjustmentRule adjustment1 in adjustments)
{
// Determine if this adjustment rule covers year desired
if (adjustment1.DateStart.Year <= year && adjustment1.DateEnd.Year >= year)
adjustment = adjustment1;
}
if (adjustment == null)
return null;
//TimeZoneInfo.TransitionTime startTransition, endTransition;
DateTime dstStart = GetCurrentYearAdjustmentDate(adjustment.DaylightTransitionStart);
DateTime dstEnd = GetCurrentYearAdjustmentDate(adjustment.DaylightTransitionEnd);
if (dstStart >= DateTime.UtcNow.Date)
return dstStart;
if (dstEnd >= DateTime.UtcNow.Date)
return dstEnd;
return null;
}
private static DateTime GetCurrentYearAdjustmentDate(TimeZoneInfo.TransitionTime transitionTime)
{
int year = DateTime.UtcNow.Year;
if (transitionTime.IsFixedDateRule)
return new DateTime(year, transitionTime.Month, transitionTime.Day);
else
{
// For non-fixed date rules, get local calendar
System.Globalization.Calendar cal = CultureInfo.CurrentCulture.Calendar;
// Get first day of week for transition
// For example, the 3rd week starts no earlier than the 15th of the month
int startOfWeek = transitionTime.Week * 7 - 6;
// What day of the week does the month start on?
int firstDayOfWeek = (int)cal.GetDayOfWeek(new DateTime(year, transitionTime.Month, 1));
// Determine how much start date has to be adjusted
int transitionDay;
int changeDayOfWeek = (int)transitionTime.DayOfWeek;
if (firstDayOfWeek <= changeDayOfWeek)
transitionDay = startOfWeek + (changeDayOfWeek - firstDayOfWeek);
else
transitionDay = startOfWeek + (7 - firstDayOfWeek + changeDayOfWeek);
// Adjust for months with no fifth week
if (transitionDay > cal.GetDaysInMonth(year, transitionTime.Month))
transitionDay -= 7;
return new DateTime(year, transitionTime.Month, transitionDay);
}
}
You'd need to add some more code to retrieve and apply the adjustment delta.
Well - now, when you see all the hard work that would need to be done (and than maintained and made sure to be bug free), you might want to rething your problem to be able to use UTC.
I would use DateTime.UtcNow you won't have the issue with daylight savings

Is this a good algorithm to find each week in a given month?

I need to take a month (defined as a start and end date) and return a set of date ranges for each week in that month. A week is defined as Sunday through Saturday. A good way to visualize it is if you double click on your Windows date in the start bar:
The month of October 2011 has 6 weeks: 10/1-10/1, 10/2-10/8, 10/9-10/15, 10/16-10/22, 10/23-10/29 and 10/30-10/31.
I can describe each week as a struct:
struct Range
{
public DateTime Start;
public DateTime End;
public Range(DateTime start, DateTime end)
{
Start = start;
End = end;
}
}
I need to write a function that takes a month and returns an array of ranges within it. Here's my first attempt, which appears to work and addresses the obvious edge cases:
public static IEnumerable<Range> GetRange(DateTime start, DateTime end)
{
DateTime curStart = start;
DateTime curPtr = start;
do
{
if (curPtr.DayOfWeek == DayOfWeek.Saturday)
{
yield return new Range(curStart, curPtr);
curStart = curPtr.AddDays(1);
}
curPtr = curPtr.AddDays(1);
} while (curPtr <= end);
if(curStart <= end)
yield return new Range(curStart, end);
}
I would like to know if there's a cleaner or more obvious approach to do the same. I'm not overly concerned about performance, but I'd like to improve code readability and make the algorithm a bit more concise. Perhaps there's a very creative solution involving a single LINQ expression or something. Thanks!
This is based on simply incrementing by 7, as suggested by Previti, ready for international use. If your C# is < 4.0, remove the default parameter = DayOfWeek.Sunday
public static IEnumerable<Range> GetRange(DateTime start, DateTime end, DayOfWeek startOfTheWeek = DayOfWeek.Sunday)
{
if (start > end)
{
throw new ArgumentException();
}
// We "round" the dates to the beginning of the day each
start = start.Date;
end = end.Date;
// The first week. It could be "shorter" than normal. We return it "manually" here
// The 6 + startOfWeek - start.DayOfWeek will give us the number of days that you
// have to add to complete the week. It's mod 7. It's based on the idea that
// the starting day of the week is a parameter.
DateTime curDay = new DateTime(Math.Min(start.AddDays((6 + (int)startOfTheWeek - (int)start.DayOfWeek) % 7).Ticks, end.Ticks), start.Kind);
yield return new Range(start, curDay);
curDay = curDay.AddDays(1);
while (curDay <= end)
{
// Each time we add 7 (SIX) days. This is because the difference between
// as considered by the problem, it's only 6 * 24 hours (because the week
// doesn't end at 23:59:59 of the last day, but at the beginning of that day)
DateTime nextDay = new DateTime(Math.Min(curDay.AddDays(6).Ticks, end.Ticks), start.Kind);
yield return new Range(curDay, nextDay);
// The start of the next week
curDay = nextDay.AddDays(1);
}
}
Some small notes: Math.Min isn't defined for DateTime, so I cheat a little by taking the Ticks of the DateTimes and comparing them. Then I rebuild the DateTime. I always use the DateTimeKind of the start date.
When you debug yield code, remember to "materialize" the result through the use of ToList or ToArray, otherwise the code won't be executed :-)

TimeSpan for different years subtracted from a bigger TimeSpan

The language I am using is C#.
I have the folowing dillema.
DateTime A, DateTime B. If A < B then I have to calculate the number of days per year in that timespan and multiply it by a coeficient that corresponds to that year.
My problem is the fact that it can span multiple years.
For example:
Nr of Days in TimeSpan for 2009 * coef for 2009 + Nr of Days in TimeSpan for 2010 * coef for 2010 + etc
You can't do this with a simple TimeSpan, basically. It doesn't know anything about when the span covers - it's just a number of ticks, really.
It sounds to me like there are two cases you need to consider:
A and B are in the same year. This is easy - just subtract one from the other and get the number of days from that
A and B are in different years. There are now either two or three cases:
The number of days after A in A's year. You can work this out by constructing January 1st in the following year, then subtracting A
The number of days in each year completely between A and B (so if A is in 2008 and B is in 2011, this would be 2009 and 2010)
The number of days in B's year. You can work this out by constructing December 31st in the previous year, then subtracting B. (Or possibly January 1st in B's year, depending on whether you want to count the day B is on or not.)
You can use DateTime.IsLeapYear to determine whether any particular year has 365 or 366 days in it. (I assume you're only using a Gregorian calendar, btw. All of this changes if not!)
Here is a little snippet of code that might help
var start = new DateTime(2009, 10, 12);
var end = new DateTime(2014, 12, 25);
var s = from j in Enumerable.Range(start.Year, end.Year + 1 - start.Year)
let _start = start.Year == j ? start : new DateTime(j, 1, 1)
let _end = end.Year == j ? end : new DateTime(j, 12, 31)
select new {
year = j,
days = Convert.ToInt32((_end - _start).TotalDays) + 1
};
If I understood your problem correctly, solving it using Inclusion Exclusion principle would be the best.
Say, if your start date is somewhere in 2008 and the end date is in 2010:
NDAYS(start, end) * coeff_2008
- NDAYS(2009, end) * coeff_2008 + NDAYS(2009, end) * coeff_2009
- NDAYS(2010, end) * coeff_2009 + NDAYS(2010, end) * coeff_2010
Where Ndays computes the number of dates in the interval (TotalDays plus one day).
There is no need to handle leap years specially or compute december 31st.
The details you can work out in a for-loop going over jan first of each year in the span.

Calculating the elapsed working hours between 2 datetime

Given two datetimes. What is the best way to calculate the number of working hours between them. Considering the working hours are Mon 8 - 5.30, and Tue-Fri 8.30 - 5.30, and that potentially any day could be a public holiday.
This is my effort, seem hideously inefficient but in terms of the number of iterations and that the IsWorkingDay method hits the DB to see if that datetime is a public holiday.
Can anyone suggest any optimizations or alternatives.
public decimal ElapsedWorkingHours(DateTime start, DateTime finish)
{
decimal counter = 0;
while (start.CompareTo(finish) <= 0)
{
if (IsWorkingDay(start) && IsOfficeHours(start))
{
start = start.AddMinutes(1);
counter++;
}
else
{
start = start.AddMinutes(1);
}
}
decimal hours;
if (counter != 0)
{
hours = counter/60;
}
return hours;
}
Before you start optimizing it, ask yourself two questions.
a) Does it work?
b) Is it too slow?
Only if the answer to both question is "yes" are you ready to start optimizing.
Apart from that
you only need to worry about minutes and hours on the start day and end day. Intervening days will obviously be a full 9/9.5 hours, unless they are holidays or weekends
No need to check a weekend day to see if it's a holiday
Here's how I'd do it
// Normalise start and end
while start.day is weekend or holiday, start.day++, start.time = 0.00am
if start.day is monday,
start.time = max(start.time, 8am)
else
start.time = max(start.time, 8.30am)
while end.day is weekend or holiday, end.day--, end.time = 11.59pm
end.time = min(end.time, 5.30pm)
// Now we've normalised, is there any time left?
if start > end
return 0
// Calculate time in first day
timediff = 5.30pm - start.time
day = start.day + 1
// Add time on all intervening days
while(day < end.day)
// returns 9 or 9.30hrs or 0 as appropriate, could be optimised to grab all records
// from the database in 1 or 2 hits, by counting all intervening mondays, and all
// intervening tue-fris (non-holidays)
timediff += duration(day)
// Add time on last day
timediff += end.time - 08.30am
if end.day is Monday then
timediff += end.time - 08.00am
else
timediff += end.time - 08.30am
return timediff
You could do something like
SELECT COUNT(DAY) FROM HOLIDAY WHERE HOLIDAY BETWEEN #Start AND #End GROUP BY DAY
to count the number of holidays falling on Monday, Tuesday, Wednesday, and so forth. Probably a way of getting SQL to count just Mondays and non-Mondays, though can't think of anything at the moment.
especially considering the IsWorkingDay method hits the DB to see if that day is a public holiday
If the problem is the number of queries rather than the amount of data, query the working day data from the data base for the entire day range you need at the beginning instead of querying in each loop iteration.
There's also the recursive solution. Not necessarily efficient, but a lot of fun:
public decimal ElapseddWorkingHours(DateTime start, DateTime finish)
{
if (start.Date == finish.Date)
return (finish - start).TotalHours;
if (IsWorkingDay(start.Date))
return ElapsedWorkingHours(start, new DateTime(start.Year, start.Month, start.Day, 17, 30, 0))
+ ElapsedWorkingHours(start.Date.AddDays(1).AddHours(DateStartTime(start.Date.AddDays(1)), finish);
else
return ElapsedWorkingHours(start.Date.AddDays(1), finish);
}
Take a look at the TimeSpan Class. That will give you the hours between any 2 times.
A single DB call can also get the holidays between your two times; something along the lines of:
SELECT COUNT(*) FROM HOLIDAY WHERE HOLIDAY BETWEEN #Start AND #End
Multiply that count by 8 and subtract it from your total hours.
-Ian
EDIT: In response to below, If you're holiday's are not a constant number of hours. you can keep a HolidayStart and a HolidayEnd Time in your DB and and just return them from the call to the db as well. Do an hour count similar to whatever method you settle on for the main routine.
Building on what #OregonGhost said, rather than using an IsWorkingDay() function at accepts a day and returns a boolean, have a HolidayCount() function that accepts a range and returns an integer giving the number of Holidays in the range. The trick here is if you're dealing with a partial date for your boundry beginning and end days you may still need to determine if those dates are themselves holidays. But even then, you could use the new method to make sure you needed at most three calls the to DB.
Try something along these lines:
TimeSpan = TimeSpan Between Date1 And Date2
cntDays = TimeSpan.Days
cntNumberMondays = Iterate Between Date1 And Date2 Counting Mondays
cntdays = cntdays - cntnumbermondays
NumHolidays = DBCall To Get # Holidays BETWEEN Date1 AND Date2
Cntdays = cntdays - numholidays
numberhours = ((decimal)cntdays * NumberHoursInWorkingDay )+((decimal)cntNumberMondays * NumberHoursInMondayWorkDay )
Use #Ian's query to check between dates to find out which days are not working days. Then do some math to find out if your start time or end time falls on a non-working day and subtract the difference.
So if start is Saturday noon, and end is Monday noon, the query should give you back 2 days, from which you calculate 48 hours (2 x 24). If your query on IsWorkingDay(start) returns false, subtract from 24 the time from start to midnight, which would give you 12 hours, or 36 hours total non-working hours.
Now, if your office hours are the same for every day, you do a similar thing. If your office hours are a bit scattered, you'll have more trouble.
Ideally, make a single query on the database that gives you all of the office hours between the two times (or even dates). Then do the math locally from that set.
The most efficient way to do this is to calculate the total time difference, then subtract the time that is a weekend or holiday. There are quite a few edge cases to consider, but you can simplify that by taking the first and last days of the range and calculating them seperately.
The COUNT(*) method suggested by Ian Jacobs seems like a good way to count the holidays. Whatever you use, it will just handle the whole days, you need to cover the start and end dates separately.
Counting the weekend days is easy; if you have a function Weekday(date) that returns 0 for Monday through 6 for Sunday, it looks like this:
saturdays = ((finish - start) + Weekday(start) + 2) / 7;
sundays = ((finish - start) + Weekday(start) + 1) / 7;
Note: (finish - start) isn't to be taken literally, replace it with something that calculates the time span in days.
Dim totalMinutes As Integer = 0
For minute As Integer = 0 To DateDiff(DateInterval.Minute, contextInParameter1, contextInParameter2)
Dim d As Date = contextInParameter1.AddMinutes(minute)
If d.DayOfWeek <= DayOfWeek.Friday AndAlso _
d.DayOfWeek >= DayOfWeek.Monday AndAlso _
d.Hour >= 8 AndAlso _
d.Hour <= 17 Then
totalMinutes += 1
Else
Dim test = ""
End If
Next minute
Dim totalHours = totalMinutes / 60
Piece of Cake!
Cheers!

Categories