I'm trying to think of a cleaner way to do this. I want to move this into a helper method but I do not like using out params. I realize I have to use out params for the TryParse, I don't have a choice but I'd like to get this into some kind of reusable method:
startDate and endDate are in "yyyy/mm/dd" format and are string to begin with and I'm parsing below.
DateTime startDt;
DateTime endDt;
startDt = (DateTime.TryParse(startDate, out startDt) ? startDt : DateTime.Now);
endDt = ((!DateTime.TryParse(endDate, out endDt) || string.IsNullOrEmpty(endDate))) ? (startDt.AddMinutes(Configuration.Instance.RecentOrdersWindowDurationMinutes)) : endDt;
Well, if you want two results from a single method you have to either use out parameters or some wrapping type. If you're lazy and only doing this once, you could just use Tuple<DateTime, DateTime>.
public Tuple<DateTime, DateTime> GetRange(string startDate, string endDate)
{
DateTime startDt;
DateTime endDt;
if (!DateTime.TryParse(startDate, out startDt))
startDate = DateTime.Now;
if (string.IsNullOrEmpty(endDate) || !DateTime.TryParse(endDate, out endDt))
endDt = startDt.AddMinutes(Configuration.Instance.RecentOrdersWindowDurationMinutes);
return new Tuple<DateTime, DateTime>(startDt, endDt);
}
If you're looking for the more generalized case, dealing with just one at a time, you could write a TryParse that accepts a default value.
public DateTime TryParseOrDefault(string str, DateTime def)
{
DateTime ret;
if (DateTime.TryParse(str, out ret))
return ret;
else
return def;
{
Edit:
In re to a comment, your concrete type would just look something like this:
public class DateRange
{
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
Then you could use that instead of the Tuple<DateTime, DateTime>.
You can declare your own TryParse helper method, but instead of returning bool and setting the actual DateTime value using out parameter, you can return DateTime?, which will let you set default value quite nicely using ?? operator:
public static class DateTimeUtils
{
public static DateTime? TryParse(string value)
{
DateTime result;
if (!DateTime.TryParse(value, out result))
return null;
return result;
}
}
var myString = "01/01/2015";
var myDateTime = DateTimeUtils.TryParse(myString) ?? DateTime.Now;
This way you don't have to declare myDateTime before calling TryParse, like you'd have to do with standard DateTime.TryParse method.
In your case it would be something like:
DateTime startDt = DateTimeUtils.TryParse(startDate) ?? DateTime.Now;
DateTime endDt = DateTimeUtils.TryParse(endDate) ?? startDt.AddMinutes(Configuration.Instance.RecentOrdersWindowDurationMinutes);
Related
My project has many objects with date fields, and I often need to select everything where one such field is within a date range.
For example:
public class Contract
{
public DateTime SignDate { get; set; }
public DateTime ReleaseDate { get; set; }
}
public class PersonalCheck
{
public DateTime SignDate { get; set; }
public DateTime ProcessDate { get; set; }
public DateTime VoidDate { get; set; }
}
If I only cared about SignDate, it would be easy. I would declare an Interface...
public interface IObjectWithSignDate
{
DateTime SignDate { get; set; }
}
...change my other objects to inherit from it, then create a method like this:
public static IQueryable<T> SignedWithin<T>(this IQueryable<T> items, DateTime start, DateTime end) where T : IObjectWithSignDate
{
return items.Where(q => q.SignDate >= start && q.SignDate <= end);
}
How can I avoid rewriting this function for ReleaseDate, ProcessDate, VoidDate, etc.? Can I make this method take in an IQueryable of any object and a variable telling it which date field to run this selector against?
Note this would have to be able to a) execute in LinqToEntities to run against a database and b) not add a lot of overhead (as I'm fearful reflection might do)
Simple but specific
You can add an extension method like this:
public static class DateTimeExtensions
{
public static bool IsBetween(this DateTime thisDateTime, DateTime start, DateTime end)
{
return thisDateTime >= start && thisDateTime <= end;
}
}
which you can unit test in isolation.
Then you can use this on whichever DateTime field you want to check. For example:
var start = new DateTime(2017, 1, 1);
var end = new DateTime(2017, 12, 31, 23, 59, 59);
IList<Contract> contracts = new List<Contract>(); // or anything enumerable
var contractsSignedBetween = contracts.Where(x => x.SignDate.IsBetween(start, end));
var contractsReleasedBetween = contracts.Where(x => x.ReleaseDate.IsBetween(start, end));
(Notice how I set the start datetime to have 00:00:00 time, and the end datetime to have 23:59:59 time [feel free to include milliseconds as well], so that times within the last day are included.)
Making that reusable
If you find yourself needing to do that a lot, you could do an extension for that
public static class EnumerableContractsExtensions
{
public static IEnumerable<Contract> SignedBetween(this IEnumerable<Contract> contracts, DateTime start, DateTime end)
{
return contracts.Where(x => x.SignDate.IsBetween(start, end));
}
}
and use it like this
var contractsSignedBetween = contracts.SignedBetween(start, end);
which could also be unit tested in isolation.
More flexible but specific
Use an expression to say which date you want...
public static class EnumerableContractsExtensions
{
public static IEnumerable<Contract> Between(this IEnumerable<Contract> contracts, Func<Contract, DateTime> selector, DateTime start, DateTime end)
{
return contracts.Where(x => selector(x).IsBetween(start, end));
}
}
and then do:
var contractsSignedBetween = contracts.Between(x => x.SignDate, start, end);
var contractsReleasedBetween = contracts.Between(x => x.ReleaseDate, start, end);
Flexible and generic
Go the whole hog and do it generically (although you can't make it an extension method since it's generic):
public static class EnumerableExtensions
{
public static IEnumerable<T> Between<T>(IEnumerable<T> items, Func<T, DateTime> selector, DateTime start, DateTime end)
{
return items.Where(x => selector(x).IsBetween(start, end));
}
}
again, this is testable in its own right, and can be used like this:
IList<Contract> contracts = new List<Contract>();
IList<PersonalCheck> personalChecks = new List<PersonalCheck>();
var contractsSignedBetween = EnumerableExtensions.Between(contracts, x => x.SignDate, start, end);
var checksSignedBetween = EnumerableExtensions.Between(personalChecks, x => x.SignDate, start, end);
Making it IQueryable
To make this work as IQueryable the approach needs to shift to an expression tree, since LINQ to Entities does not know how to translate a method into SQL.
public static IQueryable<TSource> Between<TSource, TKey>(
this IQueryable<TSource> source,
Expression<Func<TSource, TKey>> keySelector,
TKey low,
TKey high)
where TKey : IComparable<TKey>
{
Expression key = keySelector.Body;
Expression lowerBound = Expression.LessThanOrEqual(Expression.Constant(low), key);
Expression upperBound = Expression.LessThanOrEqual(key, Expression.Constant(high));
Expression and = Expression.AndAlso(lowerBound, upperBound);
Expression<Func<TSource, bool>> lambda =
Expression.Lambda<Func<TSource, bool>>(and, keySelector.Parameters);
return source.Where(lambda);
}
which would still be used like this:
var contractsSignedBetween = contracts.Between(x => x.SignDate, start, end);
And this works for things other than DateTimes as well. Hope this helps.
This question already has answers here:
Entity Framework DateTime and UTC
(19 answers)
Closed 6 years ago.
I have C# program where all DateTime objects are DateTimeKind.UTC. When saving the objects to the database it stores UTC as expected. However, when retrieving them, they are DateTimeKind.Unspecified. Is there a way to tell Entity Framework (Code First) when creating DateTime objects in C# to always use DateTimeKind.UTC?
No, there's not. And it's actually DateTimeKind.Unspecified.
However, if you are concerned about supporting multiple timezones, you should consider using DateTimeOffset. It's like a regular DateTime, except that it does not represent a "perspective" of time, it represents an absolute view, in which 3PM (UTC - 3) equals 4PM (UTC - 2). DateTimeOffset contains both the DateTime and the time zone and it's supported by both EntityFramework and SQL Server.
You can have your datacontext fix up all the relevant values as it goes. The following does so with a cache of properties for entity types, so as to avoid having to examine the type each time:
public class YourContext : DbContext
{
private static readonly List<PropertyInfo> EmptyPropsList = new List<PropertyInfo>();
private static readonly Hashtable PropsCache = new Hashtable(); // Spec promises safe for single-reader, multiple writer.
// Spec for Dictionary makes no such promise, and while
// it should be okay in this case, play it safe.
private static List<PropertyInfo> GetDateProperties(Type type)
{
List<PropertyInfo> list = new List<PropertyInfo>();
foreach(PropertyInfo prop in type.GetProperties())
{
Type valType = prop.PropertyType;
if(valType == typeof(DateTime) || valType == typeof(DateTime?))
list.Add(prop);
}
if(list.Count == 0)
return EmptyPropsList; // Don't waste memory on lots of empty lists.
list.TrimExcess();
return list;
}
private static void FixDates(object sender, ObjectMaterializedEventArgs evArg)
{
object entity = evArg.Entity;
if(entity != null)
{
Type eType = entity.GetType();
List<PropertyInfo> rules = (List<PropertyInfo>)PropsCache[eType];
if(rules == null)
lock(PropsCache)
PropsCache[eType] = rules = GetPropertyRules(eType); // Don't bother double-checking. Over-write is safe.
foreach(var rule in rules)
{
var info = rule.PropertyInfo;
object curVal = info.GetValue(entity);
if(curVal != null)
info.SetValue(entity, DateTime.SpecifyKind((DateTime)curVal, rule.Kind));
}
}
}
public YourContext()
{
((IObjectContextAdapter)this).ObjectContext.ObjectMaterialized += FixDates;
/* rest of constructor logic here */
}
/* rest of context class here */
}
This can also be combined with attributes so as to allow one to set the DateTimeKind each property should have, by storing a set of rules about each property, rather than just the PropertyInfo, and looking for the attribute in GetDateProperties.
My solution, using code first:
Declare the DateTime properties in this way:
private DateTime _DateTimeProperty;
public DateTime DateTimeProperty
{
get
{
return _DateTimeProperty;
}
set
{
_DateTimeProperty = value.ToKindUtc();
}
}
Also can create the property as:
private DateTime? _DateTimeProperty;
public DateTime? DateTimeProperty
{
get
{
return _DateTimeProperty;
}
set
{
_DateTimeProperty = value.ToKindUtc();
}
}
ToKindUtc() is a extension to change DateTimeKind.Unspecified to DateTimeKind.Utc or call ToUniversalTime() if kind is DateTimeKind.Local
Here the code for the extensions:
public static class DateTimeExtensions
{
public static DateTime ToKindUtc(this DateTime value)
{
return KindUtc(value);
}
public static DateTime? ToKindUtc(this DateTime? value)
{
return KindUtc(value);
}
public static DateTime ToKindLocal(this DateTime value)
{
return KindLocal(value);
}
public static DateTime? ToKindLocal(this DateTime? value)
{
return KindLocal(value);
}
public static DateTime SpecifyKind(this DateTime value, DateTimeKind kind)
{
if (value.Kind != kind)
{
return DateTime.SpecifyKind(value, kind);
}
return value;
}
public static DateTime? SpecifyKind(this DateTime? value, DateTimeKind kind)
{
if (value.HasValue)
{
return DateTime.SpecifyKind(value.Value, kind);
}
return value;
}
public static DateTime KindUtc(DateTime value)
{
if (value.Kind == DateTimeKind.Unspecified)
{
return DateTime.SpecifyKind(value, DateTimeKind.Utc);
}
else if (value.Kind == DateTimeKind.Local)
{
return value.ToUniversalTime();
}
return value;
}
public static DateTime? KindUtc(DateTime? value)
{
if (value.HasValue)
{
return KindUtc(value.Value);
}
return value;
}
public static DateTime KindLocal(DateTime value)
{
if (value.Kind == DateTimeKind.Unspecified)
{
return DateTime.SpecifyKind(value, DateTimeKind.Local);
}
else if (value.Kind == DateTimeKind.Utc)
{
return value.ToLocalTime();
}
return value;
}
public static DateTime? KindLocal(DateTime? value)
{
if (value.HasValue)
{
return KindLocal(value.Value);
}
return value;
}
}
Remember to include in the model's file.
using TheNameSpaceWhereClassIsDeclared;
The set method of property is called when reading from datatabase with EF, or when assigned in a MVC controller's edit method.
Warning, if in web forms, if you edit dates in local timezone, you MUST convert the date to UTC before send to server.
Have a look on the michael.aird answer here: https://stackoverflow.com/a/9386364/279590
It stamp the date UTC kind during loading, with an event on ObjectMaterialized.
I have a textbox from which I am sending date as string in 'MM/dd/yyyy' formate, and when I am assigning that value to nullable datetime property value getting the error as string was not recognized as a valid datetime, I am converting the string as below then also getting the same error
private Tbl_UserDetails GetAnnouncementInformation(Tbl_UserDetails userDetails, Dictionary<string, object> details)
{
userDetails.JoiningDate = string.IsNullOrEmpty(details["JoiningDate "].ToString()) ?
(DateTime?)null :
DateTime.ParseExact(details["JoiningDate "].ToString(),
"MM/dd/yyyy", null);
userDetails.JoiningDate = string.IsNullOrEmpty(details["JoiningDate "].ToString()) ?
(DateTime?)null :
DateTime.ParseExact(details["JoiningDate "].ToString(),
"MM/dd/yyyy", CultureInfo.InvariantCulture);
}
In both the way I am getting the same error. Please help me in this.
You could do:
DateTime tempDate;
userDetails.JoiningDate = DateTime.TryParseExact(
details["JoiningDate "].ToString(),
"MM/dd/yyyy",
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out tempDate)
? tempDate
: (DateTime?)null;
With extention of :
public static class MyExtensions
{
public static DateTime? GetNullableDateTime(
this String str, string format = "MM/dd/yyyy")
{
DateTime tempDate;
var result = DateTime.TryParseExact(str, format,
CultureInfo.InvariantCulture, DateTimeStyles.None, out tempDate)
? tempDate
: default(DateTime?);
return result;
}
}
It would look like:
userDetails.JoiningDate =
details["JoiningDate "].ToString().GetNullableDateTime();
Example program
Assert.IsNull("sddfsdf".GetNullableDateTime());
Assert.IsNotNull("10/20/2014".GetNullableDateTime());
Assert.IsNotNull("20.10.2014".GetNullableDateTime("dd.MM.yyyy"));
I have two functions, in my constructor I read from the database and call all of my set functions:
while (reader.Read())
{
Status = reader["status"].ToString();
Event_Start_Date1 = reader.GetDateTime(reader.GetOrdinal("event_start_date1"));
}
my get and set method for status works fine. for the date field however, i get an error because sometimes the field coming from the database is a NULL value. how would i modify my method to assign the minvalue incase the database returns a NULL?
public DateTime Event_Start_Date1
{
get
{ return event_start_date1; }
set
{ event_start_date1 = value; }
}
Use DataReader.IsDBNull to check if the value is null:
Event_Start_Date1 = reader.IsDBNull("event_start_date1") ? DateTime.MinValue :
reader.GetDateTime(reader.GetOrdinal("event_start_date1"));
The above sentence is in the following format:
Condition ? accepted action : rejected
action
Check for DBNull and based on that you can assign MinValue.
int ordinal = reader.GetOrdinal("event_start_date1");
Event_Start_Date1 = reader.IsDBNull(ordinal)? DateTime.MinValue: reader.GetDateTime(ordinal);
Wrap the logic in a method:
private static DateTime GetDateTimeValue(SqlDataReader reader, string fieldName)
{
int ordinal = reader.GetOrdinal(fieldName);
return reader.IsDBNull(ordinal) ? reader.GetDateTime(ordinal) : DateTime.MinValue;
}
...and call it:
Event_Start_Date1 = GetDateTimeValue(reader, "event_start_date1");
A more general and reusable approach could be to make it into an extension method that can (optionally) take a default value as input:
public static class SqlDataReaderExtensions
{
public static DateTime GetDateTimeValue(this SqlDataReader reader,
string fieldName)
{
return GetDateTimeValue(reader, fieldName, DateTime.MinValue);
}
public static DateTime GetDateTimeValue(this SqlDataReader reader,
string fieldName, DateTime defaultValue)
{
int ordinal = reader.GetOrdinal(fieldName);
return reader.IsDBNull(ordinal)
? reader.GetDateTime(ordinal)
: defaultValue;
}
}
This assumes that you cannot change the model. If you have that option, go with a Nullable<DateTime> instead, and have the GetDateTimeValue method default to returning null.
Use a nullable DateTime:
public DateTime? EventStartDate { get; set; }
Which is similar to:
public Nullable<DateTime> EventStartDate { get; set; }
Read more about Nullable Types
To get the default value of DateTime, you can use either DateTime.MinValue or default(DateTime):
while (reader.Read())
{
Status = reader["status"].ToString();
var startDate = reader.GetOrdinal("event_start_date1");
EventStartDate = if reader.IsDBNull(startDate) ?
reader.GetDateTime(ordinal) :
default(DateTime);
}
Essentially, DateTime is a value-type, not a reference type, and there can't be a null DateTime (or whatever other Value Type) compile-time variable, in order for it to be capable to hold a null value it has to be wrapped in the Nullable<T>).
In .NET all the non-nullable types have a default value which can be obtained via the default keyword (e.g. default(DateTime)).
you can use a try-catch
while (reader.Read())
{
Status = reader["status"].ToString();
try{
Event_Start_Date1 = reader.GetDateTime(reader.GetOrdinal("event_start_date1"));
}
catch{
Event_Start_Date1 = //some other thing
}
or you can use GetSqlDateTime()
while (reader.Read())
{
Status = reader["status"].ToString();
Event_Start_Date1 = reader.GetSqlDateTime(reader.GetOrdinal("event_start_date1"));
}
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")
]