Перевод Anton Petrusevich <casus@mail.ru> и Alex Ott <ott@phtd.tpu.edu.ru>
И компьютерная литература и популярная пресса наполнены множеством статей о ``конце тысячелетия'' и ``Y2K'', некоторые из которых являются позитивными, а некоторые пугающими. Много людей спрашивают ``готов ли Perl к Y2K?'' Хорошо, вот то что говорит об этом документация на Perl:
Имеет ли Perl проблему 2000 года? Готов ли Perl к Y2K?
Короткий ответ: Нет, Perl не имеет проблемы 2000-го года. Да, Perl готов к Y2K. Программистов наняли для его использования, однако вы вероятно не используете его.
Длинный ответ: Perl готов к Y2K также как и ваш карандаш -- не больше и не меньше. Функции работы с временем и датами, входящие в библиотеку Perl (gmtime и localtime) предоставляют адекватную информацию для правильного определения года большего 2000 (2038 будет являться проблемой для 32-битных машин). Год, возвращаемый этими функциями при вызову в списочном контексте, равен текущему году минус 1900. Для годов между 1910 и 1999 это значение равно двухзначному десятичному числу. Для того, чтобы избежать проблемы 2000-года просто не используйте занчение года как двухзначное число. Оно не является таковым.
При использовании функций gmtime()
и localtime()
в
строковом контексте, они возвращают строку времени, которая содержит
полное значение года. Например: $timestamp =
gmtime(1005613200)
устанавливает $timestamp
равной
"Tue Nov 13 01:00:00 2001"
. Так что в этом случае
нет никакой проблемы 2000-года.
Это не значит, что Perl не может быть использован для создания не готовых к Y2K программ. Он может быть использован. Но точно также, как и ваш карандаш. Это будет ошибкой пользователя, но не языка. С риском вспыхивания NRA: ``Perl не ломает Y2K, это делают люди''. Для более длинного объяснения смотрите http://language.perl.com/news/y2k.html.
Так, мы получили ответ на вопрос. Но поскольку мы говорим о датах, то давайте рассмотрим несколько вещей, которые Perl умеет делать с датами.
Во первых, имеется три встроенных оператора времени
. Нет,
ни один из них не дает вам подписку на журнал Time --- просто
число (как беззнаковое 32-битное число) секунд с начала отсчета (1
Января 1970, 0000 GMT). В момент написания, оно достигло значения
примерно 900 миллионов и скоро пересечет отметку миллиард. Когда точно?
Мы можем использовать другую встроенную функцию Perl для перевода этого
времени в что-то такое, что может понять человек:
my $when = gmtime 1000000000;
print "timestamp turns a billion at $when\n";
Выполнив этот код я получил:
timestamp turns a billion at Sun Sep 9 01:46:40 2001
Ахх... так где-то в идее Артура Кларка о новом тысячелетии мы
получим новую цифру в значении возвращаемом time
. Я удивлюсь,
что программы дадут сбой, когда это случится.
Та строка, которая возвращается функцией gmtime
является
временем по GMT. Если вы не живете в Лондоне или в Западной Африке, то
вам будет удобней получать время соответственно вашей временной
зоне. Это делается легко: просто используйте функцию
localtime
:
my $now = localtime time;
print "It is now $now\n";
И gmtime
и localtime
возвращают строку похожую на
результат команды UNIX date, в тех случаях, когда они
вызываются в скалярном контексте, так как я выполнил это. Вы можете
также получить части даты, используя эти две функции в списочном
контексте:
my ($sec,$min,$hour,$mday,$mon,$year,
$wday,$yday,$isdst) = localtime time;
И это даст вам доступ к частям даты. Заметьте, что $mon
отсчитывается с 0 (вы наверное захотите добавить к ней 1), а
$year
является смещением с 1900 года. Да, она будет равен 100 в
2000 году, так что просто исправим это с помощью команды $year +=
1900
. Для дополнительной информации прочитайте perldoc -f
localtime
.
Для обратного преобразования есть другой способ, Perl поставляется
со стандартным модулем Time::Local
. (Документация доступна с
помощью команды perldoc Time::Local
, конечно если кто-то не
испортил ваш дистрибутив). Например, для получения значения с начала
отсчета для начала 2000 года в моей временной зоне, я могу использовать
следующий код:
use Time::Local;
my $time = timelocal(0,0,0,1,0,100);
my $string = localtime $time;
print "the big ball falls at $time => $string\n";
Здесь я печатаю заново преобразованную строку просто для того, чтобы проверить, что я выполняю правильное действие. Мы можем использовать этот код для получения обратного счетчика:
use Time::Local;
my $time = timelocal(0,0,0,1,0,100);
my $diff = $time - time;
$diff /= 86400; # seconds to days
if ($diff > 0) {
printf "we've got %.2f days to go\n", $diff;
} else {
print "we made it!\n";
}
Конечно, значение переменной $time
не изменяется между
запусками программы, так что лучше всего вычислить его один раз и
записать в программу. Но это хорошо для демонстрационной программы. Для
того, чтобы увидеть более тщательно разработанный обратный счетчик,
посетите мою домашнюю страницу по адресу
http://www.stonehenge.com/merlyn/.
Кроме встроенных функций и библиотек, также на CPAN существует
достаточное количество пакетов для работы с временем и датами. Наиболее
претенциозным из них является Date::Manip
, который вы можете
установить на свою систему (если он у вам еще не установлен) используя
инструкции описанные в perldoc perlmodinstall
. (Если у вас нет
perlmodinstall, то вы должны обновить свою версию Perl до
версии 5.005 или более поздней).
Как говорится в первом параграфе документации модуль
Date::Manip
:
Это набор подпрограмм, спроектированный для облегчения общих действий с датами и временем, Легко выполняются такие операции, как сравнение двух дат, вычисления диапазона между двумя датами или разбор дат записанных в национальном формате. С самогоDate::Manip
был нацелен на легкое выполнение ЛЮБЫХ операций с датами и временем, не обязательно быстро. Также, он ориентирован на выполнение тех операций, в каких терминах думаем мы (как люди), а не на операции выполняемые компьютерами. Также существуют другие модули, которые могут выполнять небольшое подмножество операций выполняемыхDate::Manip
более быстро, чем данный модуль, так что если скорость выполнения играет важную роль, то вы должны использовать что-то другое. Посмотрите на CPAN список модулей работы с датами и временем. Но я верю, что для получения гибких решенийDate::Manip
является лучшим выбором для вас.
Однако, как упомянуто в этом тексте, Date::Manip
является
достаточно тяжелым, только для загрузки занимая примерно пол секунды
CPU на моей мощной машине. Вероятно это нормально для программ
выполняющихся длительное время, но это очень много для коротко-живущих
приложений CGI.
Для многих приложений, когда не достаточно встроенных функций, вы
можете использовать достаточно быстрый модуль Date::Calc
(который также можно найти на the CPAN). Это модуль загружается (у
меня) примерно одну десятую секунды CPU, делая его удобным для
применения в коротко-живущих программах CGI или других часто
запускаемых утилитах.
В качестве примера возможностей Date::Calc
, я решил
эмулировать команду UNIX cal
, которая может выдавать календарь
на месяц или год на любое время от 1-го года до далекого будущего. К
сожалению, поскольку Date::Calc
не понимает Юлианский
календарь (адаптированный в 1752 в США), то я ограничу свою программу
самым недавним временем, но сохраняя тот же интерфейс командной строки.
В заключение моя программа стала выглядеть так:
#!/usr/bin/perl -w
use strict;
use Date::Calc qw(
Today Day_of_Week Days_in_Month
Month_to_Text Day_of_Week_Abbreviation
);
if (@ARGV) {
my $year = shift;
if (@ARGV) {
my $month = $year;
my $year = shift;
die "year $year must be > 1752"
unless $year > 1752;
&do_cal($year,$month);
} else {
die "year $year must be > 1752"
unless $year > 1752;
for my $month (1..12) {
&do_cal($year,$month);
print "\n" unless $month == 12;
}
}
} else {
my ($today_year, $today_month, $today_day)
= Today;
&do_cal($today_year, $today_month);
}
sub do_cal {
my $cal_year = shift;
my $cal_month = shift;
my $month_name = Month_to_Text($cal_month);
my $start_dow = Day_of_Week(
$cal_year,$cal_month,1
);
my $end_day = Days_in_Month(
$cal_year,$cal_month
);
my $curday = 1 - $start_dow % 7;
print "
$month_name $cal_year\n";
print map { sprintf " %4.4s ",
Day_of_Week_Abbreviation($_) } 7,1..6;
print "\n";
{
for ($curday..($curday+6)) {
if ($_ < 1) {
print "<<<<<<";
} elsif ($_ <= $end_day) {
printf "[ %2d ]", $_;
} else {
print ">>>>>>";
}
}
print "\n";
$curday += 7;
redo if $curday <= $end_day;
}
}
Здесь самыми заметными вещами является то, что я использую
Date::Calc
для получения сегодняшнего месяцы и года
(Today
), получение текстового имени месяца
(Month_to_Text
), нахождение дня недели (Day_of_Week
),
нахождение последнего дня месяца (Days_in_Month
) и даже
получение правильных имен для дней недели
(Day_of_Week_Abbreviation
).
Имея все эти подпрограммы очень легко написать программу. Что должно
бы сделать ее более легкой, если заметить, что почти все мои предыдущие
подпрограммы &do_cal
могли быть заменены подпрограммой
Date::Calc
с именем Calendar
, о которой я не знал,
пока почти не закончил программу.
Так, что важным советом является ``Не изобретайте колесо... прочитайте всю документацию''. В следующий раз я последую своему собственному совету. И если вы все еще думаете, что Perl не достаточно полезен, то теперь вы знаете как получить дату с помощью Perl или по крайней мере правильное время. Наслаждайтесь.