i have an array of vacation dates. These are always going to be weekdays. Lets say
DateTime[] dates = new DateTime[] {"1/8/2010","1/3/2010","1/6/2010","1/7/2010","1/21/2010"}
i now have a single input date. Lets say:
DateTime vacationDateToCheck = 1/7/2010;
i want to find the vacation set (list of dates) given the set above. In particular, i want the first and last date of the list.
So for example, if i pass in vacationDateToCheck variable (1/7/2010) i would get back
Yes, you are on vacation from 1/6/2010 to 1/8/2010.
If i pass in 1/4/2010 it would return an empty result.
Here is the kicker. I want it to factor in weekends and go across weekends. So if i have a vacation on a friday and on the following Monday, i would want it to include that as one list.
any suggestions?
First, you'll have to sort your array, or at least make a sorted copy if it's important that you keep the order in the original list. If you don't, this isn't going to be easy.
Then, find the index of the element you're checking for. Once you've done that, create a loop going forwards, and one going backwards in your array, and count the date you've reached in the loop if the two dates are consective; otherwise, stop that loop (and do the other loop if you haven't done that already).
This assumes that all days in the range you're looking for can be found in the list, including weekends and holidays.
EDIT: If you want to include weekends implictly, you'll have to check the weekday using DayOfWeek while iterating over the days. The simplest way to do this is probably to also maintain a variable for the next expected day; every iteration in your loop, you add (or subtract, when moving backwards through the sorted array) one day. If the day you're processing is a Friday (or Monday when moving backwards), add/subtract an additional 2 days to skip the weekend.
Here's the basic idea (not tested, but you should get the point). I'm assuming that you've sorted dates already, and that you've found the initial date in your array (at index i). For simplicity, I'm also assuming that vacationDateToCheck is never a Saturday or Sunday; if it can be either of those, you'll have to adjust accordingly.
DateTime expectedDate = vacationDateToCheck.AddDays(1);
if (vacationDateToCheck.DayOfWeek == DayOfWeek.Friday)
expectedDate = expectedDate.AddDays(2);
DateTime startDate = vacationDateToCheck;
DateTime endDate = vacationDateToCheck;
for (int j = i + 1; i < dates.Length; i++) {
if (dates[i] == expectedDate) {
endDate = dates[i];
expectedDate = dates[i].AddDays(1);
if (dates[i].DayOfWeek == DayOfWeek.Friday)
expectedDate = expectedDate.AddDays(2);
}
else
{
break;
}
}
Iterating the other way is similar, only you add -1 and -2 days instead, and you check if the day of the week is a Monday.
Related
I have a problem finding the last time in the day (the time it's the biggest) picture below, how can I get that time?
I have to compare this time with his shift, but when I do it, I always read for the first time.
This is my code:
foreach (var shift in shifts)
{
if (von.ZPZ_Von <= shift.Arbeitsbeginn.AddMinutes(-20) &&
bis.ZPZ_Bis >= shift.Arbetsende.AddMinutes(-10))
return null;
else if (von.ZPZ_Von >= shift.Arbeitsbeginn.AddMinutes(20) &&
bis.ZPZ_Bis >= shift.Arbetsende.AddMinutes(10))
return null;
else if (von.ZPZ_Von <= shift.Arbeitsbeginn.AddMinutes(5)
&& bis.ZPZ_Bis <= shift.Arbetsende.AddMinutes(10)
)
return shift;
}
It is a method that finds the shift of workers, and if in the correct shift the worker returns the shift, if the worker comes 20 minutes or works more than 10 minutes then returns null.
This looks like data for one day:
So I need to compare the ZPZ_Bis with the last, or rather, the time.
At the moment, my method always compares ZPZ_Bis with the first departure time, i. 1899-12-30 09:52:00.000 in this case.
I would be grateful if somebody could help me with this problem, I have not really known how to handle this in the last few days.
this is my whole method:
private A_Arbeitszeitplan DetectShift(List<A_Arbeitszeitplan> shifts, PRAESENZZEIT von, PRAESENZZEIT bis, List<PRAESENZZEIT>arrivals)
If you only wish to use the TimeSpan of your DateTime you can get it like so:
From a DateTime, you can use .TimeOfDay - but that gives you a
TimeSpan representing the time into the day (10 hours).
Of course you need to compare TimeSpans with eachother:
if (von.ZPZ_Von.TimeOfDay <= shift.Arbeitsbeginn.AddMinutes(-20).TimeOfDay &&
bis.ZPZ_Bis.TimeOfDay >= shift.Arbetsende.AddMinutes(-10).TimeOfDay)
return null;
I have a scheduler program which allows the user to choose which days of the week it will be allowed to run a schedule.
The properties in my class include each day of the week.
Instead of using 7 statments like if (Schedule[i].Sunday == true)
How would I go about something like this:
if (Schedule[i].(DateTime.Now.DayOfWeek) == true)
You can make int DaysOfWeek property in the class, which will have its bits set according to day of week. Let's say 0000001 - Sunday, 0000010 - Monday, 0000011 - Sunday & Monday. I start from Sunday, because DayOfWeek enum starts from Sunday (http://referencesource.microsoft.com/#mscorlib/system/dayofweek.cs).
Then you can check the property the following way:
if ((DaysOfWeek & (1 << (int)DateTime.Now.DayOfWeek)) != 0)
{
}
You should use a multidimensional array for Schedule; your first index will be i, and your second one is the day of the week as an integer.
Then cast the DayOfWeek to an integer:
if (Schedule[i][((int)DateTime.Now.DayOfWeek)] == true)
Note that I'm using the DayOfWeek as the second index, rather than a property. This is why I mentioned using a multidimensional array earlier.
Make an enum with all day of the week set as flags,
[Flags]
Enum DaysOfWeek
{
None = 0,
Monday = 1 << 1,
Tuesday 1 << 2,
...
}
Then you will be able to use days.HasFlag(DaysOfWeek.Monday); to check for specific days.
http://msdn.microsoft.com/en-us/library/system.enum.hasflag(v=vs.110).aspx
You can also use them in a switch statement to do different actions for each days.
Sadly the current C# system.dayofweek has values from 0 to 6 http://msdn.microsoft.com/en-us/library/system.dayofweek(v=vs.110).aspx so it doesn't work with HasFlag
It's tough to say without knowing the structure of your Schedule, but if you're storing a DayOfWeek type in your Schedule you can use this:
Schedule[i].DayOfWeek == DateTime.Today.DayOfWeek;
You could also get a list of scheduled items from your List like this
var scheduledItems = schedule.Where(x=>x.DayOfWeek == DateTime.Today.DayOfWeek)
I am new to this and I have a little trouble to do this:
I have a list of timeitems:
06:40 - 07:10
06:55 - 07:13
07:00 - 08:35
07:13 - 07:14
09:00 - 10:00
10:00 - 11:00
12:00 - 13:00
12:30 - 14:00
Now I want all items which intersects:
06:40 - 07:10
06:55 - 07:13
07:00 - 08:35
07:13 - 07:14
12:00 - 13:00
12:30 - 14:00
var intersects = timeitems
.Where(a => timeitems
.Any(b => Utilities.IsBetween(a.SpanRangeStartIndex, b.SpanRangeStartIndex, b.SpanRangeEndIndex)))
.AsParallel()
.ToList();
But I only get this and I donĀ“t know why:
06:55 - 07:13
07:00 - 08:35
07:13 - 07:14
12:30 - 14:00
Thanks four your help (Remember, I am new to .net :-)
edit*
ok, a timeitem ist just a list of items with two properties:
Item1(SpanRangeStartIndex=06:40 SpanRangeEndIndex=07:10 )
Item2(SpanRangeStartIndex=06:55 SpanRangeEndIndex=07:13 )
...
Utilities.IsBetween checks if a value is between two other values (if 3 is between 2 and 6 -> true)
public static bool IsBetween(int value, int start, int end)
{
return (value > start) & (value <end);
}
Sorry for my bad English and bad c#-skill... I am very new to this
thanks
Welcome to SO!
I believe the problem that you're trying to solve is that you want to know which ranges in your set of ranges overlap any of the other ranges in the same set.
The problem seems to be that you test one end of the range for "between" but not the other.
(I wrote a sample program that does what yours does and added some comments and removed the 'SpanRange' and 'Index' from the property names as well as the .AsParallel() call - which might change the order of the data returned but still have the same overall content.)
var intersects =
data.Where(a => data
.Any(b =>
IsBetween(a.Start, b.Start, b.End) // <-- this is the test you did
|| IsBetween(a.End, b.Start, b.End) // <-- the missing other end
// || IsBetween(b.Start, a.Start, a.End) // potentially necessary
// || IsBetween(b.End, a.Start, a.End) // potentially necessary
));
I added the other two commented IsBetween calls since I think there are likely "completely contained" range tests that might fail to show when one range is completely contained within the other.
On a different note, I might try to change your thinking a little bit on how to test when ranges intersect by first thinking of the simpler case of how two ranges would NOT intersect.
Two ranges do not intersect when either:
rangeA.End < rangeB.Start which says: rangeA is entirely 'to the left of' rangeB
rangeA.Start > rangeB.End which says: rangeA is entirely 'to the right of' rangeB
doNotIntersect = (rangeA.End < rangeB.Start) || (rangeA.Start > rangeB.End)
Thus we can test whether ranges intersect by negating the above expression:
isIntersecting = (rangeA.End >= rangeB.Start) && (rangeA.Start <= rangeB.End)
However, I noted that your between test doesn't use ">=" or "<=" so a range that shares only an end with the other's start doesn't intersect. Because of this, the 09:00 - 10:00 range in the sample would not overlap with the 10:00 - 11:00 range in the sample. So, it's likely you would use > & < rather than the >= & <= operators.
I'd be happy to post the code and the results if you need it.
You're seeing this problem because you are only getting "items such that this item starts during another item", and not including "items such that another item starts during this item".
A simple fix would be
var intersects = timeitems
.Where(a => timeitems.Any(b =>
Utilities.IsBetween(a.SpanRangeStartIndex,
b.SpanRangeStartIndex, b.SpanRangeEndIndex) ||
Utilities.IsBetween(b.SpanRangeStartIndex,
a.SpanRangeStartIndex, a.SpanRangeEndIndex)))
.AsParallel()
.ToList();
which makes your code symmetrical and would include the missing 06:40 - 07:10 and 12:00 - 13:00.
However, this (as with your original) is very inefficient - O(n^2), when an O(n) algorithm should be possible.
Think of when you are dealing with the time from 12:30 to 14:00
The preceding element (from 12:00 to 13:00) intersects with that window, but your query misses it because you are only checking to see if the start time is in the range when you have to check if the end time is in the range.
That said, you can change your query to this (removed the AsParallel and ToList methods as they aren't integral to the solution):
var intersects = timeitems
.Where(a => timeitems
.Any(b =>
// Check the start of the window...
Utilities.IsBetween(a.SpanRangeStartIndex,
b.SpanRangeStartIndex, b.SpanRangeEndIndex) &&
// *AND* the end of the window...
Utilities.IsBetween(a.SpanRangeEndIndex,
b.SpanRangeStartIndex, b.SpanRangeEndIndex)));
Right now, you're iterating through the entire timeItems sequence for every item, even items that you know have already been matched and intersect (since you're not pairing them, you don't need to say item a overlaps with item b, you simply have to return that it overlaps).
With this in hand, you can reduce having to iterate through N^2 items by not using LINQ, but only if your collections are materialized and implement the IList<T> interface, which arrays and List<T> instances do).
You would look ahead, keeping track of what overlaps and was yielded, like so:
public IEnumerable<TimeItem> GetOverlappingItems(this IList<TimeItem> source)
{
// Validate parameters.
if (source == null) throw new ArgumentNullException("source");
// The indexes to ignore that have been yielded.
var yielded = new HashSet<int>();
// Iterate using indexer.
for (int index = 0; index < source.Count; ++index)
{
// If the index is in the hash set then skip.
if (yielded.Contains(index)) continue;
// Did the look ahead yield anything?
bool lookAheadYielded = false;
// The item.
TimeItem item = source[index];
// Cycle through the rest of the indexes which are
// not in the hashset.
for (int lookAhead = index + 1; lookAhead < source.Count; ++lookAhead)
{
// If the item has been yielded, skip.
if (yielded.Contains(lookAhead)) continue;
// Get the other time item.
TimeItem other = source[lookAhead];
// Compare the two. See if the start or the end
// is between the look ahead.
if (Utilities.IsBetween(item.SpanRangeStartIndex,
other.SpanRangeStartIndex, other.SpanRangeEndIndex) ||
Utilities.IsBetween(item.SpanRangeEndIndex,
other.SpanRangeStartIndex, other.SpanRangeEndIndex))
{
// This is going to be yielded.
lookAheadYielded = true;
// Yield the item.
yield return other;
// Add the index to the hashset of what was yielded.
yielded.Add(lookAhead);
}
}
// Was a look ahead yielded?
// No need to store the index, we're only moving
// forward and this index doesn't matter anymore.
if (lookAheadYielded) yield return item;
}
}
LINQ might not be a good idea here, as you're doing a LOT of double counting. If you can assume they're all sorted by the starting index (which you can just order it using LINQ if you can't make that guarantee) then it's a whole lot easier to keep a rolling window as you iterate over them:
timeitem workingRange = null, rangeStart = null;
bool matched = false;
foreach(timeitem t in timeitems) // timeitems.OrderBy(ti => ti.SpanRangeStartIndex) if unsorted
{
if(workingRange is null)
{
rangeStart = t;
workingRange = new timeitem { SpanRangeStartIndex = t.SpanRangeStartIndex, SpanRangeEndIndex = t.SpanRangeEndIndex };
continue;
}
if(Utilities.IsBetween(t.SpanRangeStartIndex,
workingRange.SpanRangeStartIndex, workingRange.SpanRangeEndIndex))
{
if(!matched)
{
matched = true;
yield return rangeStart;
}
workingRange.SpanRangeEndIndex = Math.Max(workingRange.SpanRangeEndIndex, t.SpanRangeEndIndex);
yield return t;
}
else
{
matched = false;
rangeStart = t
workingRange = new timeitem { SpanRangeStartIndex = t.SpanRangeStartIndex, SpanRangeEndIndex = t.SpanRangeEndIndex };
}
}
A few notes. Keeping a reference to the original first item of the range, since I don't know whether it's a struct/class and it's better to yield the original items unless you're performing some sort of transformation. The working range can easily be modified to use DateTime (which might be easier to read/understand). We need to keep track of whether we've matched yet, because we still need to yield/return the original working item and ensure we don't yield it again (can't use ranges as a measure, as subsequent timeitems could be entirely within the initial range). Finally, if the item we're checking is not within the range, we reset all our state variables and treat them as our beginning range.
This ensures you only ever have to traverse through the collection once, at the expense of sorting it beforehand (which, if you can ensure they get to this point sorted in the first place you eliminate that need anyway). Hope that helps, wish there was an easier way.
On our windows application, We have startDate and EndDate. On click of Execute button event, we need to call a third party web service with our search string + daterange( date from 01/01/2010 to 12/31/2010). Now our search criteria can return us thousands of records but web service have limitation of able to return only 10K records per transaction.
Which required us to break down our dateRange. So basically we need following;
For (X dateRange if RecordCount > 10000) then
X dateRange/2 which will be 01/01/2010 to 06/01/2010 in our case and check condition again and do this recursively until we get daterange block where RecordCount is < 10000.
Then start with Next date, for example, if we get 9999 records for 01/01/2010 to 03/30/2010 then we need to get records for next block starting 04/01/2010
Is this possible with Recursion?
RecursionFunction(dtStart, dtEnd)
{
if (WebService.RecordCount > 9999)
{
TimeSpan timeSpan = dtEnd.Subtract(dtStart);
DateTime mStart = dtStart;
DateTime mEnd = dtStart.AddDays(timeSpan.Days / 2);
RecursionFunction(dtStart,dtEnd);
}
else
{
Get Records here
}
}
But with above code, recursion will have following blocks
01/01/2010, 12/31/2010 > 10000
01/01/2010, 07/03/2010 > 10000
01/01/2010, 04/02/2010 < 10000
So after finishing getting record, recursion will start again with block 01/01/2010,07/03/2010 which we don't need. We need to start next recursion with 04/03/2010,12/31/2010
Thanks in advance for help.
It looks like you are trying to split the input range until it is small enough to handle. Try calling it for both ranges:
RecursionFunction(mStart, mEnd);
RecursionFunction(mEnd.AddDays(1), dtEnd);
The first step is to change the RecursionFunction call (at line 8 of your example) to:
RecursionFunction(mStart, mEnd);
But, then, you'll also need to call it again with the other half of the date range.
RecursionFunction(mEnd + AddDays(1), dtEnd);
Also, you need to handle the results (presumably combining the two answers).
var set1 = RecurseFunction(...);
var set2 = RecurseFunction(...);
return set1.Concat(set2);
This is like divide and conquer. You need to get results from the left and the right of the split and combine them and return that value. So you can keep getting smaller until you have enough data you can deal with and just return that. Then keep joining the result sets together.
public IList<Data> GetRecords(DateTime start, DateTime end)
{
var RecordCount = WebService.RecordCount(start, end);
if (RecordCount < 10000) return WebService.GetRecords(start, end);
DateTime l, m, e;
l = start;
e = end;
var midDay = end.Subtract(start).TotalDays / 2;
m = start.AddDays(midDay);
var left = GetRecords(l, m);
var right = GetRecords(m.AddDays(1), e);
return left.Concat(right);
}
This is how I would do it
static List<string> RecursiveGet(DateTime StartDate, DateTime EndDate, List<string> Output)
{
if (Webservice.RecordCount > 9999)
{
TimeSpan T = EndDate.Subtract(StartDate);
T = new TimeSpan((long)(T.Ticks / 2));
DateTime MidDate = StartDate.Add(T);
Output.AddRange(RecursiveGet(StartDate, MidDate, Output));
Output.AddRange(RecursiveGet(MidDate.AddMilliseconds(1), EndDate, Output));
}
else
{
//Get Records here, return them in array
Output.Add("Test");
}
return Output;
}
static List<string> GetRecords(DateTime StartDate, DateTime EndDate)
{
return RecursiveGet (StartDate, EndDate, new List<string>());
}
Note, Couldn't test it
It works by dividing the dates in half, then searching each of them, and if one is still bigger than 9999, then doing it again.
An easy way would be a form of pagination. If your using JSON or XML, you can put the amount of total results and just return a set number of results (return the offset too). This way you can do a loop to check if your on the last page and after you get the last results page, break out of it.
Don't forget to put checks in if a particular transaction fails though. It's not an ideal solution on such a large dataset but it is a workaround
It sounds much easier to just reuse the last date for the data you actually got back in a while-loop than to home in with recursion like this.
Then start with Next date, for example, if we get 9999 records for 01/01/2010 to 03/30/2010 then we need to get records for next block starting 04/01/2010
March has 31 days.
Pseudo-C# code
var dtStart = DateTime.Parse("2010-01-01");
var dtEnd = DateTime.Parse("2010-12-31");
var totalRecords = new List<RecordType>();
var records = WebService.Get(dtStart, dtEnd);
totalRecords.Add(records);
while (dtStart < dtEnd && records.Count > 9999)
{
dtStart=records.Last().Date;
records = WebService.Get(dtStart, dtEnd);
totalRecords.Add(records);
}
To ease the load on the service you could calculate the timespan for the previous run and only get that many days for the next run in the while-loop.
How you should handle the inevitable doublets depends on the data on the records.
I just realized I presumed you had a date in the returned data. If not, then disregard this answer.
i need to calculate the number of workdays between two dates. a workday is any day between Monday through Friday except for holidays. the code below does this, but it uses a loop. does anyone see a way to get rid of the loop or at least optimize it?
thanks
konstantin
using System;
using System.Linq;
namespace consapp
{
static class Program
{
static void Main(string[] args)
{
var holidays = new DateTime[] { new DateTime(2010, 11, 23), new DateTime(2010, 11, 30) };
var date_start = new DateTime(2010, 12, 3);
var date_end = date_start.AddDays(-9.9);
var duration = (date_end - date_start).Duration();
for (var d = date_end; d < date_start; d = d.Date.AddDays(1))
{
if (holidays.Contains(d.Date) || d.DayOfWeek == DayOfWeek.Saturday || d.DayOfWeek == DayOfWeek.Sunday)
{
duration -= TimeSpan.FromDays(1) - d.TimeOfDay;
}
}
Console.WriteLine(duration);
}
}
}
I would investigate an algorithm like the following. (Sorry no code provided.)
Count the number of full 7-day weeks between your start and end date. You should be able to do this with .NET DateTime and TimeSpan objects.
For each full 7-day week, add 5 days to your result.
Figure out the partial weeks including your start and end dates.
Loop through your holidays and reduce your result by 1 for each holiday between your start and end date. Looping is better here because there are likely far fewer holidays to loop over than days between your start and end date.
Have fun!
EDIT: For source code, check out this answer: Calculate the number of business days between two dates?
Is performance really problematic here? Unless profiling suggests otherwise I'd guess this code doesn't really slow down your application. It's performance should be fine unless you calculate the workdays for thousands of long intervals per second.
If your holiday list is much larger than just two dates then convert it into a HashSet<T> which has O(1) lookup time.
And of course you can turn around the code. So you don't loop over the days in the interval, but over the holidays. Then you just calculate the number of week-days in the interval(should be simple math) and subtract the number of holidays that fall on a week-day.
If it's really necessary you can pre-calculate the workdays since some fixed date, and then subtract the lookup result from the beginning of the period from the lookup result from the end of the period.
if you want faster code, don't loop over each day in the range:
remove from your list of holidays all holidays that fall on sunday or saturday, then use the timespan methods to give you the number of days between the two dates. With a little math (think about integer division by 7) you can get the number of mon-thursday days in that range, subtract the number of holidays that don't fall on the weekend from that number and you are done.
Just roll with it as is. This is not going to waste much time since the bounds are small. When you have some working code, move on. No need to mercilessly optimise code for no reason.
just because I started this as a fun puzzle, here's the code:
[Test]
public void TestDateTime() {
var start = DateTime.Now.Date;
var end = DateTime.Now.Date.AddDays(35);
var workdays = (end - start).Days - ((end - start).Days/7)*2
- (((end - start).Days%7==0)?0:(((int)start.DayOfWeek==0)?1:Math.Max(Math.Min((int)start.DayOfWeek + (end - start).Days%7 - 6, 2), 0)));
new []{DateTime.Now.AddDays(19), DateTime.Now.AddDays(20)}.ToList().ForEach(
x => { if (x.DayOfWeek != DayOfWeek.Saturday && x.DayOfWeek != DayOfWeek.Sunday) workdays--; });
Console.Out.WriteLine("workdays = {0}", workdays);
}
Christmas day and Boxing day are included as holidays.