LINQ query for DateTime ranges overlapping in collection items - c#

In the following case, the method CanUnloadAll checks if there are no overlap in the unloading times, considering all trucks.
The current scenario, should returns TRUE but it is returning FALSE.
What's wrong with the logic in the LINQ query?
using System;
using System.Collections.Generic;
using System.Linq;
public class UnloadingTime
{
public DateTime Start { get; private set; }
public DateTime End { get; private set; }
public UnloadingTime(DateTime start, DateTime end)
{
this.Start = start;
this.End = end;
}
}
public static class UnloadingTrucks
{
public static bool CanUnloadAll(IEnumerable<UnloadingTime> unloadingTimes)
{
return unloadingTimes.Any(
TruckA => unloadingTimes.Any(
TruckB => TruckB != TruckA &&
!((
TruckA.Start.Date >= TruckB.End.Date ||
TruckB.Start.Date >= TruckA.End.Date
))
));
}
public static void Main(string[] args)
{
var format = System.Globalization.CultureInfo.InvariantCulture.DateTimeFormat;
UnloadingTime[] unloadingTimes = new UnloadingTime[]
{
new UnloadingTime(DateTime.Parse("3/4/2019 19:00", format), DateTime.Parse("3/4/2019 20:30", format)),
new UnloadingTime(DateTime.Parse("3/4/2019 22:10", format), DateTime.Parse("3/4/2019 22:30", format)),
new UnloadingTime(DateTime.Parse("3/4/2019 22:40", format), DateTime.Parse("3/4/2019 23:00", format))
};
Console.WriteLine(UnloadingTrucks.CanUnloadAll(unloadingTimes));
}
}
To make it easier, I am using .NET Fiddle.
https://dotnetfiddle.net/Mis663
Regards
Solution:
public static bool CanUnloadAll(IEnumerable<UnloadingTime> unloadingTimes)
{
bool hasOverlap = unloadingTimes.Any(
TruckA => unloadingTimes.Any(
TruckB => TruckB != TruckA &&
!((
TruckA.Start >= TruckB.End ||
TruckB.Start >= TruckA.End
))
));
return !hasOverlap;
}

You are using the DateTime.Date property, which is the date component without the time part. You must use the DateTime:
bool flag = TruckA.Start >= TruckB.End || TruckB.Start >= TruckA.End

Related

Generic type and field accessing in C#

