Date comparison with MaxValue not working - c#

I have a nullable Date property called BoughtDate.
I am trying the following:
if( item.BoughtDate.Value.Equals( DateTime.MaxValue) ) item.BoughtDate= null;
also tried this:
if( item.BoughtDate.Equals( DateTime.MaxValue) ) item.BoughtDate=null;
When debugging, my BoughtDate and DateTime.MaxValue seems exactly the same - yet it says it is not the same(does not set my item.BoughtDate to null)
Why does my comparison not work?

The problem,as #PaulSuart points in the comments, is probably the milliseconds precision. DateTime.MaxValue.TimeOfDay is {23:59:59.9999999},but your BoughtDate.TimeOfDay is probably {23:59:59.9990000},so they are not equal.
What i would do is just comparing the dates, i think that's enough in most cases:
if( item.BoughtDate.Value.Date.Equals( DateTime.MaxValue.Date) ) item.BoughtDate= null;

EDIT: As someone pointed out it doesn't answer the original question, just provides an alternative.
Try using DateTime.Compare
https://msdn.microsoft.com/pl-pl/library/system.datetime.compare(v=vs.110).aspx
public static void Main()
{
DateTime date1 = new DateTime(2009, 8, 1, 0, 0, 0);
DateTime date2 = new DateTime(2009, 8, 1, 12, 0, 0);
int result = DateTime.Compare(date1, date2);
string relationship;
if (result < 0)
relationship = "is earlier than";
else if (result == 0)
relationship = "is the same time as";
else
relationship = "is later than";
Console.WriteLine("{0} {1} {2}", date1, relationship, date2);
}
in your example maybe like that:
if(DateTime.Compare(item.BoughtDate.Value,DateTime.MaxValue) == 0) item.BoughtDate=null;

I did a quick test for your case:
static void Main(string[] args)
{
DateTime? BoughtDate = DateTime.MaxValue;
//subtract 100 milliseconds from it
BoughtDate = BoughtDate.Value.AddMilliseconds(-1);
Console.WriteLine(BoughtDate.Value.Hour); //23
Console.WriteLine(DateTime.MaxValue.Hour); //23
Console.WriteLine(BoughtDate.Value.Minute); //59
Console.WriteLine(DateTime.MaxValue.Minute); //59
Console.WriteLine(BoughtDate.Value.Second); //59
Console.WriteLine(DateTime.MaxValue.Second); //59
Console.WriteLine(BoughtDate.Value.Year); //9999
Console.WriteLine(DateTime.MaxValue.Year); //9999
Console.WriteLine(BoughtDate.Value.Month); //12
Console.WriteLine(DateTime.MaxValue.Month); //12
Console.WriteLine(BoughtDate.Value.Day); //31
Console.WriteLine(DateTime.MaxValue.Day); //31
Console.WriteLine(BoughtDate.Value.Millisecond); //998
Console.WriteLine(DateTime.MaxValue.Millisecond); //999
if (BoughtDate.Value.Equals(DateTime.MaxValue))
{
Console.WriteLine("equals comparison succeeded"); //doesn't get printed
}
}
After reducing just one millisecond from original value it doesn't passes the equality check condition in if block but if you see the value of BoughtDate in quick watch window they seem exactly the same as you said (as it shows only year, month, day, hour, minute and second parts).
It shows millisecond part only when you expand the + sign.
So your date time variables must be differing in millisecond part even if they appear to be the same at one glance.

I think you might find answer in the below link if the Date you are fetching from SQL-Server.
SQL-Server only supports the time range till 23:59:59.997 and this might cause few ticks less than what C# supports. You can just compare the dates as #Pikoh already mentioned.
https://learn.microsoft.com/en-us/sql/t-sql/data-types/datetime-transact-sql

Related

Should an exception be thrown and handled if it cannot happen?

