Array of dates, check if date exists - c#

I have an object Member,
Member
PropertyA
PropertyZ
Membership[] Memberships
and Membership object is
Membership
PropertyA
PropertyZ
string Status
DateTime? StartDate
DateTime? EndDate
If I now have,
var members = GetAllMembers(DateTime.Now);
I want to find out if member has any active membership today,
// iterating through each member here
if(member.Memberships.Any())
{
// check if user has any membership duration that falls into
// today's date and membership status is also "active"
}
Problem is,
I am not sure how to check if any membership duration that falls into today's date and membership status is also "active"

to check that the targetdt(DateTime.Now) is between StartDate & EndDate:
if (member.Memberships.Any(x=>targetDt.Ticks > x.StartDate.Ticks && targetDt.Ticks < x.EndDate.Ticks && x.Status=="Active"))
{
// Has membership on targetDt.
}

I assume that null value for start or end date means 'open' interval
var today = DateTime.Now;
var activeMembers = allMembers.Where(m =>
m.Memberships.Any(ms =>
ms.Status == "Active" &&
(ms.StartDate == null || ms.StartDate <= today) && (ms.EndDate == null || today <= ms.EndDate)));

So I would structure your main classes slightly more different from what you have now. This it to get of having to compare on strings for the status, but since I don't know exactly how the active property is set but I would assume that for a membership to be active the start and (optional) end date have to be smaller than and greater than today's date.
class Member
{
private readonly IList<Membership> memberships;
public Member()
{
memberships = new List<Membership>();
}
public ReadOnlyCollection<Membership> Memberships
{
get
{
return new ReadOnlyCollection<Membership>(memberships);
}
}
}
class Membership
{
private readonly DateTime endDate;
private readonly DateTime startDate;
public Membership(DateTime startDate)
{
this.startDate = startDate;
}
DateTime StartDate
{
get
{
return this.startDate;
}
}
DateTime? EndDate
{
get
{
return endDate;
}
}
public Status Status
{
get
{
return CalculateMemebershipStatus();
}
}
private Status CalculateMemebershipStatus()
{
var todaysDate = DateTime.Today;
if (StartDate > todaysDate)
{
return Status.Deactivated;
}
if (!EndDate.HasValue)
{
return Status.Active;
}
if (todaysDate < EndDate.Value)
{
return Status.Active;
}
return Status.Deactivated;
}
}
enum Status
{
Active,
Deactivated
}
And when you need to find out all the members that have an active membership today you can do:
var memberCollection = new List<Member>();
...
var activeMembers = memberCollection.Where(x => x.Memberships.Any(y=> y.Status == Status.Active));
So this will go through your member collection and select the ones that have an active status today. Although as previously said this depends on how you build the active property but this is more contained in that testing for the activeness of the membership based on the date is done within the membership object. So the consumer of this data doesn't need to know how to say that a membership is active they simply only need to know that some will be active and others will be deactivated.

Related

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")
]

DateTime? AddDays Extension Method

I want to write an extension method that adds one day to a Nullable DateTime, but modifies the date itself.
I want to use it as follows:
someDate.AddOneDay();
This directly changes the value of someDate.
The code I initially wrote was:
public static DateTime? AddOneDay(this DateTime? date)
{
if (date.HasValue)
{
date.Value = date.Value.AddDays(1);
}
return null;
}
but this doesn't work since the reference is changed thus calling it this way
won't change the value of someDate.
Is there a way to achieve this and avoid code like:
someDate = someDate.AddOneDay();
Also I was thinking for some setter of the DateTime properties, but they don't have any..
public int Day { get; }
public int Month { get; }
public int Year { get; }
You can't DateTime is immutable, and should stay that way.
Just do:
someDate = someDate.AddOneDay();
And if you want to be more specific, you could rename your function to:
DateTime? someDate = someDate.AddOneDayOrDefault();
old school %)
public static void AddOneDay(ref DateTime date)
{
if (date != null) date = date.AddDays(1);
}
usage:
DateTime date = DateTime.Now;
AddOneDay(ref date);
UPD
one line version of method:
public static void AddOneDay(ref DateTime date) { date = date.AddDays(1); }
C# does support a similar feature, even for mutable values, which is the use of += on nullable values:
DateTime? date = GetDate();
var oneDay = TimeSpan.FromDays(1);
date += oneDay;

DateTime expired check

