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?

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.

History

Initial Release: June 16th, 2003

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

This caused me a HUGE headache. After loads of obscure attempts to get around this, I ended up.....

1) Created my own control using a textbox control, a button and a calendar to substitute the crappy DTP control
2) I was using databinding, and the dataset datetime data type also doesn't support nulls, so I databound to the tag property of my control (which inherits the textbox control) so that I could leave the text of the textbox blank.
3) My control used a timer to take value of 'text' property into the 'tag' property to which I'd bound my dataset, after first checking to see if text was blank or not. If it was, it would put 31/12/1900 to the tag property instead. When the dataadapters update method was called, 31/12/1900 would be sent to the db in place of the NULL.
4) Created a TSQL script which would loop through every column in every table in my database, check to see if it was a datetime sql data type, and if it was produce an "Instead Of Update" trigger and an "Instead Of Insert" trigger to transpose any dates of 31/12/1900 into null values before doing the insert/update.

and this is the abbreviated account. Wot a Nightmare!!!!!! Please, Please MIcrosoft, let me wake up and see that it was all a dream, and I haven't really lost tens of hours of development time