I have a function that takes three parameters (day, month & year) and creates a new dateTime. It's a public function that gets called when any of three comboboxes are dropped down.
When unit testing I accidentally entered an invalid value and it threw a invalid date time exception, but this won't be possible in the application as the comboboxes are pre-populated with valid values only.
So question is should I still check and handle this exception in the function?
In general, yes, any public function could be called from anywhere and it is good practice to defend your code from invalid inputs also if, at certain point in time, you are sure about who feeds the inputs to the function.
However, this supposed function could handle the impossible situations by itself without triggering an exception if the inputs are not good.
It is relatively easy to check the inputs and follow the well known pattern of TryParse
public bool TryMakeDateTime(int year, int month, int day, out DateTime date)
{
date = DateTime.MinValue;
if(!IsValidDay(year, month, day))
return false;
date = new DateTime(year, month, day);
return true;
}
public bool IsValidDay(int year, int month, int day)
{
if(day < 1 || day > 31)
return false;
if(month < 1 || month > 12)
return false;
if(day > 30 && (month == 2 ||
month == 4 ||
month == 6 ||
month == 9 ||
month == 11))
return false;
// This is arbitrary, adjust the check to your constraints
if(year < 1900 || year > 2099)
return false;
if(month == 2)
{
// IsLeapYear cannot handle values below 1 or higher than 9999
// but we have already checked the year with more retrictive
// constraints.
int extraDay = (DateTime.IsLeapYear(year) ? 1 : 0);
if(day > (28 + extraDay))
return false;
}
return true;
}
Yes, the function should throw an exception for invalid inputs, if the function, in isolation, allows invalid inputs to be submitted. You don't know how, or from where, a future developer might call this function. But another, better option is to code the function so that only valid inputs are allowed.
You can do this by changing the type of the inputs from integer values to Enums. Create a Month Enum
public enum CalendarMonth {
NotSet = 0, January = 1, February = 2,
March = 3, April = 4, May = 5, June = 6,
July = 7, August = 8, September = 9,
October = 10, November = 11, December = 12}
and a DayOfMonth Enum
public enum DayOfMonth {
NotSet = 0, dom1 = 1, dom2 = 2, ....etc., ... dom31 = 31 }
You could code the function to treat the 31st of months with only 30 days in them as the first of the next month, and Feb 29, 30 and 31 as March 1,2,3, etc., to avoid treating this as invalid. Then your function's signature would be
public DateTime NewDate(DayOfMonth dom, CalendarMonth month, int year);
and it would not be possible to pass it invalid values. (except I guess for year values outside the DateIme.MinDate to DateTime.MaxDate range)
You should not prevent or catch the exception. But you should make sure an exception will really happen in that "impossible" case.
Exceptions are meant for "impossible" situations that are thought not to occur "normally".
If for example you call a DateTime constructor overload, that constructor will already throw an exception if the input is invalid. If you reason that will not happen in your situation, do not handle that case. The exception message generated by the framework is just fine.

How do I get the closest DateTime from a List<DateTime>

Say I have the most recent DateTime and a List of all the possible dates. How would I efficiently go about finding the closest date time to last year's date in the list?
Say my list is comprised of the following:
2014-03-07
2014-03-14
2014-03-21
2014-03-28
...
2015-03-06
2015-03-13
2015-03-20
My most recent date is 2015-03-20, but I want to retrieve last year's date, 2014-03-21.
This is what I have currently, but it won't work if last year's date is one day off (eg; my time periods are stored weekly).
public DateTime LastYearDate()
{
List<DateTime> times = GetAllDates();
times.Sort();
times.Reverse();
DateTime currentDate = times.First();
return times.Where(dt => dt == currentDate.AddYears(-1)).First();
}
I'm not sure what I would use to recursively calculate the closest date, so if you have any ideas of what direction I should take (reference to any Linq functions to check out), that would be appreciated.
Just order by the difference between the date in the list and the date you're looking for:
var dateToFind = currentDate.AddYears(-1);
times.OrderBy(t => (t - dateToFind).Duration).FirstOrDefault();
(The difference between two date is an instance of TimeSpan; the Duration property returns the absolute value)
As it's sorted, you can use a binary search to try to find an exact match. If List<T>.BinarySearch returns a non-negative number, you know you've found an exact match. Otherwise, you can apply the bitwise complement operator to find the index that the value would be inserted at. You then need to check whether the value before or at that index is further from the target. So something like this:
var target = currentDate.AddYears(-1);
List<DateTime> times = GetAllDates();
if (times.Count == 0)
{
// ??? Work out what you want to do here, e.g. throw an exception
}
times.Sort();
var index = times.BinarySearch(target);
if (index >= 0)
{
return times[index];
}
int insertIndex = ~index;
// Handle boundary cases
if (insertIndex == 0)
{
return times[0];
}
if (insertIndex == times.Count)
{
return times[insertIndex - 1];
}
// Okay, two options - find the closest
var timeBefore = times[insertIndex - 1];
var timeAfter = times[insertIndex];
// TODO: Work out what you want to do if they're equidistant.
return target - timeBefore > timeAfter - target ? timeAfter : timeBefore;
Having said that, spender's comment on Thomas Levesque's answer gives a very simple solution:
var target = currentDate.AddYears(-1);
List<DateTime> times = GetAllDates();
if (times.Count == 0)
{
// ??? Work out what you want to do here, e.g. throw an exception
}
return times.OrderBy(t => (target - t).Duration).First();
Note that TimeSpan.Duration is always non-negative; it's like Math.Abs but for TimeSpan values.

