Стандартный класс 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/