Перевод 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 или по крайней мере правильное время. Наслаждайтесь.