System.OutOfMemoryException thrown when computing date ranges - c#

It might be a simple fix, but I can't for the life of me think of how to do this. I compute a bunch of StartDates and End Dates into a bunch of arrays of dates using this query:
this.Reserved = unit.Reservations.Where(r => r.Active.HasValue && r.Active.Value).SelectMany(r => Utilities.DateRangeToArray(r.StartDate, r.EndDate)).ToArray();
Utilities.DateRangeToArray() is defined as follows:
public static IEnumerable<DateTime> DateRangeToArray(DateTime start, DateTime end) {
DateTime curDate = start;
while (curDate <= end) {
yield return curDate;
curDate.AddDays(1);
}
}
Is there a way to make this less memory intensive?
Thanks!

Your code is broken - AddDays doesn't change the existing value, it returns a new value. You're ignoring that new value, thus creating an infinite loop.
Change your code to:
public static IEnumerable<DateTime> DateRangeToArray(DateTime start,
DateTime end) {
DateTime curDate = start;
while (curDate <= end) {
yield return curDate;
curDate = curDate.AddDays(1);
}
}
Another hint: unit testing can help you find this sort of problem long before you try to use the method in a LINQ query. I'd also change the name, given that it's not returning an array.

You're sure you don't have any reservations where r.StartDate > r.EndDate, right? If you do, you'll get an infinite loop, I think.

I assume the out of memory is when converting the result to the array. Two points:
The output will contain duplicate dates for overlapping reservations.
Perhaps Reserved should be a collection of date ranges (start,end) rather than containing every date?

Related

Comparing DateTime.Now to value stored in SQL

I am converting ADODB namespace code to SqlClient. I am trying to replace code that utilized recordset.Value. I am having problems converting this line of code. How can I subtract DateTime.Now from the value in the time_of_lock (Datetime data type) column in SQL?
else if (DateTime.Now - rs.Fields["time_of_lock"].Value < TimeSpan.FromMinutes(15))
I am assuming you can get two DateTime values from your code. If so, try pop both into the following function:
private bool TimesApartNoMoreThan(DateTime first, DateTime second, int threshold)
{
return (second - first).TotalMinutes > threshold;
}
when you subtract two DateTimes, you get a TimeSpan. Having obtained that, you can represent it as whatever unit of duration you need (in this case, minutes):
From your statement, I am assuming rs.Fields["time_of_lock"] is of DateTime datatype. If that is the case, please try this:
DateTime.Now .Subtract(rs.Fields["time_of_lock"].Value).TotalMinutes < 15

What is the best way to make this code recursive

I have to edit some code that has a proposedDate (as a DateTime object called minDate) and an array of blackout dates. Given a proposed Date, it tries to see if this is valid (NOT a blackout dates). If it is a blackout date, then keep checking the next day until you find a date that is not a valid checkout date. The existing code looks like this
if ( blackoutDates.Contains(minDate))
{
minDate = minDate.AddDays(1);
dateOffset = dateOffset + 1;
if ( blackoutDates.Contains(minDate))
{
minDate = minDate.AddDays(1);
dateOffset = dateOffset + 1;
if (blackoutDates.Contains(minDate))
{
minDate = minDate.AddDays(1);
dateOffset = dateOffset + 1;
}
}
}
Clearly there is a repeated pattern here and I am trying to figure out the best way to clean up this code and make it elegant.
No need for recursion. You can do this in a loop.
while(blackoutDates.Contains(minData)){
minData = minData.AddDays(1);
++dataOffset;
}
I don't know what language is this, but check if there is already a standard API for doing what you need first.
I wouldn't make it recursive. I would make it a while loop:
while(blackoutDates.Contains(minDate))
{
minDate = minDate.AddDays(1);
dateOffset = dateOffset + 1;
}
Recursion can express loops, but looping constructs are usually clearer when used in the context they are designed for. They also make it a bit simpler to reach data that is outside the scope of the loop than recursion does (specifically local variables).

How to check if data is in range