Instead of T (Type) if I use class name i.e. sampleclass this code works properly,
but if I use T, then it shows that
'T' does not contain a definition for 'TimeStamp' and no accessible
extension method 'TimeStamp' accepting a first argument of type 'T'
could be found (are you missing a using directive or an assembly
reference?)
//Get collection.
var collection = this.GetDatabaseConnection().GetCollection<T>
(collectionName);
//filter to read specific data.
var filter = Builders<T>.Filter.Where(result => result.TimeStamp >=
startTime && result.TimeStamp <= endTime);
List < T > queryData = collection.Find<T>(filter, null).ToList();
Previously it was as follows and working finely:
//Get collection.
var collection = this.GetDatabaseConnection().GetCollection<Sampleclass>.
(collectionName);
//filter to read data using specific timestamp.
var filter = Builders<Sampleclass>.Filter.Where(result =>
result.TimeStamp >=
startTime && result.TimeStamp <= endTime);
List < Sampleclass > queryData = collection.Find<Sampleclass>
(filter, null).ToList();
i would do it using a base class like this
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using System;
using System.Linq;
namespace StackOverflow
{
public class Program
{
public class Entity
{
[BsonId]
public ObjectId Id { get; set; }
public DateTime TimeStamp { get; set; }
}
public class Sample : Entity
{
public string Something { get; set; }
}
private static void Main(string[] args)
{
var collection = new MongoClient("mongodb://localhost:27017")
.GetDatabase("Test")
.GetCollection<Entity>("Samples");
var sample = new Sample
{
Id = ObjectId.GenerateNewId(),
Something = "something",
TimeStamp = DateTime.UtcNow
};
collection.InsertOne(sample);
var result = collection.AsQueryable()
.Where(s =>
s.TimeStamp >= DateTime.UtcNow.AddMinutes(-1) &&
s.TimeStamp <= DateTime.UtcNow.AddMinutes(1))
.ToArray();
}
}
}
The problem is that T doesn't have a TimeStamp property as it's a generic type. If you always have to access the TimeStamp of whatever type you process in your collection, you could consider using an interface that has TimeStamp as a get function. All the types you want to process would have to implement that interface.
public interface MyInterface
{
TimeSpan TimeStamp { get; } //or whatever type your are using for your TimeStamp property
}
var collection = this.GetDatabaseConnection().GetCollection<MyInterface>
(collectionName);
var filter = Builders<MyInterface>.Filter.Where(result => result.TimeStamp >=
startTime && result.TimeStamp <= endTime);
List<MyInterface> queryData = collection.Find<MyInterface>(filter,null).ToList();
This way it's still quite generic. If you want to still use type T you would have to work with reflection to find the property TimeStamp on your processed type.
Edit: Some advice for using type T: You always have to consider that T can be anything, from an integer to any other object imaginable. Sometimes it can be quite useful if you have a method for example that doesn't need to access any properties on the type T object it is given but is used for many different types. I lately used T to build a little ParseOrDefault method to catch values that can't be converted:
private T ParseOrDefault<T>(Object value, T defaultValue)
{
try
{
return (T)Convert.ChangeType(value, typeof(T));
}
catch (Exception)
{
return defaultValue;
}
}
As soon as you have to deal with more specific use cases T is mostly not that useful.
after reading your comments on my first answer, i'd like to propose the following solution using MongoDB.Entities
hope this is more or less what you're trying to achieve...
using MongoDB.Entities;
using System;
using System.Collections.Generic;
namespace StackOverflow
{
public class Program
{
public class MySample : Entity
{
public DateTime TimeStamp { get; set; }
}
public class Sample1 : MySample
{
public string SomeProp { get; set; }
}
public class Sample2 : MySample
{
public string AnotherProp { get; set; }
}
private static void Main(string[] args)
{
new DB("test");
var sample1 = new Sample1 { SomeProp = "some prop value", TimeStamp = DateTime.UtcNow };
var sample2 = new Sample2 { AnotherProp = "another prop", TimeStamp = DateTime.UtcNow };
DB.Save(sample1);
DB.Save(sample2);
var s1 = FindSamples<Sample1>();
var s2 = FindSamples<Sample2>();
List<T> FindSamples<T>() where T : MySample
{
return DB.Find<T>()
.Many(s =>
s.TimeStamp >= DateTime.UtcNow.AddMinutes(-1) &&
s.TimeStamp <= DateTime.UtcNow.AddMinutes(1));
}
}
}
}

How to check the current system is falls between given date time ? C# [duplicate]