I'm trying to build an extension that will check if a specific date is expired (compared to today's date).
Example: DateTime.Now is: 10/26/2013 11:34:59 AM
If specific datetime is: 10/22/2013 11:34:59 AM then return true (expired)
If specific datetime is 10/28/2013 11:34:59 AM then return false (not expired)
Is this a proper way to do it? (to compare time also because I don't need just the date)
public static bool IsExpired(this string specificDate)
{
bool flag = false;
DateTime currentDate = DateTime.Now;
DateTime target;
if (DateTime.tryParse(specificDate, out target)
{
flag = target < currentDate;
}
return flag;
}
Try this:
public static bool IsExpired(this DateTime specificDate)
{
return specificDate < DateTime.Now;
}

LINQ2SQL No supported translation to SQL exists in Entity Framework?

We've been using LINQ2SQL on our mvc project for a while and its getting a bit long in the teeth.
One of the issues that we constantly face is the "No supported translation to SQL exists" when we try and do a query using something like.
LINQ2SQL partial class
public class User
{
public bool IsActive
{
get
{
return (StartDate < DateTime.Now || StartDate == null) && (EndDate > DateTime.Now || EndDate == null);
}
}
public DateTime? StartDate{get;set;}
public DateTime? EndDate{get;set;}
}
and the query is something like
Datacontext.Users.Where(u => u.IsActive)
you get
The member 'User.IsActive' has no supported translation to SQL.
We are considering moving to EF code first and I'm curious if this same problem exists, and if not how is EF working differently to get around it?
If you have a user DTO class (that is not partial with the Linq2sql entity) there should be not problem to project into that class.
public class UserDTO
{
public bool IsActive{get;set;}
public DateTime? StartDate{get;set;}
public DateTime? EndDate{get;set;}
}
public IList<UserDTO> GetActiveUsers()
{
using(var db = new DbContext())
{
var users = GetUsers(db);
return users.Where(u => u.IsActive).ToList();
}
}
private IQuerable<UserDTO> GetUsers(DbContext db)
{
return (from u in db.Users
select new UserDTO()
{
StartDate = u.StartDate,
EndDate = u.EndDate,
IsActive = (u.StartDate < DateTime.Now || u.StartDate == null) && (u.EndDate > DateTime.Now || u.EndDate == null)
});
}

Get a range of dates given an interval and step

Edit
It looks like creating a table that holds the DateTimes by minutes to join against would make the most sense. 100 years worth of minutes is ~52M rows. Indexed by the Ticks should make the query run pretty fast. It now becomes
Thanks for the feedback everyone!
I have a class called Recurrence that looks like this:
public class Recurrence
{
public int Id { get; protected set; }
public DateTime StartDate { get; protected set; }
public DateTime? EndDate { get; protected set; }
public long? RecurrenceInterval { get; protected set; }
}
It is an entity framework POCO class. There are two things I want to do with this class all with standard query operators. (So that the query runs entirely server side).
First I want to create a query that returns all the dates from the start date to the end date inclusive with the given recurrence interval. The iterative function is simple
for(i=StartDate.Ticks; i<=EndDate.Ticks; i+=RecurrenceInterval)
{
yield return new DateTime(i);
}
Enumerable.Range() would be an option but there is no long version of Range. I'm thinking my only option here is Aggregate but I'm still not very strong with that function.
Finally once I have that query working, I want to return the values from there that are within a time window i.e. between a different start and end date. That is easy enough to do using SkipWhile/TakeWhile.
Here's how I could do it if DateTime.Ticks was an int
from recurrence in Recurrences
let range =
Enumerable
.Range(
(int)recurrence.StartDate.Ticks,
recurrence.EndDate.HasValue ? (int)recurrence.EndDate.Value.Ticks : (int)end.Ticks)
.Where(i=>i-(int)recurrence.StartDate.Ticks%(int)recurrence.RecurrenceLength.Value==0)
.SkipWhile(d => d < start.Ticks)
.TakeWhile(d => d <= end.Ticks)
from date in range
select new ScheduledEvent { Date = new DateTime(date) };
I guess what I need is an implementation of LongRange that could execute over an EF Query.
Here's the function that yields the intersection of the Recurrence points and a specified subinterval:
public class Recurrence
{
public int Id { get; protected set; }
public DateTime StartDate { get; protected set; }
public DateTime? EndDate { get; protected set; }
public long? RecurrenceInterval { get; protected set; }
// returns the set of DateTimes within [subStart, subEnd] that are
// of the form StartDate + k*RecurrenceInterval, where k is an Integer
public IEnumerable<DateTime> GetBetween(DateTime subStart, DateTime subEnd)
{
long stride = RecurrenceInterval ?? 1;
if (stride < 1)
throw new ArgumentException("Need a positive recurrence stride");
long realStart, realEnd;
// figure out where we really need to start
if (StartDate >= subStart)
realStart = StartDate.Ticks;
else
{
long rem = subStart.Ticks % stride;
if (rem == 0)
realStart = subStart.Ticks;
else
// break off the incomplete stride and add a full one
realStart = subStart.Ticks - rem + stride;
}
// figure out where we really need to stop
if (EndDate <= subEnd)
// we know EndDate has a value. Null can't be "less than" something
realEnd = EndDate.Value.Ticks;
else
{
long rem = subEnd.Ticks % stride;
// break off any incomplete stride
realEnd = subEnd.Ticks - rem;
}
if (realEnd < realStart)
yield break; // the intersection is empty
// now yield all the results in the intersection of the sets
for (long t = realStart; t <= realEnd; t += stride)
yield return new DateTime(t);
}
}
You could create your own date range method
public static class EnumerableEx
{
public static IEnumerable<DateTime> DateRange(DateTime startDate, DateTime endDate, TimeSpan intervall)
{
for (DateTime d = startDate; d <= endDate; d += intervall) {
yield return d;
}
}
}
Then query with
var query =
from recurrence in Recurrences
from date in EnumerableEx.DateRange(recurrence.StartDate,
recurrence.EndDate ?? end,
recurrence.RecurrenceInterval)
select new ScheduledEvent { Date = date };
This assumes that RecurrenceInterval is declared as TimeSpan and end as DateTime.
EDIT: Would this version restrict the recurrences on the server side as you excpect?
var query =
from recurrence in Recurrences
where
recurrence.StartDate <= end &&
(recurrence.EndDate != null && recurrence.EndDate.Value >= start ||
recurrence.EndDate == null)
from date in EnumerableEx.DateRange(
recurrence.StartDate,
recurrence.EndDate.HasValue && recurrence.EndDate.Value < end ? recurrence.EndDate.Value : end,
recurrence.RecurrenceInterval)
where (date >= start)
select new ScheduledEvent { Date = date };
Here the returned recurrences already take in account the start and the end date, thus not returning obsolete recurrences. EnumerableEx.DateRange has no effect on the first part of the query.

Categories

Resources