Datetime filter in kendo grid - c#

My code is in C# .NET
I am using Kendo Grid version 2013.2.716.340 and server binding to show data in grid.
In Kendo UI Grid, I have a dateTime column but the column filter input only has a date picker but no time picker. Due to this if I select the option IsEqualTo and give a date then I get zero results as the time is set to 00:00:00 in the filter but the columns have some time value.
I want to add time picker along with date picker.
I tried to do this on my column, but it didn't work:
columns.Bound(o => o.Time).Title("Time").Format("{0:MM/dd/yyyy HH:mm:ss}").Filterable(f => f.UI("DateTimeFilter")).Width("5%");
And have applied below script :
<script type="text/javascript">
function DateTimeFilter(control)
{
$(control).kendoDateTimePicker();
}
</script>
The above code works when I select exact datetime from datetimepicker but it doesn't work when I select isequalto.
For eg : If I have this datetime "12/21/2013 07:15:45" displayed in my kendo grid column and when I copy this datetime to isequalto option under filter it does not gives any data.
Also I tried the example provided on this link It also didn't work in my case. Example on this link uses Ajax binding. I need to apply it in case of server binding.
This is an attached image that shows what I want to apply. Here is the link for image.
If I copy the datetime shown in grid to the filter It should filter correctly and give result.
I will be very thankful if anybody could help me out in solving my issue. Thanks in advance.