How to specify the time precision for a comparison

I have two date values that are being returned, I then need to compare these to ensure lastWriteDate of the file has not been updated since caching.
The problem I've got is, the lastWriteDate has the milliseconds included, and the clientDate doesn't. So when compared, the lastWriteDate will always be greater if it is in the same second.
if (!(Request.Headers["If-Modified-Since"] == null))
{
DateTime clientDate = Convert.ToDateTime(Request.Headers["If-Modified-Since"]);
DateTime lastWriteDate = System.IO.File.GetLastWriteTime(newImagePath);
if (lastWriteDate <= clientDate)
{
//Code here not being reached
}
}
Those miliseconds aren't important for the comparison I'm making, so what would be the best way to compare these values without them?
Subtract one date from the other. THen you will end up with a TimeSpan object, of which you can simply check the number of seconds and ignore the milliseconds.
You can truncate the milliseconds in this way:
if (lastWriteDate.AddMilliseconds(-lastWriteDate.Millisecond) <= clientDate)
{
// ....
}
You could also create a TimeSpan and check if it's lower than your maximum:
TimeSpan diff = clientDate - lastWriteDate;
if (diff < TimeSpan.FromSeconds(1))
{
// ....
}

Calculating how many minutes there are between two times

I have a datagridview in my application which holds start and finish times. I want to calculate the number of minutes between these two times. So far I have got:
var varFinish = tsTable.Rows[intCellRow]["Finish Time"];
TimeSpan varTime = (DateTime)varFinish - (DateTime)varValue;
int intMinutes = TimeSpan.FromMinutes(varTime);
But the last line won't compile because it says I am using invalid arguments for the Timespan constructor. I've researched quite a bit about how to calculate the number of minutes between two times, but I'm hitting a bit of a brick wall. Can someone please advise me on the best way to achieve my objective.
EDIT/
Now my code is as follows:
var varFinish = tsTable.Rows[intCellRow]["Finish Time"];
TimeSpan varTime = (DateTime)varFinish - (DateTime)varValue;
int intMinutes = (int)varTime.TotalMinutes;
But I am getting an invalid cast on the second line. Both varFinish and varValue are times e.g. 10:00 and 8:00 say. So not sure why they won't cast to type DateTime?
Try this
DateTime startTime = varValue
DateTime endTime = varTime
TimeSpan span = endTime.Subtract ( startTime );
Console.WriteLine( "Time Difference (minutes): " + span.TotalMinutes );
Edit:
If are you trying 'span.Minutes', this will return only the minutes of timespan [0~59], to return sum of all minutes from this interval, just use 'span.TotalMinutes'.
double minutes = varTime.TotalMinutes;
int minutesRounded = (int)Math.Round(varTime.TotalMinutes);
TimeSpan.TotalMinutes: The total number of minutes represented by this instance.
In your quesion code you are using TimeSpan.FromMinutes incorrectly. Please see the MSDN Documentation for TimeSpan.FromMinutes, which gives the following method signature:
public static TimeSpan FromMinutes(double value)
hence, the following code won't compile
var intMinutes = TimeSpan.FromMinutes(varTime); // won't compile
Instead, you can use the TimeSpan.TotalMinutes property to perform this arithmetic. For instance:
TimeSpan varTime = (DateTime)varFinish - (DateTime)varValue;
double fractionalMinutes = varTime.TotalMinutes;
int wholeMinutes = (int)fractionalMinutes;
You just need to query the TotalMinutes property like this varTime.TotalMinutes
If the difference between endTime and startTime is greater than or equal to 60 Minutes , the statement:endTime.Subtract(startTime).Minutes; will always return (minutesDifference % 60). Obviously which is not desired when we are only talking about minutes (not hours here).
Here are some of the ways if you want to get total number of minutes(in different typecasts):
// Default value that is returned is of type *double*
double double_minutes = endTime.Subtract(startTime).TotalMinutes;
int integer_minutes = (int)endTime.Subtract(startTime).TotalMinutes;
long long_minutes = (long)endTime.Subtract(startTime).TotalMinutes;
string string_minutes = (string)endTime.Subtract(startTime).TotalMinutes;