I need to know if a Date is between a DateRange. I have three dates:
// The date range
DateTime startDate;
DateTime endDate;
DateTime dateToCheck;
The easy solution is doing a comparison, but is there a smarter way to do this?
Nope, doing a simple comparison looks good to me:
return dateToCheck >= startDate && dateToCheck < endDate;
Things to think about though:
DateTime is a somewhat odd type in terms of time zones. It could be UTC, it could be "local", it could be ambiguous. Make sure you're comparing apples with apples, as it were.
Consider whether your start and end points should be inclusive or exclusive. I've made the code above treat it as an inclusive lower bound and an exclusive upper bound.
Usually I create Fowler's Range implementation for such things.
public interface IRange<T>
{
T Start { get; }
T End { get; }
bool Includes(T value);
bool Includes(IRange<T> range);
}
public class DateRange : IRange<DateTime>
{
public DateRange(DateTime start, DateTime end)
{
Start = start;
End = end;
}
public DateTime Start { get; private set; }
public DateTime End { get; private set; }
public bool Includes(DateTime value)
{
return (Start <= value) && (value <= End);
}
public bool Includes(IRange<DateTime> range)
{
return (Start <= range.Start) && (range.End <= End);
}
}
Usage is pretty simple:
DateRange range = new DateRange(startDate, endDate);
range.Includes(date)
You could use extension methods to make it a little more readable:
public static class DateTimeExtensions
{
public static bool InRange(this DateTime dateToCheck, DateTime startDate, DateTime endDate)
{
return dateToCheck >= startDate && dateToCheck < endDate;
}
}
Now you can write:
dateToCheck.InRange(startDate, endDate)
You can use:
return (dateTocheck >= startDate && dateToCheck <= endDate);
I’ve found the following library to be the most helpful when doing any kind of date math. I’m still amazed nothing like this is part of the .Net framework.
http://www.codeproject.com/Articles/168662/Time-Period-Library-for-NET
Following on from Sergey's answer, I think this more generic version is more in line with Fowler's Range idea, and resolves some of the issues with that answer such as being able to have the Includes methods within a generic class by constraining T as IComparable<T>. It's also immutable like what you would expect with types that extend the functionality of other value types like DateTime.
public struct Range<T> where T : IComparable<T>
{
public Range(T start, T end)
{
Start = start;
End = end;
}
public T Start { get; }
public T End { get; }
public bool Includes(T value) => Start.CompareTo(value) <= 0 && End.CompareTo(value) >= 0;
public bool Includes(Range<T> range) => Start.CompareTo(range.Start) <= 0 && End.CompareTo(range.End) >= 0;
}
In case anyone wants it as a Validator
using System;
using System.ComponentModel.DataAnnotations;
namespace GROOT.Data.Validation;
internal class DateRangeAttribute : ValidationAttribute
{
public string EndDate;
public string StartDate;
public override bool IsValid(object value)
{
return (DateTime)value >= DateTime.Parse(StartDate) && (DateTime)value <= DateTime.Parse(EndDate);
}
}
Usage
[DateRange(
StartDate = "01/01/2020",
EndDate = "01/01/9999",
ErrorMessage = "Property is outside of range")
]

Merge two list of TimeRanges into one