From my experience, the kendoDateTimePicker is really picky; if the format of the filter cannot specify the datetime precision of the column data, it will not find it.
In your case, your column format is "MM/dd/yyyy HH:mm:ss" (with seconds). The default format for the kendoDateTimePicker is "MM/dd/yyyy h:mm tt" (without seconds and hour spec is mismatched). Since you initialized a default kendoDateTimePicker, no matter what you put in the picker, you could never filter to a date that IS EQUAL TO a column value since you couldn't input how many seconds it was.
The easiest way to ensure it works is to use the same format for both column and the kendoDateTimePicker . Replace your DateTimeFilter function with this:
function DateTimeFilter(control)
{
$(control).kendoDateTimePicker({
format: "MM/dd/yyyy HH:mm:ss",
timeFormat: "HH:mm:ss"
});
}
With regards to the kendoDateTimePicker:
format defines the input value format for control
timeFormat defines the time format of the time picker
interval (didn't use it above), but it specifies the time interval in minutes between each option of the time picker.
I am not using asp.net mvc, so I'm not 100% sure if this solves your problem. However I am certain it will clear up at least some of the filtering issues you have. I can provide a jsfiddle for a purely html/javascript sample if you want.

I know I am late with this answer, but it might still help someone.
The above code works when I select exact datetime from datetimepicker but it doesn't work when I select isequalto. For eg : If I have this datetime "12/21/2013 07:15:45" displayed in my kendo grid column and when I copy this datetime to isequalto option under filter it does not gives any data.
I guess you are experiencing this because your server-side DateTime values contain fractional second data as well and the equals operator does not ignore them at comparison. I have found it easier to come up with a server-side solution instead of writing all sort of dirty JS workarounds.
The idea is that whenever you find a filter in the DataSourceRequest object that would filter on a DateTime property, you manually replace it with a CompositeFilterDescriptor, which truncates the value to the desired precision, sets it as the lower bound and then adds one unit of the desired precision (sec, min, hour, etc.) and sets it as the upper bound.
The code is the following:
public static class KendoHelpers
{
public enum DateTimePrecision
{
Seconds = 1,
Minutes = 2,
Hours = 4
}
public static DataSourceRequest NormalizeDateFilters(this DataSourceRequest request, DateTimePrecision precision)
{
// TODO: Add parameter validation.
for (int i = 0; i < request.Filters.Count; ++i)
{
FilterDescriptor filter = request.Filters[i] as FilterDescriptor;
if (filter != null && filter.ConvertedValue is DateTime && filter.Operator == FilterOperator.IsEqualTo)
{
DateTime val = (DateTime)filter.ConvertedValue;
CompositeFilterDescriptor newFilter = new CompositeFilterDescriptor
{
LogicalOperator = FilterCompositionLogicalOperator.And
};
DateTime lowerBound;
DateTime upperBound;
if (precision == DateTimePrecision.Seconds)
{
lowerBound = val.TruncateToWholeSeconds();
upperBound = lowerBound.AddSeconds(1);
}
else if (precision == DateTimePrecision.Minutes)
{
lowerBound = val.TruncateToWholeMinutes();
upperBound = lowerBound.AddMinutes(1);
}
else if (precision == DateTimePrecision.Hours)
{
lowerBound = val.TruncateToWholeHours();
upperBound = lowerBound.AddHours(1);
}
else
{
// If someone would be stupid enough to supply Hours | Minutes
throw new ArgumentException("Not supported precision. Only Second, Minute, Hour values are supported.", "precision");
}
newFilter.FilterDescriptors.Add(new FilterDescriptor
{
Member = filter.Member,
MemberType = filter.MemberType,
Operator = FilterOperator.IsGreaterThanOrEqualTo,
Value = lowerBound
});
newFilter.FilterDescriptors.Add(new FilterDescriptor
{
Member = filter.Member,
MemberType = filter.MemberType,
Operator = FilterOperator.IsLessThan,
Value = upperBound
});
request.Filters[i] = newFilter;
}
}
return request;
}
}
Remarks:
The DateTime truncater extension is based on this answer.
This method will only do anything if the operator is equals, because if you select Is later than or the like, the default behavior will work just as well.
This method does not care about any present CompositeFilterDescriptors becasue an expression dateToSearch = 2016-11-21 11:22:00 AND dateToSearch = 2016-11-21 11:59:00 makes no sense anyway.
Similar thing could be done for DateTimeOffset values.

An enhancement to Balázs' answer, this assumes that you are using a simple date portion of DateTime and don't care about the time portion at all. It is also recursive to handle being filtered with other unrelated filters.
public static IList<IFilterDescriptor> NormalizeDateFilters(this IList<IFilterDescriptor> filters)
{
for (var i = 0; i < filters.Count; i++)
{
if (filters[i] is CompositeFilterDescriptor compositeFilterDescriptor)
{
compositeFilterDescriptor.FilterDescriptors.NormalizeDateFilters();
}
else if (filters[i] is FilterDescriptor filterDescriptor &&
filterDescriptor.ConvertedValue is DateTime &&
filterDescriptor.Operator == FilterOperator.IsEqualTo)
{
var value = DateTime.Parse(filterDescriptor.Value.ToString());
var start = value.Date;
var end = start.AddDays(1);
var newFilter = new CompositeFilterDescriptor
{
LogicalOperator = FilterCompositionLogicalOperator.And
};
newFilter.FilterDescriptors.Add(new FilterDescriptor
{
Member = filterDescriptor.Member,
MemberType = filterDescriptor.MemberType,
Operator = FilterOperator.IsGreaterThanOrEqualTo,
Value = start
});
newFilter.FilterDescriptors.Add(new FilterDescriptor
{
Member = filterDescriptor.Member,
MemberType = filterDescriptor.MemberType,
Operator = FilterOperator.IsLessThan,
Value = end
});
filters[i] = newFilter;
}
}
return filters;
}

Related

Credit Card Expiry Check Linq C#

My application automatically bills subscribed customers using a payment processing vendor. I keep only information on the expiry date of the card and the vendors reference number for auto-billing. Everyday I want to check members whose cards will expire within 30 days. I am running a background process to send email reminders. I am struggling getting Linq to accept my query. The expiry date is stored in the database as a string eg 0319 for March 2019. I am wondering if I have any chance of getting this to work. Please assist if you can. My last resort might be having to format expiry dates currently stored as strings mmyy in database to proper dates.
int mon = DateTime.Now.Month;
int yr = DateTime.Now.Year;
int days = DateTime.DaysInMonth(yr, mon);
int dy = DateTime.Now.Day;
var allCardExpiring = db.DirectDebits.Include(i => i.Customer).Where(a =>a.DdOk && a.Customer.PassOk && DateTime.DaysInMonth(Convert.ToInt32(a.DateExpiry.Substring(2, 4)), Convert.ToInt32(a.DateExpiry.Substring(0, 2)))+days-dy < 30).Select(a => a.DirectDebitId).Distinct().ToList();
This is a good example that shows that you should not form the database into the format that operators use to enter their input. If your database would have had the ExpiryDate as a DateTime, you wouldn't have had this problem. Of course with the cost that when operators enter their expiry date you would have to convert it to a DateTime, but (1) that is easier than converting back and (2) What do you more often: query the ExpiryDate or update it?
If you are stuck with this database, then my advice would be to create a query where you split your MonthYear property into a a Month and a Year, using DbFunctions.Left and DbFunctions.Right then convert it in your query to a proper DateTime using DbFunctions.CreateDateTime
If you need this conversion for other functions, consider creating separate IQueryable functions for this, so you can re-use it.
As extension function for your DirectDebit that takes an input sequence of DirectDebits and returns a DateTimeDirectDebit sequence:
public static IQueryable<DateTimeDirectDebit> ToDateTimeDirectDebits(
this IQueryable<DirectDebit> directDebits)
{
return directDebits.Select(directDebit => new
{
// split ExpiryDate into a Month and a Year
ExpiryDate = new
{
Month = DbFunctions.Left(directDebit.DateExpire, 2),
Year = DbFunctions.Right(directDebit.DateExpire, 2),
}
DirectDebit = directDebit,
})
.Select(directDebit => new DateTimeDirectDebit
{
// create the ExpiryDate as DateTime
ExpiryDate = DbFunctions.CreateDateTime(
directDebit.ExpiryDate.Year,
directDebit.ExpiryDate.Mnth,
1, // first day of the month
...),
DirectDebit = directDebit.DirectDebit,
});
}
You also need a function that returns the DateTimeDirectDebits that expire within a certain amount of days. Again as an extension method:
public static IQueryable<DateTimeDirectDebit> WhereExpiresWithinDays(
this IQueryable<DateTimeDirectDebit> source,
int nrOfDays)
{
DateTime now = DateTime.now;
DateTime limitDate = now.AddDays(nrOfDays);
return source.Where(directDebit => directDebit.ExpiryDate < limitDate);
}
Similarly you might want a function that returns all directDebits that expire next Month use DbFunctions.DiffMonths for this.
Usage:
using (var dbContext = new ...)
{
var directDebitsThatExpireNextMonth = dbContext.DirectDebits
.ToDateTimeDirectDebits
.WhereExpiresWithinDays(30)
.Select(...);
}
The nice thing is, that by using these LINQ-like extension methods, you can hide how your database is structured, especially the parts that you are not happy about. This way, your code does not have to be restructures so much if your database changes internally, especially those functions that don't use these changes.
Of course, for a proper hiding of the database structure, class DateTimeDirectDebits should not expose property DirectDebits, but only the properties you want to show to the outside world.

DateTime formatting to remove time if not time is zero (00:00:00)

I have a page to display content of different tables upon user selection.
These tables have fields DateTime type, but time is not always relevant, specially when time is 00:00:00
These means that, sometimes the table to display might have a BirthDate column where time is irrelevant, others might have CreatedOn where time is necessary to be displayed in the view.
I am not able to specifically set a format for an specific column as the view can show any table, however in code behind (C#) I am able to identify if a column is DateTime type and set a format before displayng.
Using condition like:
DataTable dt0 = dsDataSet.Tables[0].Copy();
foreach (DataColumn column in dt0.Columns)
{
if (column.DataType == typeof(System.DateTime))
{
var printDateFormat = dtfi.ShortDatePattern;
:..
:..
}
}
The result will be dd-MM-yyyy for all DateTime columns, and will remove the time for columns even for those columns for which time is needed to be displayed.
An ideal solution should display the data as follow:
in DB -> DateTime |In Page View| in DB -> DateTime |In Page View |
===================|============|===================|===================|
BirthDate | |Created On | |
===================|============|===================|===================|
07/03/2014 00:00:00|07/03/2014 |05/03/2015 03:04:01|05/03/2015 03:04:01|
12/01/2014 00:00:00|12/01/2014 |03/01/2015 06:05:01|03/01/2015 06:05:01|
Thank you in advance.
I'm not sure how efficient this is, but this will strip the time when you need that done:
First Cast the Date as type DATE, then recast it as DATETIME
CAST(CAST(GETDATE() AS Date) AS Datetime)
However, if you're just formatting it in C#, just call ToShortDateString() on the DateTime field.
UPDATE
So, you just need to display the time if it has a non-zero time. In that case, you could:
Test the Hours, Minutes and Seconds to see if they are each 0.
var date = DateTime.Now;
var printDate = (date.Hour == 0 && date.Minute == 0 && date.Second == 0)
? date.ToShortDateString() : date.ToShortDateTimeString();
UPDATE 2
One other thing would be to get the time of day and then the total seconds. This will be zero at midnight.
var printDate2 = (date.TimeOfDay.TotalSeconds == 0)
? date.ToShortDateString() : date.ToShortDateTimeString();
You can create a new DataTable, replacing the DateTime column to a string column, specifying the desired format.
// DataTable with original data
// May contain DataTime with zero time.
var dt = new DataTable();
// DataTable with Date converted to string.
var dt2 = new DataTable();
var dateIndexes = new HashSet<int>();
// Create columns in dt2, changing DateTime with zero time to string type
foreach (DataColumn column in dt.Columns)
{
if (column.DataType == typeof(DateTime) &&
dt.Rows.Cast<DataRow>()
.Select(row => (DateTime)row[column])
.All(dateTime => dateTime.TimeOfDay.Ticks == 0))
{
// If all time is zero then create string type column.
dt2.Columns.Add(column.ColumnName, typeof(string));
// Remember the index of the column with zero time
dateIndexes.Add(column.Ordinal);
}
else
{
// Create column same type.
dt2.Columns.Add(column.ColumnName, column.DataType);
}
}
// Copy rows
foreach (DataRow row in dt.Rows)
{
var newRow = dt2.NewRow();
dt2.Rows.Add(newRow);
for (int i = 0; i < row.ItemArray.Length; i++)
{
if (dateIndexes.Contains(i))
{
// Column with zero time. Convert it to string.
newRow[i] = ((DateTime)row[i]).ToShortDateString();
}
else
{
// Copy as is.
newRow[i] = row[i];
}
}
}
One solution is to add a custom extension method to the DateTime class that returns your custom string format (this potentially saves a lot of duplicated code and provides a single place to change the format later if needed):
public static class Extensions
{
public static string GetCustomFormatString(this DateTime input,
bool excludeTimeIfZero = true)
{
return input.TimeOfDay == TimeSpan.Zero && excludeTimeIfZero
? input.ToString("MM/dd/yyyy")
: input.ToString("MM/dd/yyyy hh:mm:ss");
}
}
Example Usage
private static void Main()
{
// DateTime.Now includes a non-zero time (except at midnight)
Console.WriteLine(DateTime.Now.GetCustomFormatString());
// A new DateTime has a zero time value
Console.WriteLine(new DateTime().GetCustomFormatString());
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
Output

Change the date part of a DateTime property before database record created

I have a view model in which I am storing a DateTime but in my view using a JQUERY datetimepicker, time only:
ViewModel
[DataType(DataType.Time)]
public DateTime? MondayFrom { get; set; }
[DataType(DataType.Time)]
public DateTime? MondayTo { get; set; }
As it stands, when the Create method gets called it is using todays date plus the time selected from the timepicker.
As I am not particularly concerned with the Date part of the DateTime, I want to change the day, month & year to 01/01/1900 or something less specific than the date the record was written, before the record is created, this is purely to avoid any confusion in the future.
I don't want to get bogged down on whether this is the right thing to do or not.
I'm struggling to get a handle on the Date part of the DateTime, see below:
public void CreateClub(Club club)
{
foreach (var item in club.MeetingDays)
{
// change Date part to 01/01/1900, leave the time as is..
}
_repository.CreateClub(club);
}
How might I floor the date part of the item but leave the time well alone?
Just use the TimeOfDay property to extract the time within the day, and add that to the date you want:
private static readonly DateTime BaseDate = new DateTime(1900, 1, 1);
var updatedDateTime = BaseDate + otherDateTime.TimeOfDay;
You could even write an extension method or two:
public static DateTime WithDate(this DateTime start, DateTime date)
{
// No need to use the Date property if you already know
// it will be midnight...
return date.Date + start.TimeOfDay;
}
public static DateTime WithFloorDate(this DateTime start)
{
return start.WithDate(FloorDate);
}
Of course, I'd suggest you use Noda Time where you can specify dates, times and date/time values (with or without a time zone or UTC offset0 separately, but that's a different conversation.
DateTime is immutable - you cant just change part of it. You can extract the time and add it to a "base" date:
for(int i=0; i < club.MeetingDays.Count; i++)
{
club.MeetingDays[i] = new DateTime(1900, 1, 1) + club.MeetingDays[i].TimeOfDay;
}
Note that you need a for loop so you can place the new value back in the collection. You could also use Linq:
club.MeetingDays = club.MeetingDays
.Select(t => new DateTime(1900, 1, 1) + t.TimeOfDay)
.ToList();
Assuming that club.MeetingDays is a List<Datetime>

Using ToString on a DateTime object

In my database the date is stored like datetime but when I want to perform a search/filtering I want it to be based only on the date ignoring the exact time. I spend a lot of time figuring out how to do it and finally I got a working solution on my own :
string val = rule.Data;
if (!string.IsNullOrEmpty(val))
{
switch (rule.Field)
{
case "Date": {
DateTime parsedDate = DateTime.ParseExact(
val,
"dd/MM/yyyy",
CultureInfo.InvariantCulture);
var pYear = parsedDate.Year;
var pMonth = parsedDate.Month;
var pDay = parsedDate.Day;
rows = rows.Where(o => o.Date >= parsedDate && o.Date <= new DateTime(pYear, pMonth, pDay, 12, 59, 40)); break;
}
}
}
This is working Ok. It needs a little change but I think I can use the code above. However today a college of mine pass me a solution which is from a previous project being developed here, and this solution is a lot shorter and I would prefer to use it if possble. It looks like this:
string val = rule.Data;
if (!string.IsNullOrEmpty(val))
{
switch (rule.Field)
{
case "Date": { rows = rows.Where(o => o.Date.ToString("dd/MM/yyyy") == val); break; }
}
}
The code doesn't break when I try this but it's not filtering data too. I always get empty result. I guess that o.Date.ToString("dd/MM/yyyy") is where the problem lies. I don't know is it ok to use ToString() like this for DateTime object. In the example I'm using ToString() also get a format type like the one I'm providing here - ToString("dd/MM/yyyy") - but in my case ToString() is not overloaded anywhere. Is this a standard way to manipulate DateTime objects or I just can't find the place where ToStrin() is predefined. And finally, can you provide me with a working example based on the code above.
Depending on what culture o.Date is, Try:
string val = rule.Data;
if (!string.IsNullOrEmpty(val))
{
switch (rule.Field)
{
case "Date":
{
rows = rows.Where(o => o.Date.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture) ==
DateTime.ParseExact(val,
"dd/MM/yyyy",
CultureInfo.InvariantCulture));
break;
}
}
}
Or you could set the culture of the current thread instead:
Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
Edit: It should work if you avoid using strings:
e.g.
DateTime maxDate = new DateTime(2020, 11, 17);
if (DateTime.Now.Date > maxDate)
{
// this will just work regardless of setting culture
}
I think if you need to compare dates then you can just get a Date component of a DateTime and compare it to your predefined value. This should be faster as there won't be a need to transform date to string every time as well. So you can first get your reference value like that DateTime.ParseExact(value, "dd/MM/yyyy", CultureInfo.InvarianCulture). You can just use a constructor of a DateTime to compose it as well.
You shouldn't have to use strings at all. If it is a datetime (or similar) in the database, and a DateTime in your c# code, then there is never a good reason to use a string as an intermediate step.
Also, you should pay close attention to the .Kind property of your DateTime values. And you should never be comparing local times against DateTime.Now. If you do, you may introduce errors during daylight saving time transitions. Instead, you should use UTC DateTime values, or use DateTimeOffset values instead. Read more here.

C# DateTime evaluation problem

So I'm trying to figure out what I'm doing wrong with this logic. It seems straightforward and my breakpoints indicate that the evaulation in the 'if' statement is resolving as True, but sum.ppStart et al aren't getting 14 days added to them.
It's probably something simple, but any help would be appreciated.
//Determine the start/end days of each week of the pay period and retrieve a list of those entries
DateTime[] weeks = timeTools.calcPP(0);
DateTime today = DateTime.Now.Date;
if (today > weeks[3])
{
weeks[0].AddDays(14);
weeks[3].AddDays(14);
weeks[4].AddDays(14);
}
sum.ppStart = weeks[0];
sum.ppEnd = weeks[3];
sum.payDate = weeks[4];
AddDays returns a new instance of DateTime, the existing value is not changed, it is an immutable structure. When using the function, capture the result
DateTime myDate = ...
myDate = myDate.AddDays(14);
That is because you're not using the result of the AddDays method. The signature is
public DateTime AddDays(double days)
or so (see link). You need to do this:
weeks[0] = weeks[0].AddDays(14);
You need to assign the values:
if (today > weeks[3])
{
weeks[0] = weeks[0].AddDays(14);
weeks[3] = weeks[3].AddDays(14);
weeks[4] = weeks[4].AddDays(14);
}

Categories