C# start with certain value in foreach

Is there a way to start with a certain value in the foreach? In specific I would like to start with 7:30AM.
DateTime start = DateTime.Today;
var clock = from offset in Enumerable.Range(0, 48)
select start.AddMinutes(30 * offset);
foreach (DateTime time in clock)
{
string timeValue = time.ToString("hh:mmtt");
}
In short, no. Just make your Enumerable.Range cover the right range (might want to double-check):
var clock = from offset in Enumerable.Range(15, 48)
select start.AddMinutes(30 * offset);
Edit
A cleaner solution might be something like:
DateTime start = // set start
DateTime end = // set end
while (start <= end) {
// do stuff here
start = start.AddMinutes(30);
}
It is not possible to "start" at a different value in the collection with the foreach syntax directly (wrapping is an option and the following example could even be considered a form of wrapping -- LINQ generation is often lazy), but it is possible to iterate over an appropriately modified collection and obtain the desired semantics.
Enumerable.Range returns something IEnumerable<T> -- you can always Where-limit or Skip-through it (e.g., see the IEnumerable/LINQ (extension) methods) and then use the resultant as the "for each" collection.
For instance (this relies on the fact that time is increasing and somewhat "inefficient", but chances of it even mattering are close to nil in most applications):
foreach (DateTime time in clock.Where(t => t >= someStartTime)) {
string timeValue = time.ToString("hh:mmtt");
}
Another solution based on ide's comment (which is, in my mind, cleaner than the above -- it also has to evaluate the conditional less):
foreach (DateTime time in clock.SkipWhile(t => t < someStartTime)) {
...
}
Note that these IEnumerable results are lazily evaluated streams.
However, some of the other answers provide nice alternative solutions and different ways of approaching the problem. I would only use the above if the initial sequence could not be altered and/or was used for different purposes requiring the original data.
Happy coding.
You can set your start to 7:30AM today, like this:
DateTime start = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 7, 30, 0);
Just replace this, It'll start with 7:30 AM.
var clock = from offset in Enumerable.Range(15, 48)
Good luck!
DateTime start = DateTime.Today.Add(new TimeSpan(7, 30, 0));
var clock = from offset in Enumerable.Range(0, 48)
select start.AddMinutes(30 * offset);
foreach (DateTime time in clock)
{
string timeValue = time.ToString("hh:mmtt");
}
So, to start from 7:30 today, the key line is the first one:
DateTime start = DateTime.Today.Add(new TimeSpan(7, 30, 0));
It would be clearest is you set the time specifically and add to that. By simply changing the starting point of the enumeration, you'll get the desired starting times but it is unclear what the value is by inspection.
var start = DateTime.Today.Add(new TimeSpan(7, 30, 0));
var times = Enumerable.Range(0, 48)
.Select(offset => start.AddMinutes(30 * offset));
foreach (var time in times)
{
string timeValue = time.ToString("hh:mmtt");
// ...
}

Categories

Resources