I was wondering if there is any neat way to check is data is in allowed range. I mean in c# we can represent data from 0001-01-01 to (I think) 9999-01-01. However if we try to do something like that
DateTime result = DateTime.Parse("0001-01-01").Subtract(TimeSpan.FromDays(1))
I get an exception. Is there any neat way to check is it is possible to do DateTime operations (addition subtraction etc)
Just use the comparison operators (>, <, >=, <=, == and !=), as they are implemented in DateTime.
Example:
DateTime lowerAllowedDate = new DateTime(1,1,1); // 01/01/0001
DateTime upperAllowedDate = new DateTime(3000, 12, 31) // 31/12/3000
DateTime now = DateTime.Now
if (lowerAllowedDate <= now && now < upperAllowedDate)
{
//Do something with the date at is in within range
}
Consider these extension methods.
public static class ValidatedDateTimeOperations
{
public static bool TrySubtract (this DateTime dateTime, TimeSpan span, out DateTime result)
{
if (span < TimeSpan.Zero)
return TryAdd (dateTime, -span, out result);
if (dateTime.Ticks >= span.Ticks)
{
result = dateTime - span;
return true;
}
result = DateTime.MinValue;
return false;
}
public static bool TryAdd (this DateTime dateTime, TimeSpan span, out DateTime result)
{
if (span < TimeSpan.Zero)
return TrySubtract (dateTime, -span, out result);
if (DateTime.MaxValue.Ticks - span.Ticks >= dateTime.Ticks)
{
result = dateTime + span;
return true;
}
result = DateTime.MaxValue;
return false;
}
}
The can be called like this:
DateTime result;
if (DateTime.MinValue.TrySubtract (TimeSpan.FromDays(1), out result)
{
// Subtraction succeeded.
}
Checking for an overflow in a given operation beforehand is cumbersome and I'm not really sure it's really worth it against simply handling the exception.
You could for example do the following when subtracting:
DateTime date;
TimeSpan subtractSpan;
if ((date - DateTime.MinValue) < subtractSpan)
{
//out of range exception: date - subtractSpan
}
Worth it? Your call.
Take a look at the DateTime structure documentation in MSDN.
In particular, you can take a look at:
TryParse and TryParseExact
The comparison operators
MinValue and MaxValue
You can also put try..catch (ArgumentOutOfRangeException) around the DateTime values you are trying to use.
However, if you are consistently (or ever?) running into this kind of exception, I'd take a closer look at your design. Unless you are doing some serious date-crunching, I don't know of any instance where I would be bumping into the min and max values.

Finding missing dates from a collection

I've dates array with values below:
"07/07/2011", "08/05/2011", "09/07/2011", "12/07/2011"
Using this as input in my C# program, I need to build a new collection which will have missing dates..ie. 10/07/2011, 11/07/2011.
Is recursion the best way to achieve this?
Thanks.
Not at all. This should be a straightforward process. You have a starting date, you have an interval... You start walking the array and if the next value does not match your previous value plus the interval you insert a new value into the new array. If it does match, you copy that value.
If you need more data (metadata) about each entry then create a class that holds the date and whatever metadata you find useful (e.g. a bool like this_value_was_inserted_artificially)
Using recursion would unnecessarily complicate things.
No recursion needed. This can probably be optimized, but should do the job:
public static IEnumerable<DateTime> FindMissingDates(IEnumerable<DateTime> input)
{
// get the range of dates to check
DateTime from = input.Min();
DateTime to = input.Max();
// how many days?
int numberOfDays = to.Subtract(from).Days;
// create an IEnumerable<DateTime> for all dates in the range
IEnumerable<DateTime> allDates = Enumerable.Range(0, numberOfDays)
.Select(n => from.AddDays(n));
// return all dates, except those found in the input
return allDates.Except(input);
}
You can pull this off very nicely with Linq:
var dates=new[]{
DateTime.Parse("07/07/2011"),
DateTime.Parse("08/07/2011"),
DateTime.Parse("09/07/2011"),
DateTime.Parse("12/07/2011")};
var days=(dates.Max()-dates.Min()).Days;
var otherDays=
Enumerable
.Range(0,days)
.Select(d=>dates.Min().AddDays(d))
.Except(dates);

Checking if Var from LINQ query is Null and returning values older than x

Hello everyone I'm currently having 2 issues with the code below:
Upon return of result1 I'm trying to complete a check to see if it is != null and if it is't it will begin to delete the records selected. The issue is that even when result1 returns nothing and defaults the if statement doesn't pick this up so I guess I'm missing something but what?
I'm wishing to return only values which are over 10 mintues old (this will later be scaled to 12 hours) to do this I'm checking against a.DateTime which is a DateTime value stored in a database. However if i use the <= or >= operators it doesn't work so again what am I missing?
DateTime dateTime = DateTime.Now.Subtract(new TimeSpan(0, 0, 10, 0));
var result1 = (from a in cpuInfo
where a.DateTime <= dateTime
select a).DefaultIfEmpty(null);
if (result1 != null)
{
foreach (TblCPUInfo record1 in result1)
{
localDB.TblCPUInfo.DeleteOnSubmit(record1);
localDB.SubmitChanges();
}
}
Philippe has talked about the sequence side of things - although you don't even need the call to Any(). After all, if there are no changes the loop just won't do anything.
Do you really want to submit the changes on each iteration? It would probably make more sense to do this once at the end. Additionally, you can use DateTime.AddMinutes to make the initial "10 minutes ago" simpler, and if you're only filtering by a Where clause I'd use dot notation.
After all these changes (and making the variable names more useful), the code would look like this:
DateTime tenMinutesAgo = DateTime.Now.AddMinutes(-10);
var entriesToDelete = cpuInfo.Where(entry => entry.DateTime <= tenMinutesAgo);
foreach (var entry in entriesToDelete)
{
localDB.TblCPUInfo.DeleteOnSubmit(entry);
}
localDB.SubmitChanges();
Now, as for why <= isn't working for you... is it possible that you need the UTC time instead of the local time? For example:
DateTime tenMinutesAgo = DateTime.UtcNow.AddMinutes(-10);
If that still isn't working, I suggest you have a look at the generated query and play with it in a SQL tool (e.g. Enterprise Manager or SQL Server Management Studio) to work out why it's not returning any results.
DefaultIfEmpty will return a single item with the content you provided, so in your case a collection with a single value "null".
You should check for elements in the collection using the Any() extension method. In your case:
DateTime dateTime = DateTime.Now.Subtract(new TimeSpan(0, 0, 10, 0));
var result1 = from a in cpuInfo
where a.DateTime <= dateTime
select a;
if (result1.Any())
{
foreach (TblCPUInfo record1 in result1)
{
localDB.TblCPUInfo.DeleteOnSubmit(record1);
localDB.SubmitChanges();
}
}
But if this is really your code, you can skip the Any() check completely, because the foreach loop will not run if there are no elements in result1.

Categories

Resources