Создание утилит для обработки дат

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

Решение
В этом разделе приведено несколько примеров таких утилит.

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

• isoize_date.pl читает файл, отыскивая даты в формате, принятом в США (MM-DD-YY) и преобразует их в формат ISO.

• cvt_date.pl преобразует даты в/из формата ISO и форматов дат, принятых в США и Великобритании. Это более универсальная программа, чем isoize_ date.pl, но она требует указать, какой ввод вы ожидаете получить и какой вывод следует формировать.

• monddccyy_to_iso.pl ищет даты типа Feb. 6, 1788 и преобразует их в формат ISO. Программа показывает, как отображать даты с нечисловыми составляющими в формат, понятный MySQL.

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


(Используйте cvt_file.pl для работы с файлами в другом формате.)

Наша первая утилита обработки дат, isoize_date.pl, ищет даты в формате, принятом в США, и преобразует их в формат ISO. Вы должны заметить, что в ее основе лежит общий цикл обработки ввода, к которому добавлено немного кода для выполнения конкретного преобразования:

#! /usr/bin/perl -w
# isoize_date.pl – Читает входные данные, ищет значения, соответствующие образцу
# даты, преобразует их в формат ISO. Кроме того преобразует двузначные года
# в четырехзначные, используя точку перехода 70.
# По умолчанию ищет даты в формате MM-DD-[CC]YY.# Предполагает, что строки ввода разделены символами табуляции,
# признак конца строки – перевод строки.
# Не проверяет, действительно ли допустимы даты
# (например, не будет жаловаться на значение 13-49-1928).
use strict;
# точка перехода, в которой двузначные года рассматриваются как 19XX
# (более ранние рассматриваются как 20XX)
my $transition = 70;
while (<>)
{
chomp;
my @val = split (/\t/, $_, 10000); # разбить, сохраняя все поля
for my $i (0 ..


@val - 1)
{
my $val = $val[$i];
# искать строки в формате MM-DD-[CC]YY
next unless $val =~ /^(\d{1,2})\D(\d{1,2})\D(\d{2,4})$/;
my ($month, $day, $year) = ($1, $2, $3);
# чтобы интерпретировать даты как DD-MM-[CC]YY,
# заменить предыдущую строку следующей:
#my ($day, $month, $year) = ($1, $2, $3);
# преобразовать двузначный год в четырехзначный,
# затем обновить значение в массиве
$year += ($year >= $transition ? 1900 : 2000) if $year < 100;
$val[$i] = sprintf ("%04d-%02d-%02d", $year, $month, $day);
}
print join ("\t", @val) . "\n";
}
exit (0);

Если вы подаете на вход isoize_date.pl такой файл:

Fred 04-13-70
Mort 09-30-69
Brit 12-01-57
Carl 11-02-73
Sean 07-04-63
Alan 02-14-65
Mara 09-17-68
Shepard 09-02-75
Dick 08-20-52
Tony 05-01-60

то будет сформирован следующий вывод:

Fred 1970-04-13
Mort 2069-09-30
Brit 2057-12-01
Carl 1973-11-02
Sean 2063-07-04Alan 2065-02-14
Mara 2068-09-17
Shepard 1975-09-02
Dick 2052-08-20
Tony 2060-05-01

Сценарий isoize_date.pl имеет специальное назначение.


Он конвертирует только формат дат США в формат ISO. Он не выполняет проверку корректности составляющих даты и не позволяет задать точку перехода для добавления века. Более универсальное средство было бы более полезным. Следующий сценарий, cvt_date.pl, расширяет возможности isoize_date.pl; он распознает даты в формате ISO, США и Великобритании и преобразует любой из этих форматов в любой другой. Кроме того, сценарий конвертирует двузначные года в четырехзначные, позволяя указать точку перехода, и может выводить предупреждения о неподходящих датах. Поэтому его можно использовать для предварительной обработки ввода перед загрузкой в MySQL или для пост-обработки данных, экспортированных из MySQL для использования в других программах.

Сценарий cvt_date.pl распознает следующие опции:

--iformat=формат, --oformat=формат, --format=формат,

Установить формат даты для ввода, вывода или ввода и вывода. Значение формат по умолчанию – iso; кроме того, cvt_date.pl воспринимает любую строку, начинающуюся с us или br, как задающую формат даты США или Великобритании соответственно.

--add-century
Преобразовать двузначные значения года в четырехзначные.

--columns=список_столбцов
Преобразовать даты только в указанных столбцах.


По умолчанию cvt_date.pl ищет даты во всех столбцах. Если опция указана, то список_столбцов –это список из одной или более позиций столбцов, разделенных запятыми.

Нумерация позиций начинается с 1.

--transition=n
Установить точку перехода для преобразования двузначных значений годов в четырехзначные. Значение по умолчанию равно 70. Эта опция включает --add-century.

--warn
Предупреждает о плохих датах. (Обратите внимание на то, что эта опция может выводить ложные предупреждения, если даты имеют двузначные составляющие года, а опция --add-century не указана, так как в этом случае проверка на високосный год не всегда будет корректной.)

Я не буду приводить здесь код cvt_date.pl (его большая часть посвящена обработке опций командной строки), если хотите, можете сами просмотреть исходные тексты. Для того чтобы посмотреть, как cvt_date.pl работает, предположим, что у вас есть файл newdata.txt с таким содержимым:

