Пустая дата в C# - часть 1, собственный класс даты

Стандартный класс 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 под возможность ввода пустой даты и просто ввода даты строкой, многие пользователи считают это более удобным.


Комментарии

Пустая дата в C# - часть 1, собственный класс даты — Комментарии (2)

Добавить комментарий для Ведомир Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *


*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>