Linq to Entity Birthday Comparison - c#

I have the requirement to create a query using Linq to Entities where the birthday must fall within 2 days ago and the next 30 days.
The following returns nothing:
DateTime twoDaysAgo = DateTime.Now.AddDays(-2);
int twoDaysAgoDay = twoDaysAgo.Day;
int twoDaysAgoMonth = twoDaysAgo.Month;
DateTime MonthAway = DateTime.Now.AddDays(30);
int monthAwayDay = MonthAway.Day;
int monthAwayMonth = MonthAway.Month;
var bdays = from p in db.Staffs where EntityFunctions.TruncateTime(p.BirthDate) > EntityFunctions.TruncateTime(twoDaysAgo) &&
EntityFunctions.TruncateTime(p.BirthDate) < EntityFunctions.TruncateTime(MonthAway)
orderby p.BirthDate select p;
return bdays;
The problem I'm having is that I need something where if the birthday falls from 11/3 to 12/5, it should return it. The reason it fails because the birthdays include the Year. However, when I use something like:
p.BirthDate.Value.Month
I receive the error that this isn't support with Linq to Entities. Any assistance would be appreciated.

Year-wrapping independent solution:
void Main()
{
var birthdays = new List<DateTime>();
birthdays.Add(new DateTime(2013, 11, 08));
birthdays.Add(new DateTime(2012, 05, 05));
birthdays.Add(new DateTime(2014, 05, 05));
birthdays.Add(new DateTime(2005, 11, 08));
birthdays.Add(new DateTime(2004, 12, 31));
foreach(var date in birthdays.Where(x => x.IsWithinRange(twoDaysAgo, MonthAway))){
Console.WriteLine(date);
}
}
public static class Extensions {
public static bool IsWithinRange(this DateTime #this, DateTime lower, DateTime upper){
if(lower.DayOfYear > upper.DayOfYear){
return (#this.DayOfYear > lower.DayOfYear || #this.DayOfYear < upper.DayOfYear);
}
return (#this.DayOfYear > lower.DayOfYear && #this.DayOfYear < upper.DayOfYear);
}
}
Output with
DateTime twoDaysAgo = DateTime.Now.AddDays(-2);
DateTime MonthAway = DateTime.Now.AddDays(30);
8/11/2013 0:00:00
8/11/2005 0:00:00
Output with
DateTime twoDaysAgo = new DateTime(2012, 12, 25);
DateTime MonthAway = new DateTime(2013, 01, 05);
31/12/2004 0:00:00

If you want to ignore the value of the year, what about using DayOfYear function ?
var bdays = from p in db.Staffs
where EntityFunctions.DayOfYear(p.BirthDate) > EntityFunctions.DayOfYear(twoDaysAgo) &&
EntityFunctions.DayOfYear(p.BirthDate) < EntityFunctions.DayOfYear(MonthAway)
orderby p.BirthDate select p;

You can change all the years to now since year is irrelevant and then you can check it this way
DateTime twoDaysAgo = DateTime.Today.AddDays(-2);
DateTime monthAway = DateTime.Today.AddMonths(1);
List<DateTime> checkDates = new List<DateTime>
{ new DateTime(2011, 11, 3), new DateTime(2011, 12, 5), new DateTime(2011, 12, 6), new DateTime(2011, 11, 2) };
checkDates = checkDates.Select(x => new DateTime(DateTime.Today.Year, x.Month, x.Day)).ToList();
var bdays = from p in checkDates
where (p >= twoDaysAgo && p <= monthAway) ||
(p>= twoDaysAgo.AddYears(-1) && p <= monthAway.AddYears(-1))
orderby p
select p;
This results in
11/3/2013 12:00:00 AM
12/5/2013 12:00:00 AM
This also works with the following list of dates when today is new DateTime(2013, 12, 31)
List<DateTime> checkDates = new List<DateTime>
{ new DateTime(2011, 12, 29), new DateTime(2011, 12, 28), new DateTime(2011, 1, 30), new DateTime(2011, 2, 2) };
Giving the results
1/30/2013 12:00:00 AM
12/29/2013 12:00:00 AM

How about if you add the the nr. of years from the birthdate to today?
Something like:
(untested)
var now = DateTime.Now;
var twoDaysAgo = now.AddDays(-2);
var monthAway = now.Now.AddDays(30)
var bdays =
from p in db.Staffs
let bDay = EntityFunctions.AddYears(p.BirthDate,
EntityFunctions.DiffYears(now, p.BirthDate))
where
bDay > twoDaysAgo &&
bDay < monthAway
orderby p.BirthDate
select p;

Related

Do not include 2nd date range in 1st date range

DateTime startDate1 = (DateTime)StartDate;
DateTime endDate1 = (DateTime)EndDate;
DayOfWeek? day = db.DayOfWeek;
var dayCount = dates.Count(x => x.DayOfWeek == day);
List<DateTime> dates =
Enumerable.Range(0, (int)((EndDate - StartDate).TotalDays) + 1)
.Select(n => StartDate.AddDays(n))
.ToList();
DateTime startDate1 = new DateTime(2018, 11, 01);
DateTime endDate1 = new DateTime(2018, 11, 30);
DateTime startDate2 = new DateTime(2018, 11, 25);
DateTime endDate2 = new DateTime(2018, 11, 30);
if ((startDate2 >= startDate1 && startDate2 <= endDate1) ||
(endDate2 >= startDate1 && endDate2 <= endDate1))
I want to count the number DayOfWeek inside of the range startDate1 and endDate1 and exclude startDate2 and endDate2 from that range completely.
In the example ranges above, my current code return '4' Mondays when I want it to return '3'.
Let's say the endDate1 is now (2018, 12, 31) and the rest of the dates are the same, it should return a count of '8' for Monday instead of the '9'.
Basically, I want to exclude the (2) range from the count regardless if it is within/overlaps the (1) range. How would I go about this?

Week difference between 2 dates in C#

I'm trying to make a function in C# that returns the week difference between two dates. Its goal is to provide the same result of:
select datediff(ww,'2018-04-13','2018-04-16') as diff
In the example above there is only 3 days between these dates, but they are in different weeks, so the result should be 1.
I've tried to use .TotalDays but it's not working properly. I also tried .GetWeekOfYear but it won't return correctly when the year of the dates are different. I've seem many questions here on StackOverflow and on other forums and so far none of them match my case. This is the function I'm trying to far:
public static int GetWeekDiff(DateTime dtStart, DateTime dtEnd) {
// Doesn't work
var val = ((dtEnd - dtStart).TotalDays / 7);
val = Math.Ceiling(val);
return Convert.ToInt32(val);
// Doesn't work well between years
DateTimeFormatInfo dinfo = DateTimeFormatInfo.CurrentInfo;
var x = dinfo.Calendar.GetWeekOfYear(dtStart, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday);
var y = dinfo.Calendar.GetWeekOfYear(dtEnd, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday);
return y - x;
}
In the first part of my function, I tried what is described in this post. It didn't work
Can you help me?
Thanks in advance.
First figure how many days there are between the two dates. Divide the number of days by 7 to get full weeks.
Now figure out if there's an extra week to be counted by finding taking the number of days modulus 7 to get any remaining days. If the first date plus remaining days falls in a different week, add an extra week on to the count.
void Main()
{
var first = new DateTime(2018, 04, 13);
var second = new DateTime(2018, 04, 16);
Console.WriteLine(weekDiff(first, second));
}
public int weekDiff(DateTime d1, DateTime d2, DayOfWeek startOfWeek = DayOfWeek.Monday)
{
var diff = d2.Subtract(d1);
var weeks = (int)diff.Days / 7;
// need to check if there's an extra week to count
var remainingDays = diff.Days % 7;
var cal = CultureInfo.InvariantCulture.Calendar;
var d1WeekNo = cal.GetWeekOfYear(d1, CalendarWeekRule.FirstFullWeek, startOfWeek);
var d1PlusRemainingWeekNo = cal.GetWeekOfYear(d1.AddDays(remainingDays), CalendarWeekRule.FirstFullWeek, startOfWeek);
if (d1WeekNo != d1PlusRemainingWeekNo)
weeks++;
return weeks;
}
static void Main(string[] args)
{
DateTime date1 = new DateTime(2018, 04, 18);
DateTime date2 = new DateTime(2018, 04, 19);
System.Console.WriteLine((GetDiff(new DateTime(2018, 04, 18), new DateTime(2018, 04, 18)))); // 0
System.Console.WriteLine((GetDiff(new DateTime(2018, 04, 22), new DateTime(2018, 04, 23)))); // 1
System.Console.WriteLine((GetDiff(new DateTime(2018, 04, 16), new DateTime(2018, 04, 22)))); // 0
System.Console.WriteLine((GetDiff(new DateTime(2018, 04, 18), new DateTime(2018, 05, 03)))); // 2
}
private static int GetDiff(DateTime date1, DateTime date2)
{
date1 = SetDayToMonday(date1);
date2 = SetDayToMonday(date2);
return (int)((date2 - date1).TotalDays / 7);
}
private static DateTime SetDayToMonday(DateTime date)
{
var weekDay = date.DayOfWeek;
if (weekDay == DayOfWeek.Sunday)
return date.AddDays(-6);
else
return date.AddDays(-((int)weekDay-1));
}
First, set the day to the monday of the current week. Then count all full weeks(= /7 days as int). Easy as it is, it works probably across weeks and years.
See if this works. There could be more use cases that this doesn't cover, and the solution depends on how you define a week boundary (this assumes Sunday-Monday based on a comment above).
// Output:
// Weeks between 12/28/2017 and 1/10/2018: 2
// Weeks between 4/13/2018 and 4/16/2018: 1
// Weeks between 4/21/2018 and 4/22/2018: 0
// Weeks between 4/22/2018 and 4/23/2018: 1
void Main()
{
var datePairs = new List<KeyValuePair<DateTime, DateTime>>();
datePairs.Add(new KeyValuePair<DateTime, DateTime>(new DateTime(2017, 12, 28), new DateTime(2018, 1, 10)));
datePairs.Add(new KeyValuePair<DateTime, DateTime>(new DateTime(2018, 4, 13), new DateTime(2018, 4, 16)));
datePairs.Add(new KeyValuePair<DateTime, DateTime>(new DateTime(2018, 4, 21), new DateTime(2018, 4, 22)));
datePairs.Add(new KeyValuePair<DateTime, DateTime>(new DateTime(2018, 4, 22), new DateTime(2018, 4, 23)));
foreach (var datePair in datePairs)
{
var string1 = datePair.Key.ToShortDateString();
var string2 = datePair.Value.ToShortDateString();
Console.WriteLine($"Weeks between {string1} and {string2}: {GetWeekDiff(datePair.Key, datePair.Value)}");
}
}
public static int GetWeekDiff(DateTime dtStart, DateTime dtEnd)
{
var totalDays = (dtEnd - dtStart).TotalDays;
var weeks = (int)totalDays / 7;
var hasRemainder = totalDays % 7 > 0;
if (hasRemainder)
{
if (!(dtStart.DayOfWeek.Equals(DayOfWeek.Saturday) && dtEnd.DayOfWeek.Equals(DayOfWeek.Sunday)))
{
weeks++;
}
}
return weeks;
}
Maybe it can help
public static int GetIso8601WeekOfYear(DateTime time)
{
// Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll
// be the same week# as whatever Thursday, Friday or Saturday are,
// and we always get those right
DayOfWeek day = CultureInfo.InvariantCulture.Calendar.GetDayOfWeek(time);
if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
{
time = time.AddDays(3);
}
// Return the week of our adjusted day
return CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
}
Get the correct week number of a given date
Can't comment yet and already used a flag on this post on something I believed to be similar. Here is another post I found that appears to align with the solution you are trying to create:
Get the number of calendar weeks between 2 dates in C#
This is my implementation to solve a similar problem, I haven't tested in thoroughly but it seems to work.
var dt1 = DateTime.Today.AddDays(-30);
var dt2 = DateTime.Today;
var noOfDays =(int) (dt2 - dt1).TotalDays;
int reminder;
var weeks = Math.DivRem(noOfDays, 7, out reminder);
weeks = reminder > 0 ? weeks + 1 : weeks;
It returns 1 week for 6 days or less gap, which is exactly what I needed.

Group By Date.Date

I am struggling to do a group by. I have the following list:
Date = 10/03 06:40 AM, Val = 10
Date = 10/03 08:55 PM, Val = 5
Date = 11/03 06:40 AM, Val = 5
Date = 11/03 10:50 AM, Val = 9
Date = 11/03 06:40 PM, Val = 14
And I want this list:
Date = 10/03, Val = 5
Date = 11/03, Val = 14
So a list grouped by Date.Date but with Val depending on Max(d => d.Date).
I did it with a foreach but I am pretty sure that we can do something better using LINQ (groupby,select). Any ideas?
Cheers
I believe you want something like
static T MaxBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
where TKey: IComparable<TKey> =>
source.OrderByDescending(selector).First();
Then
from item in items
group item by item.Date.Date into byDate
select new
{
Date = byDate.Key,
byDate.MaxBy(item => item.Date).Val
}
If you do not wish to create a dedicated method you can also do it inline of course
from item in items
group item by item.Date.Date into byDate
let ordered = from item in byDate
orderby item.Date descending
select item.Val
select new
{
Date = byDate.Key,
Val = ordered.First()
}
Note: Thanks to mjwills who pointed out a bug which I was then able to correct
It should be something like this:
var result = from d in list
group d by d.Date.Date into grouped
let max = grouped.Max(d => d.Date)
select new {
Date = grouped.Key,
list.FirstOrDefault(c => c.Date == max)?.Val
};
Something along the line of:
values
.GroupBy(value => value.Date.Date)
.Select(grouped => new
{
Date = grouped.Key,
Val = values.First(value => value.Date == grouped.Date.Max()).Val
});
Here's what i have end up with :
Example entity :
public class ExampleEntity
{
public DateTime Date { get; set; }
public int Val { get; set; }
}
Example code :
var listOfExampleEntity = new List<ExampleEntity>()
{
new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 3, 40, 00),
Val =2
},
new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 4, 40, 00),
Val =2
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 5, 40, 00),
Val =5
},new ExampleEntity()
{
Date = new DateTime(2018, 10, 03, 3, 40, 00),
Val =2
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 7, 40, 00),
Val =3
},new ExampleEntity()
{
Date = new DateTime(2018, 11, 03, 8, 40, 00),
Val =4
},
};
var temp = listOfExampleEntity.GroupBy(
p => p.Date.Date,
p => p.Val,
(date, val) => new { Date = date, Value = val.Last() });

