Стандартный класс DateTime создает массу проблем в повседневной работе из-за того, что он не поддерживает пустых значений. На практике существует масса ситуаций, в которых дата чего бы то ни было неизвестна и именно этот факт надо обрабатывать и отображать на экране, а не некое минимально значение. На уровне базы данных дело обстоит проще - в любой колонке может быть значение null. Стандартный DateTime в C# вообще не может быть равен null. Но даже если мы используем введенную в .net 2.0 обертку Nullable<T> и таки приравняем дату к null, мы все равно не сможем с ней нормально работать. Почему?
- при выводе на экран для пустой даты надо выводить пустую строку
- при вводе даты ее надо проверять на корректность, причем пустая дата будет правильной
- надо сравнивать даты и сортировать их
- при записи и чтении из базы данные мы будем получать пустые даты не в виде null, а в виде DBNull.Value
- надо проверять, не находится ли дата в будущем
В итоге мы получаем огромное количество самых разнообразных проверок и условий с постоянными конвертациями в строки и обратно. Самым тупым и простым выходом из ситуации является хранение дат внутри программы в виде строк, пустая строка - пустая дата. Данный подход уменьшает количество проверок и ветвлений в рутинных операциях, но не решает проблему полностью и добавляет ряд новых проблем, требующих постоянной конвертации строк в DateTime.
Остается только одно - писать собственный класс даты, реализующих недостающую или криво сделанную функциональность. Можно только догадываться, о чем думали высокооплачиваемые и мегапрофессиональные программисты Microsoft, делая непригодный для использования в реальной жизни класс даты. Для любителей умных слов нижеприведенный класс будет реализовывать шаблон проектирования Декоратор.
Декорировать мы будем nullable DateTime, добавляя к ней недостающие функции и возможности. А именно
- возможность простой конвертации в строки для вывода с автоматической проверкой на пустоту
- возможность создания из строк и DateTime
- более простые и удобные служебные функции для сравнения дат и записи в базу данных
Интерфейс класса
- три конструктора - пустой, из строки из DateTime, ячейки DataTable
DatePlus()
DatePlus(DateTime startDateTime)
DatePlus(object dbDate)
- самоочевидно свойство Empty
- свойства, конвертирующие в наиболее частоиспользуемые типы строк, включая универсальную строку даты для MS SQL сервера
SqlDateString
- object свойство для записи в БД, содержащее либо DateTime либо DBNull.Value
- функции для сравнения After, Before, Equal возвращающие false при сравнении с пустой датой
- проверка на дату в будущем InFuture
- статическая проверка строки на правильность ее как даты DateCorrect
// расширенный класс дат, поддержка пустых значений, дополнительные функции public class DatePlus { private DateTime? _nullableDateTimeBase = null; public DatePlus() { } public DatePlus(DateTime startDateTime) { _nullableDateTimeBase = startDateTime.Date; } public DatePlus(string startDateTimeStr) { if (startDateTimeStr != "") { _nullableDateTimeBase = Convert.ToDateTime(startDateTimeStr).Date; } } // создание из ячейки DataTable, прочитанной из базы public DatePlus(object dbDate) { if (dbDate != DBNull.Value) { _nullableDateTimeBase = Convert.ToDateTime(dbDate); } } //The pointer for this method was null. public bool Empty { get { return !_nullableDateTimeBase.HasValue; } } public bool InFuture { get { bool inFuture = false; // проверка на будущее имеет смысл только для непустых дат if (_nullableDateTimeBase == null) { return inFuture; } DateTime nowDateTime = DateTime.Now; int compareCode = _nullableDateTimeBase.Value.CompareTo(nowDateTime); //_nullableDateTimeBase наступает позже nowDateTime if (compareCode > 0) { inFuture = true; } return inFuture; } } public DateTime DateTime { get { if (_nullableDateTimeBase != null) { return _nullableDateTimeBase.Value; } else { throw new Exception("Невозможно вернуть DateTime так как дата пустая."); } } } // строка даты для ms sql не зависящая от региональных настроек public string SqlDateString { get { string universalSqlString = ""; if (!_nullableDateTimeBase.HasValue) { throw new Exception("Невозможно создать универсальную строку даты для MS Sql из пустой даты!"); } universalSqlString = _nullableDateTimeBase.Value.ToString("yyyyMMdd"); return universalSqlString; } } public string MonthFullYear { get { string monthFullYear = ""; if (_nullableDateTimeBase != null) { monthFullYear = System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(_nullableDateTimeBase.Value.Month) + " " + _nullableDateTimeBase.Value.Year; } return monthFullYear; } } // DateTime или DBNull.Value public object DBDateObject { get { object dbDateObj = null; if (_nullableDateTimeBase.HasValue) { dbDateObj = _nullableDateTimeBase.Value; } else { dbDateObj = DBNull.Value; } return dbDateObj; } } public string ShortString { get { string shortString = ""; if (_nullableDateTimeBase != null) { shortString = _nullableDateTimeBase.Value.ToShortDateString(); } return shortString; } } public string LongString { get { string longString = ""; if (_nullableDateTimeBase != null) { longString = _nullableDateTimeBase.Value.ToLongDateString(); } return longString; } } public bool After(DatePlus dateToCompare) { bool after = false; if (this.Empty || dateToCompare.Empty) { return after; } int compareCode = _nullableDateTimeBase.Value.CompareTo(dateToCompare.DateTime); //_nullableDateTimeBase наступает позже dateToCompare if (compareCode > 0) { after = true; } return after; } public bool Before(DatePlus dateToCompare) { bool before = false; if (this.Empty || dateToCompare.Empty) { return before; } int compareCode = _nullableDateTimeBase.Value.CompareTo(dateToCompare.DateTime); //_nullableDateTimeBase наступает раньше if (compareCode < 0) { before = true; } return before; } public bool Equal(DatePlus dateToCompare) { bool equal = false; if (this.Empty || dateToCompare.Empty) { return equal; } int compareCode = _nullableDateTimeBase.Value.CompareTo(dateToCompare.DateTime); if (compareCode == 0) { equal = true; } return equal; } public string Year { get { string year = ""; if (_nullableDateTimeBase != null) { year = _nullableDateTimeBase.Value.Year.ToString(); } return year; } } public override string ToString() { return ShortString; } public static bool DateCorrect(string dateString) { bool dateCorrect = true; if (dateString == "") { return dateCorrect; } try { Convert.ToDateTime(dateString); } catch { dateCorrect = false; } return dateCorrect; } // end class DatePlus }
Во второй части статьи речь пойдет о переделке стандартного DateTimePicker под возможность ввода пустой даты и просто ввода даты строкой, многие пользователи считают это более удобным.
Приветствую! А где вторая часть статьи?
Здесь
http://nullpro.info/2012/composite-control-winforms/