I have a class named TimeRange and another Interval both have same following structure
public class TimeRange
{
public TimeOfDay start{get; set;}
public TimeOfDay end{get; set;}
}
I have two list
List<TimeRange> timeRanges = new List<TimeRange>();
timeRanges.Add(new TimeRange(Timespan.FromHours(5), Timespan.FromHours(6)));
timeRanges.Add(new TimeRange(Timespan.FromHours(8), Timespan.FromHours(9)));
List<Interval> interval = new List<Interval>();
interval.Add(new Interval(Timespan.FromHours(1), Timespan.FromHours(7)));
interval.Add(new Interval(Timespan.FromHours(10), Timespan.FromHours(15)));
I want to merge these lists into one so that final result will contain this
Timespan.FromHours(1), Timespan.FromHours(5)
Timespan.FromHours(5), Timespan.FromHours(6)
Timespan.FromHours(6), Timespan.FromHours(7)
Timespan.FromHours(8), Timespan.FromHours(9)
Timespan.FromHours(10), Timespan.FromHours(15)
Another case:
timeRange.Add(new Interval(TimeSpan.FromHours(9), TimeSpan.FromHours(17))
timeRange.Add(new Interval(TimeSpan.FromHours(17), TimeSpan.FromHours(19))
interval.Add(new Interval(TimeSpan.FromHours(0), TimeSpan.FromHours(4))
interval.Add(new Interval(TimeSpan.FromHours(4), TimeSpan.FromHours(5))
interval.Add(new Interval(TimeSpan.FromHours(5), TimeSpan.FromHours(9))
interval.Add(new Interval(TimeSpan.FromHours(9), TimeSpan.FromHours(10))
interval.Add(new Interval(TimeSpan.FromHours(12), TimeSpan.FromHours(13))
Expected Result:
Timespan.FromHours(0), Timespan.FromHours(4)
Timespan.FromHours(4), Timespan.FromHours(5)
Timespan.FromHours(5), Timespan.FromHours(9)
Timespan.FromHours(9), Timespan.FromHours(10)
Timespan.FromHours(10), Timespan.FromHours(12)
Timespan.FromHours(12), Timespan.FromHours(13)
Timespan.FromHours(13), Timespan.FromHours(17)
Timespan.FromHours(17), Timespan.FromHours(19)
After realizing I was thinking about this problem in the wrong way I came to an approach that should work in most scenarios. If you come across some data that this doesn't work on please provide it to me and I'll fix it. Thanks!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
List<TimeRange> timeRanges = new List<TimeRange>();
timeRanges.Add(new TimeRange(TimeSpan.FromHours(2), TimeSpan.FromHours(3)));
timeRanges.Add(new TimeRange(TimeSpan.FromHours(8), TimeSpan.FromHours(9)));
timeRanges.Add(new TimeRange(TimeSpan.FromHours(1), TimeSpan.FromHours(5)));
timeRanges.Add(new TimeRange(TimeSpan.FromHours(3), TimeSpan.FromHours(6)));
List<Interval> intervals = new List<Interval>();
intervals.Add(new Interval(TimeSpan.FromHours(1), TimeSpan.FromHours(7)));
intervals.Add(new Interval(TimeSpan.FromHours(10), TimeSpan.FromHours(15)));
timeRanges.AddRange(intervals.Select(x => new TimeRange(x.start, x.end)));
timeRanges = TimeRange.ResolveOverlaps(timeRanges);
timeRanges.ForEach(x => Console.WriteLine($"{x.start} - {x.end}"));
Console.Read();
}
}
public class TimeRange
{
public TimeSpan start { get; set; }
public TimeSpan end { get; set; }
public TimeRange(TimeSpan st, TimeSpan en)
{
start = st;
end = en;
}
public static List<TimeRange> ResolveOverlaps(List<TimeRange> timeRanges)
{
var times = new List<TimeSpan>();
times.AddRange(timeRanges.Select(x => x.start));
times.AddRange(timeRanges.Select(x => x.end));
times = times.Distinct().OrderBy(x => x.Ticks).ToList();
timeRanges.Clear();
while (times.Count > 1)
{
timeRanges.Add(new TimeRange(times[0], times[1]));
times.RemoveAt(0);
}
return timeRanges;
}
}
public class Interval
{
public TimeSpan start { get; set; }
public TimeSpan end { get; set; }
public Interval(TimeSpan st, TimeSpan en)
{
start = st;
end = en;
}
}
}

SQLCLR custom aggregate with datetime parameter

Hy,
I have post a question about CLR User-Defined Aggregates few month ago on this post.
This works great with int. But now I would like to do the same functions with a datetime parameter.
But I can't get work.
Like this, the code won't work... The problem is on the read function who generate this error on sql server :
System.ArgumentOutOfRangeException: Les graduations doivent être comprises entre DateTime.MinValue.Ticks et DateTime.MaxValue.Ticks.
Nom du paramètre : ticks
System.ArgumentOutOfRangeException:
à System.DateTime..ctor(Int64 ticks)
à sMaxDatetime.Read(BinaryReader reader)
So I tried to convert my sql Datetime into ticks, with this, but it'not working eather. I have a OverflowException on the conversion to datetime.
I have found this post, and it's appear that I can't map my datetime to the BinaryReader...
So I running out of ideas to do my aggregate...
Have you a idea to do this ?
Here is the actual code :
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Diagnostics.Eventing.Reader;
using System.Globalization;
using Microsoft.SqlServer.Server;
using System.Text;
using System.Collections;
using System.IO;
[Serializable]
[SqlUserDefinedAggregate(
Format.UserDefined,
IsInvariantToOrder = true,
IsInvariantToNulls = true,
IsInvariantToDuplicates = true,
MaxByteSize = -1)]
public struct sMaxDatetime : IBinarySerialize
{
#region Helpers
private struct MyData
{
public string Data { get; set; }
public DateTime? Group { get; set; }
public int CompareTo(MyData other)
{
if (Group == null)
return other.Group == null ? 0 : -1;
if (other.Group == null)
return 1;
return Group.Value.CompareTo(other.Group.Value);
}
public static bool operator < (MyData left, MyData right)
{
return left.CompareTo(right) == -1;
}
public static bool operator > (MyData left, MyData right)
{
return left.CompareTo(right) == 1;
}
}
#endregion
private MyData _maxItem;
public void Init()
{
_maxItem = default(MyData);
}
public void Accumulate(SqlString data, SqlDateTime group)
{
if (!data.IsNull && !group.IsNull)
{
var current = new MyData
{
Data = data.Value,
Group = group.Value,
};
if (current > _maxItem)
{
_maxItem = current;
}
}
}
public void Merge(sMaxDatetime other)
{
if (other._maxItem > _maxItem)
{
_maxItem = other._maxItem;
}
}
public SqlString Terminate()
{
return _maxItem.Data;
}
public void Read(BinaryReader reader)
{
//if (reader.ReadBoolean())
//{
_maxItem.Data = reader.ReadString();
_maxItem.Group = new DateTime(reader.ReadInt64());
//}
//else
//{
// _maxItem = default(MyData);
//}
}
public void Write(BinaryWriter writer)
{
if (_maxItem.Group.HasValue)
{
writer.Write(true);
writer.Write(_maxItem.Group.Value.ToString());
writer.Write(_maxItem.Data);
}
else
{
writer.Write(false);
}
}
}
PS : I have this related post with sql_variant unclosed who could do the tricks but I can't get to work eather.
I think you're working too hard with a custom struct to hold the datetime and name. Here's what I came up with:
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.IO;
[Serializable]
[SqlUserDefinedAggregate(Format.UserDefined, Name = "sMaxDatetime", MaxByteSize = -1)]
public struct SO33215409 : IBinarySerialize
{
private SqlString _data;
private SqlDateTime _latest;
public void Init()
{
_data = SqlString.Null;
_latest = SqlDateTime.MinValue;
}
public void Accumulate(SqlString data, SqlDateTime dt)
{
if (dt > _latest)
{
_data = data;
_latest = dt;
}
}
public void Merge (SO33215409 Group)
{
if (Group._latest > _latest)
{
_data = Group._data;
_latest = Group._latest;
}
}
public SqlString Terminate ()
{
return _data;
}
public void Write (BinaryWriter w)
{
w.Write(_data.IsNull);
w.Write(_latest.IsNull);
if (_data.IsNull == false)
{
w.Write(_data.Value);
}
if (_latest.IsNull == false)
{
w.Write(_latest.Value.Ticks);
}
}
public void Read(BinaryReader r)
{
bool dataIsNull = r.ReadBoolean();
bool latestIsNull = r.ReadBoolean();
if (dataIsNull)
{
_data = SqlString.Null;
}
else
{
_data = r.ReadString();
}
if (latestIsNull)
{
_latest = SqlDateTime.Null;
}
else
{
DateTime d = new DateTime(r.ReadInt64());
_latest = new SqlDateTime( d );
}
}
}
And the SQL to exercise it:
WITH cte AS (
SELECT * FROM (VALUES
('Manager' , 'emp 1' , dateadd(year, -35, getdate())),
('Manager' , 'emp 2' , dateadd(year, -42, getdate())),
('Developer' , 'emp 3' , dateadd(year, -36, getdate())),
('Developer' , 'emp 4' , dateadd(year, -45, getdate())),
('Developer' , 'emp 5' , dateadd(year, -22, getdate()))
) AS x([Type], [Name], [DOB])
)
SELECT [Type], dbo.[sMaxDatetime]([Name], [DOB])
FROM cte
GROUP BY [Type]

the binary expression cannot be converted to a predicate expression in LINQ

I want to get the week number of a certain given DateTime.
public static int WeekOf(DateTime? date)
{
if (date.HasValue)
{
GregorianCalendar gCalendar = new GregorianCalendar();
int WeekNumber = gCalendar.GetWeekOfYear(date.Value, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
return WeekNumber;
}
else
return 0;
}
And then I use the above method in:
public static List<ExpressionListDictionary> MyMethod(int weeknr)
{
using (DataAccessAdapter adapter = CreateAdapter())
{
LinqMetaData meta = new LinqMetaData(adapter);
var q = (from i in meta.Test
where WeekOf(i.StartDate) == weeknr
select new ExpressionListDictionary()
{
{"SomeId", i.Id}
}
);
return q.ToList();
}
}
And finally:
List<ExpressionListDictionary> someIDs = MyMethod(weeknr);
/* weeknr = 19 -> step by step debugging */
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: SD.LLBLGen.Pro.ORMSupportClasses.ORMQueryConstructionException: The binary expression '(WeekOf(Convert(EntityField(LPLA_1.StartDate AS StartDate))) == 19)' can't be converted to a predicate expression.
I do get the title error at return q.ToList(); . How can I achieve this?
I haven't ever used the LLBLGen library/framework... But probably this is the same problem that happens with Entity Framework/LINQ-to-SQL: you can't put in a query C# methods: the query must be executed by your db server, not locally, and your db server doesn't know how to execute C# code. So the problem would be in the
**WeekOf(i.StartDate)** == weeknr part of code (that is the only BinaryExpression of your query)
The exception you have posted is quite clear that the point of the error is the one that I have suggested. Then the reason is probably the one I gave you.
Taken from https://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=22861
If you are using SQL Server, that supports DATEPART(isowk, ...) (or if you have MySQL, that supports WEEK(...))
public class SQLFunctionMappings : FunctionMappingStore
{
public SQLFunctionMappings()
{
Add(new FunctionMapping(
typeof(SQLFunctions),
"WeekOf",
1,
"DATEPART(isowk, {0})") // For SQL Server
// "WEEK({0}, 1)") For MySQL
);
}
}
public class SQLFunctions
{
public static int? WeekOf(DateTime? date)
{
return null;
}
}
and then you would use it like:
Where there is the row with LinqMetaData meta = new LinqMetaData(adapter), add:
meta.CustomFunctionMappings = new SQLFunctionMappings();
and change the where:
where SQLFunctions.WeekOf(i.StartDate) == weeknr
Here there is the list of the functions already mapped by llblgen, and how to map other functions.
you can try to Make the method you have WeekOf takes string instead of Datetime?
public static int WeekOf(String dateAsString)
{
//if (!string.IsNullOrEmpty(dateAsString))
if (!dateAsString.equals(string.empty))
{
GregorianCalendar gCalendar = new GregorianCalendar();
int WeekNumber = gCalendar.GetWeekOfYear(date.Value, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
return WeekNumber;
}
else
return 0;
}
And then you use the above below in:
public static List<ExpressionListDictionary> MyMethod(int weeknr)
{
using (DataAccessAdapter adapter = CreateAdapter())
{
LinqMetaData meta = new LinqMetaData(adapter);
var q = (from i in meta.Test
where i.startDate != null && WeekOf(i.StartDate.tostring()) == weeknr
select new ExpressionListDictionary()
{
{"SomeId", i.Id}
}
);
return q.ToList();
}
}
Try something like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MyMethod(5);
}
public static int WeekOf(DateTime? date)
{
if (date.HasValue)
{
GregorianCalendar gCalendar = new GregorianCalendar();
int WeekNumber = gCalendar.GetWeekOfYear(date.Value, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
return WeekNumber;
}
else
return 0;
}
public static List<ExpressionListDictionary> MyMethod(int weeknr)
{
using (DataAccessAdapter adapter = CreateAdapter())
{
LinqMetaData meta = new LinqMetaData(adapter);
List<ExpressionListDictionary> q = (from i in meta.Test
where WeekOf(i.StartDate) == weeknr
select new ExpressionListDictionary()
{
Id = "SomeId"
}
).ToList();
return q;
}
}
public static DataAccessAdapter CreateAdapter()
{
return new DataAccessAdapter();
}
}
public class ExpressionListDictionary
{
public string Id { get; set; }
}
public class LinqMetaData
{
public List<LinqMetaData> Test {get;set;}
public DateTime StartDate {get;set;}
public int Id { get; set; }
public LinqMetaData(DataAccessAdapter adapter)
{
}
}
public class DataAccessAdapter : IDisposable
{
public void Dispose()
{
}
}
}
​

Categories

Resources