Lambda between two dates explicit

I am in need of some wizards.
I have a table
Start End PersonID
-----------------------------------------------------
10/07/2017 00:00:00 18/07/2017 00:00:00 1
27/07/2017 00:00:00 27/07/2017 00:00:00 1
28/07/2017 00:00:00 28/07/2017 00:00:00 1
29/07/2017 00:00:00 29/07/2017 00:00:00 1
30/07/2017 00:00:00 30/07/2017 00:00:00 1
If I search for
Date Start = 11/07/2017
Date End = 12/07/2017
Using this query:
DateTime start = new DateTime(2017,07,11,0,0,0,0,0);
DateTime end = start.AddDays(1);
DateTime[] days = new DateTime[end.Subtract(start).Days];
for (int i = 0; i < end.Subtract(start).Days; i++)
{
var d = start.AddDays(i);
days[i] = d;
}
IQueryable block = tmOpen1.Calendar.Where(x => days.All(y => y >= x.start && y <= x.end)).Select(x => new { ID = x.PersonID });`
I get a positive result for ROW 1 (10/07/2017 - 18/07/2017)
However If I apply it against the remaining rows e.g. Filter
Date Start = 28/07/2017
Date End = 29/07/2017
Then obviously this will fail. How Can I get this side of the search to work.
E.g. Either
Take the first row and make it split out into individual rows
Make the Individual rows return true if a Person has several true conditions.
I hope one of the geniuses here can help.
Seems like all you really need is something like this:
DateTime start = new DateTime(2017,07,11,0,0,0,0,0);
DateTime end = start.AddDays(1);
var results = tmOpen1.Calendar
.Where(c => start <= c.end && end >= c.start)
.Select(x => new { ID = x.PersonID });
If your interval starts or ends somewhere between a start and end date from the table, than it means it is overlapping and you should included in your result.
tmOpen1.Calendar.Where(x => (startDate >= x.start && startDate <= x.end) || (endDate >= x.start && endDate <= x.end)).Select(x => new { ID = x.PersonID });
So an interval 10.07 - 27.07 should give you the first 2 rows, right?
Or is the interval supposed to be fully enclosed between 2 dates in the table?
From understanding of your question you want to know when the Date Start or Date End is within a range of dates.
You can check Date Start is within the date range or the Date End is within the date range
Example:
List<DateRange> dates = new List<DateRange>();
dates.Add(new DateRange()
{
StartDate = new DateTime(2017, 07, 10),
EndDate = new DateTime(2017, 07, 18)
});
dates.Add(new DateRange()
{
StartDate = new DateTime(2017, 07, 28),
EndDate = new DateTime(2017, 07, 28)
});
DateRange search1 = new DateRange()
{
StartDate = new DateTime(2017, 07, 11),
EndDate = new DateTime(2017, 07, 12)
};
DateRange search2 = new DateRange()
{
StartDate = new DateTime(2017, 07, 28),
EndDate = new DateTime(2017, 07, 29)
};
var result1 = dates.Where(x => search1.StartDate >= x.StartDate && search1.StartDate <= x.EndDate ||
search1.EndDate <= x.StartDate && search1.EndDate >= x.EndDate);
var result2 = dates.Where(x => search2.StartDate >= x.StartDate && search2.StartDate <= x.EndDate ||
search2.EndDate <= x.StartDate && search2.EndDate >= x.EndDate);
Simplier with the not valid time frame:
DateTime start = new DateTime(2017, 07, 11, 0, 0, 0, 0, 0);
DateTime end = start.AddDays(1);
var results = tmOpen1.Calendar.
.Where( c => ! ( c.Start > end || c.End < start) )
.Select(x => new { ID = x.PersonID } );
For DateTime start = new DateTime(2017, 07, 11, 0, 0, 0, 0, 0);
The result are:
TEST 1: 11/07/2017 00:00:00
Start:10/07/2017 00:00:00 End:18/07/2017 00:00:00 ID:1
For DateTime start = new DateTime(2017, 07, 28, 0, 0, 0, 0, 0);
The result are:
TEST 2: 28/07/2017 00:00:00
Start:28/07/2017 00:00:00 End:28/07/2017 00:00:00 ID:1
Start:29/07/2017 00:00:00 End:29/07/2017 00:00:00 ID:1
modelclassList= modelclassList.Where(x => x.gf_expdate>DateTime.Now).ToList();
to check expiry date and save back list of model class

How to find exact date range from list of date ranges by entering single date

I want to find the date range which falls in input date, following is structure
public class Duration
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
var durations = new List<Duration>();
var duration1 = new Duration()
{
StartDate = new DateTime(2017, 08, 1),
EndDate = new DateTime(2017, 08, 10)
};
durations.Add(duration1);
var duration2 = new Duration()
{
StartDate = new DateTime(2017, 08, 5),
EndDate = new DateTime(2017, 08, 10)
};
durations.Add(duration2);
var duration3 = new Duration()
{
StartDate = new DateTime(2017, 08, 5),
EndDate = new DateTime(2017, 08, 6)
};
durations.Add(duration3);
Now I want to find duration which is closest to the entered date for list of <Durations> with LINQ or for-loop
My expected result for currentDate=new DateTime(2017, 08, 7); is duration2
You first need to check if the currentDate is within the start and end dates of each range. For the ones that meet that condition, you calculate the "closeness" adding both distances. When you find one lapse(gap) smaller tan the previous, you save its index... and voilá
int lapse = Integer.MaxValue;
int counter = 0;
int index = 0;
foreach (d in durations) {
if (((d.StartDate <= currentDate) && (d.EndDate >= currentDate))) {
int newlapse = ((currentDate - d.StartDate).TotalDays + (d.EndDate - currentDate).TotalDays);
if ((newlapse < lapse)) {
lapse = newlapse;
index = counter;
}
}
counter +=1;
}
return durations(index);
If you need the middle of interval to be closest:
durations.OrderBy((d) => Math.Abs(d.EndDate.Ticks + d.StartDate.Ticks) / 2 - currentDate.Ticks).FirstOrDefault();
If you need the start of interval to be closest:
durations.OrderBy((d) => Math.Abs(d.EndDate.Ticks - currentDate.Ticks)).FirstOrDefault();
As D le mentioned above
First check if currentDate is within the start and end dates
Second select the duration with the minimal difference between start end end date
I used a nuget package called morelinq which gives nice extensions methods like MinBy:
var result = (from d in durations
where (d.StartDate <= currentDate && d.EndDate >= currentDate)
select d).MinBy(d => d.EndDate - d.StartDate);

Categories