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

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.

Related

Date comparison with MaxValue not working

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

Best way to compare Periods in using NodaTime (or alternative)

I have a requirement to have a relative min/max date validation able to be stored in a database to customize an application per customer. I decided that the NodaTime.Period due to it's capability to specify years was the best choice. However, NodaTime.Period does not offer a way to compare itself against another period.
Example data provided for this validation:
Minimum Age of 18 years old.
Maximum Age o 100 years old.
Minimum sale duration of 1 month
Maximum sale duration of 3 months
Minimum advertising campaign 7 days
(Note: Current requirements are that Year / Month / Day will not be combined in validations)
The validations are:
public Period RelativeMinimum { get; set; }
public Period RelativeMaximum { get; set; }
Given a user entered date (and now):
var now = new LocalDate(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
var userValue = new LocalDate(date.Year, date.Month, date.Day);
var difference = Period.Between(userValue, now);
I have a comparison of:
if(RelativeMinimum != null && difference.IsLessThan(RelativeMinimum))))
{
response.IsValid = false;
response.Errors.Add(MinimumErrorMessage);
}
Which is consuming an extensions class:
public static class PeriodExtensions
{
public static bool IsLessThan(this Period p, Period p2)
{
return (p.Years < p2.Years) || (p.Years == p2.Years && p.Months < p2.Months) || (p.Years == p2.Years && p.Months == p2.Months && p.Days < p2.Days);
}
public static bool IsGreaterThan(this Period p, Period p2)
{
return (p.Years > p2.Years) || (p.Years == p2.Years && p.Months > p2.Months) || (p.Years == p2.Years && p.Months == p2.Months && p.Days > p2.Days);
}
}
While this approach works, given the test conditions I have, I have to wonder why #jon-skeet didn't implement this, and immediately have to worry over what am I missing and what alternative should I be using instead?
The main reason periods aren't comparable is that they can contain components of variable lengths.
Two one-month periods aren't necessarily the same number of days long. As an example, which is greater: 1 month or 30 days? If the month is January, then that's longer than 30 days. If the month is February, that's less than 30 days.
The same applies to years. Some are 365 days long, some are 366.
Of course, that all assumes you're using the Gregorian calendar. Noda Time supports other calendar systems, and there are similar quirks in those as well.
Regarding the code:
If you want a LocalDate from a DateTime, use LocalDateTime.FromDateTime(dt).Date
To get the current date, use SystemClock.Instance.Now.InZone(tz).Date
If you intended that to be the same as DateTime.Now, which uses the local time zone of the computer where the code is running, then get tz by calling DateTimeZoneProviders.Tzdb.GetSystemDefault()
For comparison of the type of problem you have described, consider defining min and max days instead of min and max periods. Then you wont have such variation of units. You can get the difference in days like this:
long days = Period.Between(d1, d2, PeriodUnits.Days).Days;
I believe something like this would work well for your use case:
public static bool IsDifferenceLessThan(LocalDate d1, LocalDate d2, Period p)
{
if (p.HasTimeComponent)
throw new ArgumentException("Can only compare dates.", "p");
if (p.Years != 0)
{
if (p.Months != 0 || p.Weeks != 0 || p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var years = Period.Between(d1, d2, PeriodUnits.Years).Years;
return years < p.Years;
}
if (p.Months != 0)
{
if (p.Weeks != 0 || p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var months = Period.Between(d1, d2, PeriodUnits.Months).Months;
return months < p.Months;
}
if (p.Weeks != 0)
{
if (p.Days != 0)
throw new ArgumentException("Can only compare one component of a period.", "p");
var weeks = Period.Between(d1, d2, PeriodUnits.Weeks).Weeks;
return weeks < p.Weeks;
}
var days = Period.Between(d1, d2, PeriodUnits.Days).Days;
return days < p.Days;
}
Just as an additional point to Matt's already-excellent answer, we provide an option for creating an IComparer<Period> with a specific anchor point, e.g.
var febComparer = Period.CreateComparer(new LocalDate(2015, 2, 1).AtMidnight());
var marchComparer = Period.CreateComparer(new LocalDate(2015, 3, 1).AtMidnight());
var oneMonth = Period.FromMonths(1);
var twentyNineDays = Period.FromDays(29);
// -1: Feb 1st + 1 month is earlier than Feb 1st + 29 days
Console.WriteLine(febComparer.Compare(oneMonth, twentyNineDays));
// 1: March 1st + 1 month is later than March 1st + 29 days
Console.WriteLine(marchComparer.Compare(oneMonth, twentyNineDays));

How can I use the return of .DayOfWeek to check property of class

I have a scheduler program which allows the user to choose which days of the week it will be allowed to run a schedule.
The properties in my class include each day of the week.
Instead of using 7 statments like if (Schedule[i].Sunday == true)
How would I go about something like this:
if (Schedule[i].(DateTime.Now.DayOfWeek) == true)
You can make int DaysOfWeek property in the class, which will have its bits set according to day of week. Let's say 0000001 - Sunday, 0000010 - Monday, 0000011 - Sunday & Monday. I start from Sunday, because DayOfWeek enum starts from Sunday (http://referencesource.microsoft.com/#mscorlib/system/dayofweek.cs).
Then you can check the property the following way:
if ((DaysOfWeek & (1 << (int)DateTime.Now.DayOfWeek)) != 0)
{
}
You should use a multidimensional array for Schedule; your first index will be i, and your second one is the day of the week as an integer.
Then cast the DayOfWeek to an integer:
if (Schedule[i][((int)DateTime.Now.DayOfWeek)] == true)
Note that I'm using the DayOfWeek as the second index, rather than a property. This is why I mentioned using a multidimensional array earlier.
Make an enum with all day of the week set as flags,
[Flags]
Enum DaysOfWeek
{
None = 0,
Monday = 1 << 1,
Tuesday 1 << 2,
...
}
Then you will be able to use days.HasFlag(DaysOfWeek.Monday); to check for specific days.
http://msdn.microsoft.com/en-us/library/system.enum.hasflag(v=vs.110).aspx
You can also use them in a switch statement to do different actions for each days.
Sadly the current C# system.dayofweek has values from 0 to 6 http://msdn.microsoft.com/en-us/library/system.dayofweek(v=vs.110).aspx so it doesn't work with HasFlag
It's tough to say without knowing the structure of your Schedule, but if you're storing a DayOfWeek type in your Schedule you can use this:
Schedule[i].DayOfWeek == DateTime.Today.DayOfWeek;
You could also get a list of scheduled items from your List like this
var scheduledItems = schedule.Where(x=>x.DayOfWeek == DateTime.Today.DayOfWeek)

How to iterate through an enumeration of flags and create checkboxes that are checked appropriately for each value?

I have an Enum of flags to represent the days of the week, with a few extra values that indicate weekdays, weekends, every day, or no days.
Here's the Enum:
[Flags]
public enum DayOfWeek : short
{
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64,
Weekday = 62,
Weekend = 65,
Everyday = 127,
None = 0
}
I also have a View Model with a property, DayOfWeek with a type of DayOfWeek.
In my View, I need to create a checkbox for each day of the week and somehow appropriately check the boxes based on which days have been saved.
How can I do this?
I found a good solution.
Here's what I came up with:
#foreach (DayOfWeek item in Enum.GetValues(typeof(DayOfWeek)))
{
if (0 < item && (item <= DayOfWeek.Friday || item == DayOfWeek.Saturday))
{
#Html.Label("DayOfWeek", item.ToString())
#Html.CheckBox("DayOfWeek", (Model.DayOfWeek.HasFlag(item)), new { value = item })
}
}
The conditional statement within the loop is to ensure that only the actual days get displayed.
This works for my purposes, and the concept can be easily applied to other Enumerations. Specific implementation will need some changes, but you get the idea.
You can iterate through your Enum like this:
foreach (var val in typeof(DayOfWeek).GetEnumNames())
....
Also you can Pars and convert back a string to your Enum
var day = Enum.Parse(typeof(DayOfWeek) , yourString);
yourstring can be anything you want for example the name of the checkbox.
I don't know more about your problem and your project architecture, it's up to you to adapt these options to your problem.I hope this helps.
It's a fairly manual process... To check if Sunday is selected, for example:
bool isSunday = (dayOfWeekEnum & DayOfWeek.Sunday) == DayOfWeek.Sunday;

DateTime interval restriction in C#

The problem:
I am in process of implementing a scheduler for my advisor in school. The scheduler supposes to setup a 15 minutes interval time slot from 8:00 AM to 5:00 PM, Monday to Friday. In addition, the advisor will have to specify the start and end dates of the scheduler. The scheduler will also feature an option to specify if the 15 minutes time slot is not open. Meaning my advisor will be able to mark specific time slot as NOT AVAILABLE.
What I have so far:
I have created a simple class:
public class TimeSlot
{
public DateTime dateTime
{
get;
set;
}
public bool isAvailable
{
get;
set;
}
TimeSlot(DateTime dt, bool Avalible)
{
dateTime = dt;
isAvailable = Avalible;
}
}
The class basically represents an object for one time slot in the scheduler. I also have a list of time slots that keeps a list of the valid time slots:
List<TimeSlot> TSList = new List<TimeSlot>();
Note that a valid time slot means the following:
Date is within: Monday to Friday.
Time is within: 8:00 AM to 5:00 PM
Time slots are within: 15 minutes interval.
In addition, I have a method that fill in the TSList as the following:
private void button_Next_Click(object sender, RoutedEventArgs e)
{
/* Getting the values of fromDate and toDate from the GUI controls*/
DateTime fromDate = datePicker1.SelectedDate.Value;
DateTime toDate = datePicker2.SelectedDate.Value;
while (fromDate <= toDate)
{
/*This ensures that we only deal with days Monday to Friday*/
if (fromDate.DayOfWeek.ToString() != "Saturday" && fromDate.DayOfWeek.ToString() != "Sunday")
{
/*PROBLEM HERE!!*/
}
/*Updating fromDate: Incrementing fromDate by 1 day*/
fromDate = fromDate.AddDays(1);
}
}
Notes that I was only able to satisfy the first condition in my valid time slot conditions. Thus, I was only able to restrict the dates to be within Monday to Friday range.
The questions:
I am trying to achieve the missing two valid conditions for a time slot:
How to restrict the times to be only 8:00am to 5:00 pm?
How to make time slots separated by 15 minutes interval?
First, please use DayOfWeek.Saturday and DayOfWeek.Sunday for the comparision, converting to a string is not necessary...
Then just use a simple loop like
DateTime startSlot = fromDate.Date.AddHours(8); // Starts at 8:00AM
while (startSlot.Hour < 17) {
// Construct time slot class
startSlot = startSlot.AddMinutes(15);
}
This gives you startSlot values starting at 8:00am at every date ranging to 5pm (i.e. the last one is 4:45pm).
Why are you considering building this out of nothing?
Why are you not starting with one of the many calendar management programs that are available off the shelf? For example, Microsoft Outlook contains calendar and schedule management, and you can do all of what you describe, easily. It also integrates with other scheduling tools via .ICS files, it syncs with mobile devices, syncs with Google Calendar, and so on.
But there are lots of other options. Google Calendar is another obvious one.
I don't know why you would ever consider starting from scratch. Unless it's an academic exercise (and no, I don't mean that you work in academia), then you should use larger building blocks to start.
It's like building a structure, starting with sand and water, instead of pre-fabricated concrete block.
Just quick implementation. Let me know if you need some comments.
// Round interval
const int roundInterval = 15;
var remainder = fromDate.TimeOfDay.Minutes % roundInterval;
var curTime = remainder == 0 ? fromDate : fromDate.AddMinutes(roundInterval - remainder);
curTime = curTime.AddSeconds(-curTime.TimeOfDay.Seconds);
var delta = TimeSpan.FromMinutes(roundInterval);
while (curTime < toDate)
{
while (curTime.DayOfWeek == DayOfWeek.Saturday || curTime.DayOfWeek == DayOfWeek.Sunday)
{
curTime = curTime.Date.AddDays(1);
}
if (curTime.TimeOfDay.Hours < 8)
{
curTime = curTime.AddHours(8 - curTime.TimeOfDay.Hours);
curTime = curTime.AddMinutes(-curTime.TimeOfDay.Minutes);
continue;
}
if (curTime.TimeOfDay.Hours >= 17)
{
curTime = curTime.AddHours(24 - curTime.TimeOfDay.Hours);
curTime = curTime.AddMinutes(-curTime.TimeOfDay.Minutes);
continue;
}
TSList.Add(new TimeSlot(curTime, true));
curTime = curTime.Add(delta);
}
}
DateTime myScheduledTimeSlot = new DateTime(2010, 10, 26, 8, 45, 0);
// Use existing check to check day of week constraint...
// Check if the datetime falls on a correct minute boundary
switch (myScheduledTimeSlot.Minute)
{
case 0:
case 15:
case 30:
case 45:
// The time slot is valid
break;
default:
// The time slot is not valid
break;
}
It is pretty simple to check whether it falls in a 15 minute slot as you don't have weird boundaries keeping every hour identical. I'd recommend checking out Quart.NET if you want to save some time doing eventing/scheduling.

Categories

Resources