I have a an object that includes a date range
public class MyObject
{
public int Id {get; set;}
public string Name {get; set;}
public DateTime StartDate {get; set;}
public DateTime EndDate {get; set;}
}
I want to be able to pass in an instance of MyObject and check to make sure all other objects that have intersecting dates, such as
var myObj = new MyObject
{
Id = 1,
Name = "Test",
StartDate = new Date(2017, 3,1),
EndDate = new Date(2017, 3,10)
};
foreach(var objInList in objList)
{
bool done = false;
//if both dates are inside rnage
if (objInList.StartDate <= myObj.StartDate && myObj.StartDate <= objInList.EndDate
&& objInList.StartDate<= myObj.EndDate && myObj.EndDate<= objInList.EndDate)
{// update start and end
done = true;
}
// if start of new item is in range, but end isnt
else if (objInList.StartDate<= myObj.StartDate && myObj.StartDate <= objInList.EndDate)
{
if (!done)
{
done = true;
}
}
// if both dates are inside range
else if (objInList.StartDate<= myObj.EndDate && myObj.EndDate <= objInList.EndDate)
{
if (!done)
{ }
}
}
Basically I need to update objInList to ensure that there are no date intersects after the loop. I am having a hard time putting the logic in place without it getting overly complicated.
The code for checking date range intersects is as follows:
MyObject a = ...
MyObject b = ...
DateTime maxStart = a.StartDate > b.StartDate ? a.StartDate : b.StartDate;
DateTime minEnd = a.EndDate < b.EndDate ? a.EndDate : b.EndDate;
bool intersect = (maxStart < minEnd);
I hope i helps you:
private bool IsTimeBetween(DateTime theTime, DateTime startTime, DateTime endTime)
{
if (theTime >= startTime && theTime <= endTime)
return true;
else
return false;
}
private void check()
{
MyObject myObj = new MyObject
{
Id = 1,
Name = "Test",
StartDate = new DateTime(2017, 3, 1),
EndDate = new DateTime(2017, 3, 10)
};
List<MyObject> objList = new List<MyObject>();
foreach (var objInList in objList)
{
if (objInList.StartDate >= objInList.EndDate)
continue;
if (IsTimeBetween(objInList.StartDate, myObj.StartDate, myObj.EndDate)
|| IsTimeBetween(objInList.EndDate, myObj.StartDate, myObj.EndDate))
{
//intersects
}
if (objInList.StartDate < myObj.StartDate && objInList.EndDate > myObj.EndDate)
{
//intersects
}
}
}
Related
The task is pretty easy and I have an iterative solution but I am thinking that there is probably a more efficient of cleaner solution. I have a list of objects which contain a year and a month property. I want to make sure every month from a given start year + month to a given end year + month is covered.
This is my current solution:
for (int year = startYear; year <= endYear; year++)
{
int startM = year == startYear ? startMonth : 1;
int endM = year == endYear ? endMonth : 12;
for (int month = startM; month <= endM; month++)
{
if (!someList.Any(x => x.Year == year && x.Month == month))
throw new Exception("List contains gaps.");
}
}
The extension methods for DateTime below create a time series that can be used to solve your problem by joining with Linq - works for daily gaps as well.
This is a more extensible solution, not necessarily more efficient given it uses Linq vs interation.
Usage
void Main()
{
var startDate = new DateTime(2014, 1, 1);
var months = 36;
//sample w/ 3 month gaps
var monthsWithGaps = Enumerable.Range(0, months).Where(i=> i % 3 == 0)
.Select(h=> startDate.AddMonths(h))
.Dump();
//usage
startDate.GetTimeSlices(monthsWithGaps.Last(), true)
.Where(d=> d.DayOfMonth == 1)
.GroupJoin(monthsWithGaps, slice => slice.Date, set => set, (slice, set) =>
new {
slice.Date,
set
})
.Where(result => !result.set.Any()) //identify gaps
.Count(result => !result.set.Any()) //count gaps
.Dump();
}
Extension Method Implementation
public static class DateTimeExtensions
{
public static List<TimeSlice> GetTimeSlices(this DateTime date, int numberOfDays)
{
int count = 1;
return Enumerable.Range(0, numberOfDays).Select(x => date.AddDays(x)).Select(x => new TimeSlice
{
Date = x.Date,
Year = x.Year,
MonthOfYear = x.Month,
MonthOfSet = ((count - 1) / 30) + 1,
WeekOfSet = ((count - 1) / 7) + 1,
DayOfMonth = x.Day,
DayOfSet = count++
}).ToList();
}
public static List<TimeSlice> GetTimeSlices(this DateTime date, DateTime endDate, bool includeEndDate = true)
{
return GetTimeSlices(date, (endDate - date).Days + (includeEndDate ? 1 : 0));
}
public class TimeSlice
{
public DateTime Date { get; set; }
public int Year { get; set; }
public int MonthOfYear { get; set; }
public int MonthOfSet { get; set; }
public int WeekOfSet { get; set; }
public int DayOfMonth { get; set; }
public int DayOfSet { get; set; }
}
}
hello I want to see if there is the chance to make a linq expression to help me with performance:
I get a list List<ContentPage> contentPages :
public class ContentPage : IDateSortableContentItem
{
[DataMember]
public ContentPageType PageType { get; set; }
[DataMember]
public Country Country { get; set; }
[DataMember]
public string CultureID { get; set; }
[DataMember]
public string PageName { get; set; }}
Now this is what I do but takes too long:
private List<ContentPageViewModel> ConvertContentPagesToContentPageViewModels(List<ContentPage> contentPages)
{
List<ContentPageViewModel> contentPageViewModels = new List<ContentPageViewModel>();
foreach (var contentPage in contentPages)
{
// Check that the list to return does not have the value to be currently used
if (!contentPageViewModels.Any(p => p.PageName.ToLower() == contentPage.PageName.ToLower()))
{
// Grab all the objects with the same name
var contentForms = contentPages.Where(p => p.PageName.ToLower() == contentPage.PageName.ToLower()).ToList();
List<CultureContentPageData> cultureContentPageDatas = new List<CultureContentPageData>();
// Iterate through the pages with the same name but different cultures
foreach (var contentForm in contentForms)
{
string pageStatus = string.Empty;
if (contentForm.BeginTime < DateTime.UtcNow && contentForm.EndTime > DateTime.UtcNow) {
pageStatus = "Active";
} else if (contentForm.BeginTime > DateTime.UtcNow) {
pageStatus = "Future";
} else if (contentForm.EndTime < DateTime.UtcNow) {
pageStatus = "Inactive";
} else {
pageStatus = "Unknown";
}
cultureContentPageDatas.Add(
new CultureContentPageData()
{
Culture = contentForm.CultureID.Trim(),
MinBeginDate = contentForm.BeginTime.ToString(),
MaxEndDate = contentForm.EndTime.ToString(),
ActiveStatus = pageStatus
});
}
// Add to main list
contentPageViewModels.Add(new ContentPageViewModel()
{
PageName = contentPage.PageName,
PageType = contentPage.PageType,
PreviewUrl = GetPreviewUrl(contentPage.PageName, contentPage.PageType),
CultureContentPages = cultureContentPageDatas.OrderBy(c => _culturesByCountry.IndexOf(c.Culture)).ToList()
});
}
}
return contentPageViewModels;
}
eventually the data in the front end looks like this:
but the closer I have gotten to make it into a ContentPageViewModel list is this linq expression
var groupContentPages = contentPages.GroupBy(p => p.PageName.ToLower()).Select(grp => grp.ToList()).ToList();
and it brings this List within a List like this but not close enough:
Any pointer or help with be greatly appreciated I have worked this for weeks now, thank you
Firstly, in general LINQ to Objects is not going to be faster than foreach or even nested foreach, though your logic could be improved to using GroupBy to not exponentially collect the contentForms. Second, I am not sure in this case LINQ is an improvement in readability or understandability over what you had. Thirdly, your logic has a bit of a hole in the ActiveStatus computation - if UtcNow happens to equal BeginTime, you return Unknown - shouldn't that be Active?
In any case, here is the LINQ to objects version of your method:
private List<ContentPageViewModel> ConvertContentPagesToContentPageViewModels(List<ContentPage> contentPages) {
var UtcNow = DateTime.UtcNow;
var contentPageViewModels =
(from p in contentPages
group p by p.PageName.ToLowerInvariant() into contentForms
let contentPage = contentForms.First()
select new ContentPageViewModel() {
PageName = contentForms.First().PageName,
PageType = contentForms.First().PageType,
PreviewUrl = GetPreviewUrl(contentForms.First().PageName, contentForms.First().PageType),
CultureContentPages =
contentForms.Select(contentForm => new CultureContentPageData() {
Culture = contentForm.CultureID.Trim(),
MinBeginDate = contentForm.BeginTime.ToString(),
MaxEndDate = contentForm.EndTime.ToString(),
ActiveStatus = (contentForm.BeginTime < UtcNow && UtcNow <= contentForm.EndTime)
? "Active"
: (contentForm.BeginTime > UtcNow)
? "Future"
: (UtcNow >= contentForm.EndTime)
? "Inactive"
: "Unknown"
})
.OrderBy(c => _culturesByCountry.IndexOf(c.Culture))
.ToList()
})
.ToList();
return contentPageViewModels;
}
And here is my recommended version:
private List<ContentPageViewModel> ConvertContentPagesToContentPageViewModels(List<ContentPage> contentPages) {
var UtcNow = DateTime.UtcNow; // make the answer consistent
var contentPageViewModels = new List<ContentPageViewModel>();
foreach (var contentForms in contentPages.OrderBy(p => _culturesByCountry.IndexOf(p.CultureID.Trim())).GroupBy(p => p.PageName.ToLowerInvariant())) {
var cultureContentPageDatas = new List<CultureContentPageData>(contentForms.Count());
// Iterate through the pages with the same name but different cultures
foreach (var contentForm in contentForms) {
string pageStatus;
if (contentForm.BeginTime < UtcNow && UtcNow <= contentForm.EndTime)
pageStatus = "Active";
else if (contentForm.BeginTime > UtcNow)
pageStatus = "Future";
else if (UtcNow >= contentForm.EndTime)
pageStatus = "Inactive";
else
pageStatus = "Unknown";
cultureContentPageDatas.Add(
new CultureContentPageData() {
Culture = contentForm.CultureID.Trim(),
MinBeginDate = contentForm.BeginTime.ToString(),
MaxEndDate = contentForm.EndTime.ToString(),
ActiveStatus = pageStatus
});
}
// Add to main list
var contentPage = contentForms.First();
contentPageViewModels.Add(new ContentPageViewModel() {
PageName = contentPage.PageName,
PageType = contentPage.PageType,
PreviewUrl = GetPreviewUrl(contentPage.PageName, contentPage.PageType),
CultureContentPages = cultureContentPageDatas
});
}
return contentPageViewModels;
}
I have a class which contains date information about period, Start and End:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
then I have a List of objects A where I declared a few periods:
List<A> listOfA = new List<A>()
{
new A {Id=1,Name="1", Start = new DateTime (2020,1,1), End = new DateTime(2020,1,20) },
new A {Id=2,Name="2", Start = new DateTime (2020,1,21), End = new DateTime(2020,2,20) },
new A {Id=3,Name="3", Start = new DateTime (2020,5,11), End = new DateTime(2020,5,14) },
new A {Id=4,Name="4", Start = new DateTime (2020,5,15), End = new DateTime(2020,5,20) }
};
I want to find relation (overlapping, containing etc.) betwen given periods and periods in list:
var wrong = new A { Id = 5, Name = "5", Start = new DateTime(2020, 1, 3), End = new DateTime(2020, 4, 20) };
var ok = new A { Id = 6, Name = "6", Start = new DateTime(2020, 4, 3), End = new DateTime(2020, 4, 14) };
In above example wrong object have Start date inside one of the object in list and ok object have no relation. How to find that relation using LINQ?
It's quadratic time complexity and totally untested, however it looks good and that's what counts
var results = list.Where(x =>
list.Any(y =>
x != y &&
(x.Start >= y.Start && x.Start <= y.End ||
x.End <= y.End && x.End >= y.Start)))
.ToList();
Or
Given
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public bool Intersect(A a)
=> this != a && (Start >= a.Start && a.Start <= a.End || End <= a.End && End >= a.Start);
}
Usage
var wrong = list.Where(x => list.Any(x.Intersect)).ToList();
var good = list.Except(wrong).ToList();
You could try :
var wrong2 = listOfA.Any(i => wrong.End > i.Start && wrong.Start < i.End);
var ok2 = listOfA.Any(i => ok.End > i.Start && ok.Start < i.End);
"wrong2" will be true (overlap).
"ok2" will be false (no overlap).
Edit : You should maybe consider to create a "Period" object that would have "StartDate" and "EndDate" and a "IsOverlapping" method. Result would be the same but more readable :-) (See : ValueObject).
I have a dictionary that looks like this:
Dictionary<string, DateTime> Seasons = new Dictionary<string, DateTime>
{
{ "WINTER_START", Date1},
{ "WINTER_END", Date2 },
{ "SUMMER_START", Date3 },
{ "SUMMER_END", Date4 }
};
Let's say I have today's date var today = DateTime.Today.Date and I want to know if today's date falls in winter or summer. I've done this using an If/Else statement, but something inside me tells me there is a better way..
string currentSeason = "";
if (today >= Seasons["WINTER_START"] && today <= Seasons["WINTER_END"])
{
currentSeason = "WINTER";
}
else if (today >= Seasons["SUMMER_START"] && today <= Seasons["SUMMER_END"])
{
currentSeason = "SUMMER";
}
Not sure if you are locked down to using a Dictionary for this, but i think this is a friendlier version and how i would've tackled the problem:
void Main()
{
var seasons = new List<Season>
{
new Season("Winter", Date1, Date2),
new Season("Summer", Date3, Date4)
};
var today = DateTime.Today;
// null if no matching season was found
string currentSeason = seasons.FirstOrDefault(season => season.InSeason(today))?.Name;
}
public class Season
{
public Season(string name, DateTime start, DateTime end)
{
Name = name;
Start = start;
End = end;
}
public string Name { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public bool InSeason(DateTime input)
{
return input >= Start && input <= End;
}
}
Also a minor remark: Current_Season is not a good variable name for a local variable. This might help you improve your naming.
I have a XML like this:
<PrayerTime
Day ="1"
Month="5"
Fajr="07:00"
Sunrise="09:00"
Zuhr="14:00"
/>
A class like this:
public class PrayerTime
{
public string Fajr { get; set; }
public string Sunrise { get; set; }
public string Zuhr { get; set; }
}
And something to get the value like this:
XDocument loadedCustomData = XDocument.Load("WimPrayerTime.xml");
var filteredData = from c in loadedCustomData.Descendants("PrayerTime")
where c.Attribute("Day").Value == myDay.Day.ToString()
&& c.Attribute("Moth").Value == myDay.Month.ToString()
select new PrayerTime()
{
Fajr = c.Attribute("Fajr").Value,
Sunrise = c.Attribute("Sunrise").Value,
};
myTextBox.Text = filteredData.First().Fajr;
How can i based by current time of day say that if time is between the value of Fajr and the Value of Sunrise, then myTextBox should show the value of Fajr.
If value of current time is between sunrise and Zuhr, show Zuhr?
How can i get it to show the attribute name in myTextBox2?
For example, myTextBox shows value "07:00", and myTextBox2 shows "Fajr"?
First modify the class as per #abatischcev
public class PrayerTime
{
public TimeSpan Fajr { get; set; }
public TimeSpan Sunrise { get; set; }
public TimeSpan Zuhr { get; set; }
}
Then modify the linq query select part as:
select new PrayerTime()
{
Fajr = TimeSpan.Parse(c.Attribute("Fajr").Value),
Sunrise = TimeSpan.Parse(c.Attribute("Sunrise").Value),
Zuhr = TimeSpan.Parse(c.Attribute("Zuhr").Value)
};
then your check should be:
var obj = filteredData.First();
TimeSpan currentTime = myDay.TimeOfDay;
string result = String.Empty;
if (currentTime >= obj.Fajr && currentTime < obj.Sunrise)
{
result = "Fajar";
}
else if (currentTime >= obj.Sunrise && currentTime < obj.Zuhr)
{
result = "Zuhar";
}
textbox1.Text = result;
(By the way, Zuhr time should be between Zuhr and Asar :))
First, keep not string but TimeSpan object:
public TimeSpan Fajr { get; set; }
public TimeSpan Sunrise { get; set; }
To do this parse XML into DateTime:
TimeSpan ts = TimeSpan.Parse(c.Attribute("attr"));
So:
TimeSpan now = DateTime.Now.TimeOfDay; // time part only
var data = filteredData.First();
string result = null;
if (data.Fajr <= now && now < data.Sunrise); // notice operators greed
result = data.Fajr;
else if (data.Sunrise <= now && now <= data.Zuhr)
result = data.Zuhr;
myTextBox.Text = result;
The problem here is your code is "stringly typed". I would be better to use type that is design for time e.g. DateTime, but to quick fix it:
// don't you need a third one here?
select new PrayerTime()
{
Fajr = c.Attribute("Fajr").Value,
Sunrise = c.Attribute("Sunrise").Value,
};
Tu get current hour:
int currentHour = DateTime.Now.Hour;
Then is just simple comparison of two integers.
var data = filteredData.First();
int fajrHour = int.Parse(data.Fajr.Substring(0, 2), CultureInfo.InvariantCulture);
int sunriseHour = int.Parse(data.Sunrise.Substring(0, 2), CultureInfo.InvariantCulture);
int zuhrHour = int.Parse(data.Zuhr.Substring(0, 2), CultureInfo.InvariantCulture);
if(fajrHour <= currenHour && currenHour < sunriseHour)
{
myTextBox.Text = data.Fajr; // or to show value fajrHour.ToString()
}
if(sunriseHour <= currenHour && currenHour < zuhrHour)
{
myTextBox.Text = data.Zuhr; // zuhrHour.ToString()
}
// don't you need a third one here?