Introduction
The .NET DateTimePicker (DTP) control is quite good, until you need a nullable date. I recently came to a need for a nullable date and discovered this problem. A good example would be ShippedDate
. What should this property contain if the product has not shipped? On a related topic, the DateTime
structure doesn't support a null
value. .NET Beta's had a DateTime.Empty
value but this was removed. I've seen some workarounds with the DTP by using the checkbox to indicate a null value but I would expect some confusion from end users. The DateEdit control is the result of my efforts find a solution to this problem. It shows an empty format "__/__/____" when the value is null. The focus of this control is quick and direct data entry. It currently doesn't provide a drop down calendar, but I have an idea to build an different control for those who need more "eye candy".
Design Considerations
Invalid Dates
Stopping invalid date values from being entered can be difficult. For an example, try entering 2/29/2000 into the DTP. For this reason, I decided not to attempt any validation of the user typing other than allowing only numeric digits. This means that 03/45/____ could be entered into the DateEdit control. To handle this problem, I fire an InvalidDate
event from the OnLeave
event. This can be used to display a message, or change the back color, or whatever method you prefer to notify the user of invalid values.
Non Nullable DateTime Struct
The DateTime
structure does not allow Null values. After spending some time considering writing a NullableDateTime
structure, I decided against it. In order for this to work well, it would need to be an exact replacement for the standard DateTime
struct. Since a struct cannot be inherited, you will need to implement EVERY method contained in the DateTime
struct. One relatively straightforward solution to this problem is to define a new DateTime
constant NullValue
that equals MinValue
( 01/01/0001 ). This prevents use of MinValue
as a valid date but I think I can live with this.
Null Date Persistance
The next issue is how to handle NullValue
dates when they are persisted to your database. You could simply persist the 01/01/0001 to the database and remember that this value represents Null. I'm not fond of this solution since someone writing queries against the database might not know this. What would the SQL look like if I wanted to know which orders have not shipped?
-
Select ... Where ShippedDate Is Null
Select ... Where ShippedDate = 1/1/0001
I think the first SQL looks more logical. Also, if someone is looking at raw columnar data from the table they would see 1/1/0001, which looks like a real date. Null values would be more obvious.
Persisting NullValue
(1/1/0001) to the database does have the advantage that no translation to DBNull
is necessary.
Persisting Null dates means some additional work. A DateTime
struct that supported Null values and would convert to DBNull
automatically would be the ideal solution but since DateTime
cannot be null, we have to use a workaround. (Read more work for you) The workaround is to add code during database retrieval and persistence to convert NullValue
to DBNull
.
DateEdit Features
I have incorporated handling for international date formatting and performed "some" testing on this feature. Let me know if you find any bugs.
The DateEdit control is based on the MaskedEdit control. It uses 2 digits for month and day. This means February 5 must be entered 02/05. This adds a couple of sometimes unnecessary keystrokes during date entry.
The IsValid property returns true for valid dates or Null.
Use the Value property to get/set the DateTime
value. Note: 1/1/0001 or whatever you decide to use as the NullValue
is automatically converted to the blank mask. "__/__/____" depending on your regional settings.
Next Steps
The DateEdit control is designed for simple date entry. Addition of a drop down month calendar would make it more visually appealing. The month calendar can be handy if you're entering dates close to a known date such as +- 1 month from the current date. Entering a birth date in the DTP with any default date by using the month calendar could mean traversing 10, 20, 30 years, 1 month at a time. Not likely to be very fast. Adding a drop down month calendar would mean building a composite control based on the DateEdit, the built-in MonthCalendar and a down arrow button. Let me know what you think. The DateEditEx control would be separate so that if you don't need the added size and overhead of the drop down MonthCalendar then you don't have to use it.
The DateEdit control doesn't currently support time entry. My experience tells me that dates that require time values are best set internally from code. Time entry would add additional typing from the user and I wouldn't expect a high degree of accuracy.
This isn't a perfect solution, workable hopefully. Let me know your ideas for improvement.
History
- Initial Release: June 16th, 2003