name1 01/01/99 38
name2 12/31/00 40
name3 02/28/01 42
name4 01/02/03 44

Запустим для этого файла cvt_date.pl с опциями, указывающими, что даты должны быть в формате США, а к годам должен быть добавлен век:

% cvt_date.pl --iformat=us --add-century newdata.txt

name1 1999-01-01 38
name2 2000-12-31 40
name3 2001-02-28 42
name4 2003-01-02 44

Чтобы вывести даты в формате, принятом в Великобритании, без преобразования года:

% cvt_date.pl --iformat=us --oformat=br newdata.txt

name1 01-01-99 38
name2 31-12-00 40
name3 28-02-01 42
name4 02-01-03 44

Сценарий cvt_date.pl не имеет информации о содержимом каждого столбца.


Если у вас есть столбец со значениями, которые соответствуют образцу, но не являются датами, он перезапишет и этот столбец. Чтобы избежать подобных ситуаций, используйте опцию --columns для определения того, на какие столбцы должен воздействовать cvt_date.pl.
Сценарии isoize_date.pl и cvt_date.pl работают с датами, записанными в любом числовом формате. Но в файлах данных даты часто представлены подругому, и может потребоваться создание специального сценария для их обработки. Предположим, что файл ввода содержит даты в следующем формате (это даты вступления отдельных штатов в США):

Delaware Dec. 7, 1787
Pennsylvania Dec 12, 1787
New Jersey Dec. 18, 1787
Georgia Jan. 2, 1788
Connecticut Jan. 9, 1788
Massachusetts Feb. 6, 1788
Maryland Apr. 28, 1788
South Carolina May 23, 1788
New Hampshire Jun. 21, 1788
Virginia Jun 25, 1788
...

Даты состоят из трехсимвольного сокращенного названия месяца (за которым может следовать точка), дня месяца в числовом формате, запятой и года, тоже в числовом формате. Чтобы импортировать такой файл в MySQL, необходимо преобразовать даты в формат ISO и получить такой файл:

Delaware 1787-12-07
Pennsylvania 1787-12-12New Jersey 1787-12-18
Georgia 1788-01-02
Connecticut 1788-01-09
Massachusetts 1788-02-06
Maryland 1788-04-28
South Carolina 1788-05-23
New Hampshire 1788-06-21
Virginia 1788-06-25
...

Это весьма специфическое преобразование, хотя тип задачи (преобразование специального формата даты) достаточно распространен. Чтобы выполнить преобразование, идентифицируйте даты как значения, совпадающие с соответствующим образцом, затем сопоставьте названиям месяцев их номера и переформатируйте результат. Рассмотрим сценарий monddccyy_to_iso.pl, который показывает, как все это сделать:

#! /usr/bin/perl -w
# monddccyy_to_iso.pl – преобразование дат из формата mon[.] dd, ccyy в ISO
# Предполагается, что строки ввода разделены символами табуляции,
# а признак конца строки – перевод строки.
use strict;
my %map = # сопоставить названию месяца его номер
(
"jan" => 1, "feb" => 2, "mar" => 3, "apr" => 4, "may" => 5, "jun" => 6,
"jul" => 7, "aug" => 8, "sep" => 9, "oct" => 10, "nov" => 11, "dec" => 12
);
while (<>)
{
chomp;
my @val = split (/\t/, $_, 10000); # разбить на части, сохраняя все поля
for my $i (0 .. @val - 1)
{
# переформатировать значение, если оно совпадает с образцом, иначе считать,
# что это не дата в нужном формате и не обрабатывать ее
if ($val[$i] =~ /^([^.]+)\.? (\d+), (\d+)$/)
{
# использовать название месяца в нижнем регистре
my ($month, $day, $year) = (lc ($1), $2, $3);
if (exists ($map{$month}))
{
$val[$i] = sprintf ("%04d-%02d-%02d", $year, $map{$month}, $day);
}
else
{
# предупреждать, но не переформатировать
warn "$val[$i]: bad date?\n";
}
}
}print join ("\t", @val) . "\n";
}
exit (0);

Сценарий выполняет только переформатирование, не проверяя корректность дат. Для проверки корректности сделайте так, чтобы сценарий использовал модуль Cookbook_Utils.pm: добавьте после строки use strict такое предложение:

use Cookbook_Utils;

Тогда сценарий получит доступ к программе модуля – is_valid_date(). Чтобы использовать ее, измените раздел сценария, в котором выполняется переформатирование, следующим образом:

if (exists ($map{$month})
&& is_valid_date ($year, $map{$month}, $day))
{
$val[$i] = sprintf ("%04d-%02d-%02d",
$year, $map{$month}, $day);
}
else
{
# предупреждать, но не переформатировать
warn "$val[$i]: bad date?\n";
}

Оцените статью: (0 голосов)
0 5 0

Статьи из раздела MySQL на эту тему:
Диагностическая утилита для LOAD DATA
Извлечение и перестановка столбцов файлов данных
Импорт XML в MySQL
Импорт с помощью LOAD DATA и утилиты mysqlimport
Импорт файлов в формате CSV

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