MySQL / 10. Импорт и экспорт данных

Проверка корректности составляющих даты и времени

Задача
Строка сравнивалась с образцом и оказалась похожа на дату или время, но вы хотите выполнить дополнительную проверку, чтобы убедиться в том, что строка допустима.


Решение
Разбейте значение на части и выполните для каждой составляющей проверку на вхождение в соответствующий диапазон.

Обсуждение
Сравнения с образцом может оказаться недостаточно для проверки значений дат и времени. Например, значение типа 1947-15-19 может соответствовать образцу даты, но если вставить такое значение в столбец DATE, MySQL преобразует его в 0000-00-00. Если вы хотите узнать, корректно ли значение, до того, как оно попадет в базу данных, используйте сравнение с образцом в сочетании с проверкой на принадлежность диапазону.

Чтобы убедиться в правильности даты, разбейте ее на составляющие года, месяца и дня и проверьте, входят ли они в соответствующие диапазоны. Год должен быть меньше 9999 (в MySQL верхняя граница дат равна 9999-12-31), значение месяца должно быть от 1 до 12, а дни должны входить в диапазон от 1 до количества дней в данном месяце. Последнее условие несколько сложнее, так как зависит от месяца, кроме того, для февральских дат оно зависит еще и от года (високосный он или нет).Предположим, что вы проверяете входные даты в формате ISO. Ранее в рецепте 10.25 мы использовали функцию is_iso_date() из библиотечного файла Cookbook_Utils.pm для выполнения сравнения строки даты с образцом и разбиения ее на составляющие:

my $ref = is_iso_date ($val);
if (defined ($ref))
{
# $val соответствует образцу формата ISO;
# проверять составляющие, используя $ref->[0] - $ref->
}
else
{
# $val не соответствует образцу формата ISO
}

Функция is_iso_date() возвращает undef, если значение не совпадает с образцом формата даты ISO. В противном случае она возвращает ссылку на массив, содержащий значения года, месяца и дня.1 Чтобы выполнить дополнительную проверку составляющих даты, передайте их другой библиотечной функции, is_valid_date():

$valid = is_valid_date ($ref->[0], $ref->, $ref->);
Или, если короче:
$valid = is_valid_date (@{$ref});
Функция is_valid_date() проверяет составляющие даты так:
sub is_valid_date
{
my ($year, $month, $day) = @_;
# год должен быть неотрицательным, месяц и год - положительными
return (0) if $year < 0 || $month < 1 || $day < 1;
# проверка допустимых границ составляющих
return (0) if $year > 9999;
return (0) if $month > 12;
return (0) if $day > days_in_month ($year, $month);
return (1);
}

Для is_valid_date() требуется не строка даты, а отдельные значения года, месяца и дня. Поэтому прежде чем вызывать функцию, вам необходимо разбить тестируемые значения на составляющие. Такое требование делает функцию более универсальной. Например, ее можно использовать для проверкидат типа 12 February 2003, если перед вызовом is_valid_date() сопоставить названию месяца его номер. Если бы is_valid_date() принимала строковый аргумент, он должен был бы иметь какой-то предопределенный формат, и функция стала бы более узко специализированной.

Для вычисления количества дней месяца, представленного датой, функция is_valid_date() применяет вспомогательную функцию days_in_month(). Функция days_in_month() требует в качестве аргументов и год, и месяц, так как если месяц – февраль (2), то количество его дней зависит от того, високосный ли год. То есть необходимо передавать значение года в четырехзначном формате. Корректно определить високосный год в двузначном значении невозможно, как это было показано в рецепте 5.27. Функции days_in_month() и is_leap_year() базируются на приемах, взятых непосредственно оттуда:

sub is_leap_year
{
my $year = shift;
return (($year % 4 == 0) && ((($year % 100) != 0) || ($year % 400) == 0));
}
sub days_in_month
{
my ($year, $month) = @_;
my @day_tbl = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
my $days = $day_tbl[$month-1];
# добавить один день для февраля високосного года
$days++ if $month == 2 && is_leap_year ($year);
return ($days);
}

Чтобы выполнить проверку корректности значений времени, можно использовать аналогичную процедуру, изменив диапазоны для составляющих: от 0 до 24 для часов и от 0 до 59 для минут и секунд. Функция is_24hr_time() преобразует значения в 24-часовой формат:

sub is_24hr_time
{
my $s = shift;
return undef unless $s =~ /^(\d{1,2})\D(\d{2})\D(\d{2})$/;
return [ $1, $2, $3 ]; # вернуть часы, минуты, секунды
}

Следующая функция is_ampm_time() ищет значения времени в 12-часовом формате с необязательным суффиксом AM или PM, преобразуя время после полудня (PM) к 24-часовому формату:

sub is_ampm_time
{
my $s = shift;
return undef unless $s =~ /^(\d{1,2})\D(\d{2})\D(\d{2})(?:\s*(AM|PM))?$/i;my ($hour, $min, $sec) = ($1, $2, $3);
$hour += 12 if defined ($4) && uc ($4) eq "PM";
return [ $hour, $min, $sec ]; # вернуть часы, минуты, секунды
}

Обе функции возвращают undef для значений, не совпадающих с образцом. В противном случае они возвращают ссылку на трехэлементный массив, содержащий часы, минуты и секунды.

Статьи по MySQL на эту тему:

Диагностическая утилита для LOAD DATA
Импорт XML в MySQL
Использование временных таблиц для преобразования дат
Обмен данными между MySQL и FileMaker Pro
Обмен данными между MySQL и Microsoft Access

Вернуться в раздел: MySQL / 10. Импорт и экспорт данных