Книга: Основы программирования в Linux

Время и дата

Время и дата

Программе часто полезно иметь возможность определить время и дату. Возможно, она хочет зарегистрировать длительность собственного выполнения или ей нужно изменять свое поведение в определенные моменты времени. Например, игра может отказываться запускаться в рабочие часы или программа резервного копирования по расписанию хочет дождаться ранних часов, прежде чем начать резервное копирование в автоматическом режиме.

Примечание

Во всех системах UNIX применяется одна и та же точка отсчета времени и дат: полночь по Гринвичу (GMT) на 1 января 1970 г. Это "начало эпохи UNIX", и ОС Linux — не исключение. Время в системе Linux измеряется в секундах, начиная с этого момента времени. Такой способ обработки аналогичен принятому в системе MS-DOS за исключением того, что эпоха MS-DOS началась в 1980 г. В других системах применяют точки отсчета иных эпох.

Время задается с помощью типа time_t. Это целочисленный тип, достаточный для хранения дат и времени в секундах. В Linux-подобных системах это тип long integer (длинное целое), определенный вместе с функциями, предназначенными для обработки значений времени, в заголовочном файле time.h.

Примечание

Не думайте, что для хранения времени достаточно 32 битов. В системах UNIX и Linux, использующих 32-разрядный тип time_t, временное значение "будет превышено" в 2038 г. Мы надеемся, что к тому времени системы перейдут на тип time_t, содержащий более 32 битов. Недавнее широкое внедрение 64-разрядных процессоров превращает это практически в неизбежность.

#include <time.h>
time_t time(time_t *tloc);

Вы можете найти низкоуровневое значение времени, вызвав функцию time, которая вернет количество секунд с начала эпохи (упражнение 4.6). Она также запишет возвращаемое значение по адресу памяти, на который указывает параметр tloc, если он — непустой указатель.

Упражнение 4. Функция time

Далее для демонстрации функции time приведена простая программа envtime.c.

#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
 int i;
 time_t the_time;
 for (i = 1; i <= 10; i++) {
  the_time = time((time_t *)0);
  printf("The time is %ldn", the_time);
  sleep(2);
 }
 exit(0);
}

Когда вы запустите программу, она будет выводить низкоуровневое значение времени каждые 2 секунды в течение 20 секунд.

$ ./anytime
The time is 1179643852
The time is 1179643854
The time is 1179643856
The time is 1179643858
The time is 1179643860
The time is 1179643862
The time is 1179643864
The time is 1179643866
The time is 1179643868
The time is 1179643870

Как это работает

Программа вызывает функцию time с пустым указателем в качестве аргумента, которая возвращает время и дату как количество секунд. Программа засыпает на две секунды и повторяет вызов time в целом 10 раз.

Использование времени и даты в виде количества секунд, прошедших с начала 1970 г., может быть полезно для измерения длительности чего-либо. Вы сможете сосчитать простую разность значений, полученных из двух вызовов функции time. Однако комитет, разрабатывавший стандарт языка ISO/ANSI С, в своих решениях не указал, что тип time_t будет применяться для определения произвольных интервалов времени в секундах, поэтому была придумана функция difftime, которая вычисляет разность в секундах между двумя значениями типа time_t и возвращает ее как величину типа double:

#include <time.h>
double difftime(time_t time1, time_t time2);

Функция difftime вычисляет разницу между двумя временными значениями и возвращает величину, эквивалентную выражениювремя1–время2, как число с плавающей точкой. В ОС Linux значение, возвращаемое функцией time, — это количество секунд, которое может обрабатываться, но для максимальной переносимости следует применять функцию difftime.

Для представления времени и даты в более осмысленном (с человеческой точки зрения) виде мы должны преобразовать значение времени в понятные время и дату. Для этого существуют стандартные функции.

Функция gmtime подразделяет низкоуровневое значение времени на структуру, содержащую более привычные поля:

#include <time.h>
struct tm *gmtime(const time_t timeval)

В структуре tm, как минимум, определены элементы, перечисленные в табл. 4.2.

Таблица 4.2

Элемент tm Описание
int tm_sec Секунды, 0–61
int tm_min Минуты, 0–59
int tm_hour Часы, 0–23
int tm_mday День в месяце, 1–31
int tm_mon Месяц в году, 0–11 (January (январь) соответствует 0)
int tm_year Годы, начиная с 1900 г.
int tm_wday День недели, 0–6 (Sunday (воскресенье) соответствует 0)
int tm_yday День в году, 0–365
int tm_isdst Действующее летнее время

Диапазон элемента tm_sec допускает появление время от времени корректировочной секунды или удвоенной корректировочной секунды.

Выполните упражнение 4.7.

Упражнение 4.7. Функция gmtime

Далее приведена программа gmtime.с, выводящая текущие время и дату с помощью структуры tm и функции gmtime.

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
 struct tm *tm_ptr;
 time_t the_time;
 (void)time(&the_time);
 tm_ptr = gmtime(&the_time);
 printf("Raw time is %ldn", the_time);
 printf("gmtime gives:n");
 printf("date: %02d/%02d/%02dn",
tm_ptr->tm_year, tm_ptr->tm_mon+1, tm_ptr->tm_mday);
 printf("time: %02d:%02d:%02dn",
  tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec);
 exit(0);
}

Выполнив эту программу, вы получите хорошее соответствие текущим времени и дате:

$ ./gmtime; date
Raw time is 1179644196
gmtime gives:
date: 107/05/20
time: 06:56:36
Sun May 20 07:56:37 BST 2007

Как это работает

Программа вызывает функцию time для получения машинного представления значения времени и затем вызывает функцию gmtime для преобразования его в структуру с удобными для восприятия значениями времени и даты. Она выводит на экран полученные значения с помощью функции printf. Строго говоря, выводить необработанное значение времени таким способом не следует, потому что наличие типа длинного целого не гарантировано во всех системах. Если сразу же после вызова функции gmtime выполнить команду date, можно сравнить оба вывода.

Но здесь у вас возникнет небольшая проблема. Если вы запустите эту программу в часовом поясе, отличном от Greenwich Mean Time (время по Гринвичу) или у вас действует летнее время, как у нас, вы заметите, что время (и, возможно, дата) неправильное. Все дело в том, что функция gmtime возвращает время по Гринвичу (теперь называемое Universal Coordinated Time (всеобщее скоординированное время) или UTC). Системы Linux и UNIX поступают так для синхронизации всех программ и систем в мире. Файлы, созданные в один и тот же момент в разных часовых поясах, будут отображаться с одинаковым временем создания. Для того чтобы посмотреть местное время, следует применять функцию localtime.

#include <time.h>
struct tm *localtime(const time_t *timeval);

Функция localtime идентична функции gmtime за исключением того, что она возвращает структуру, содержащую значения с поправками на местный часовой пояс и действующее летнее время. Если вы выполните программу gmtime, но замените все вызовы функции gmtime на вызовы localtime, в отчете программы вы увидите правильные время и дату.

Для преобразования разделенной на элементы структуры tm в общее внутреннее значение времени можно применить функцию mktime:

#include <time.h>
time_t mktime(struct tm *timeptr);

Функция mktime вернет -1, если структура не может быть представлена как значение типа time_t.

Для вывода программой date "дружественных" (в противоположность машинному) времени и даты можно воспользоваться функциями asctime и ctime:

#include <time.h>
char *asctime(const struct tm *timeptr);
char *ctime(const time_t *timeval);

Функция asctime возвращает строку, представляющую время и дату, заданные tm-структурой timeptr. У возвращаемой строки формат, подобный приведенному далее:

Sun Jun  9 12:34:56 2007n

У нее всегда фиксированный формат длиной 26 символов. Функция ctime эквивалентна следующему вызову:

asctime(localtime(timeval))

Она принимает необработанное машинное значение времени и преобразует его в местное время.

А теперь выполните упражнение 4.8.

Упражнение 4.8. Функция ctime

В этом примере благодаря приведенному далее программному коду вы увидите функцию ctime в действии.

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
 time_t timeval;
 (void)time(&timeval);
 printf ("The date is: %s", ctime(&timeval));
 exit(0);
}

Откомпилируйте и затем запустите на выполнение ctime.c, и вы увидите нечто похожее на приведенные далее строки:

$ ./ctime
The date is: Sat Jun 9 08:02:08 2007.

Как это работает

Программа ctime.c вызывает функцию time для получения машинного значения времени и дает возможность функции ctime выполнить всю тяжелую работу по преобразованию этого значения в удобочитаемую строку, которую потом и выводит на экран.

Для лучшего управления точным форматированием времени и даты ОС Linux и современные UNIX-подобные системы предоставляют функцию strftime. Она довольно похожа на функцию sprintf для дат и времени и действует аналогичным образом:

#include <time.h>
size_t strftime(char *s, size_t maxsize, const char *format, struct tm *timeptr);

Функция strftime форматирует время и дату, представленные в структуре tm, на которую указывает параметр, timeptr, и помещает результат в строку s. Эта строка задается длиной maxsize (как минимум) символов. Строка format применяется для управления символами, записываемыми в строку. Как и в функции printf, она содержит обычные символы, которые будут переданы в строку, и спецификаторы преобразований для форматирования элементов времени и даты. В табл. 4.3 перечислены используемые спецификаторы преобразований.

Таблица 4.3

Спецификатор преобразования Описание
%a Сокращенное название дня недели
Полное название дня недели
%b Сокращенное название месяца
%B Полное название месяца
%c Дата и время
%d День месяца, 01–31
%H Час, 00–23
%I Час по 12-часовой шкале, 01–12
%j День в году, 001–366
%m Номер месяца в году, 01–12
%M Минуты, 00–59
%p a.m. (до полудня) или p.m. (после полудня)
%S Секунды, 00–59
%u Номер дня недели, 1–7 (1 соответствует понедельнику)
%U Номер недели в году, 01–53 (воскресенье — первый день недели)
%V Номер недели в году, 01–53 (понедельник — первый день недели)
%w Номер дня недели, 0–6 (0 соответствует воскресенью)
%x Дата в региональном формате
%X Время в региональном формате
%y Номер года, меньший 1900
%Y Год
%Z Название часового пояса
%% Символ %

Таким образом, обычная дата, такая же, как полученная из программы date, соответствует следующей строке формата функции strftime:

"%a %b %d %Н: %М: %S %Y"

Для облегчения чтения дат можно использовать функцию strptime, принимающую строку с датой и временем и формирующую структуру tm с теми же датой и временем:

#include <time.h>
char *strptime(const char *buf, const char *format, struct tm *timeptr);

Строка format конструируется точно так же, как одноименная строка функции strftime. Функций strptime действует аналогично функции sscanf: она сканирует строку в поиске опознаваемых полей и записывает их в переменные. В данном случае это элементы структуры tm, которая заполняется в соответствии со строкой format. Однако спецификаторы преобразований для strptime немного мягче спецификаторов функции strftime. Так, в функции strptime разрешены как сокращенные, так и полные названия дней и месяцев. Любое из этих представлений будет соответствовать спецификатору %a функции strptime. Кроме того, в то время как функция strftime для представления чисел, меньших 10, всегда применяет ведущие нули, strptime считает их необязательными.

Функция strptime возвращает указатель на символ, следующий за последним, обработанным в процессе преобразования. Если она встречает символы, которые не могут быть преобразованы, в этой точке преобразование просто прекращается. Для того чтобы убедиться в том, что в структуру tm записаны значимые данные, вызывающей программе следует проверять, достаточно ли символов строки принято и обработано.

Рассмотрим работу функций на примере (упражнение 4.9).

Упражнение 4.9. Функции strftime и strptime

Обратите внимание на выбор спецификаторов преобразований, использованных в следующей программе:

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
 struct tm *tm_ptr, timestruct;
 time_t the_time;
 char buf[256];
 char *result;
 (void)time(&the_time);
 tm_ptr = localtime(&the_time);
 strftime(buf, 256, "%A %d %B, %I:%S %p", tm_ptr);
 printf("strftime gives: %sn", buf);
 strcpy(buf, "Thu 26 July 2007, 17:53 will do fine");
 printf("calling strptime with: %sn", buf);
 tm_ptr = &timestruct;
 result = strptime(buf, "%a %d %b %Y, %R", tm_ptr);
 printf("strptime consumed up to: %sn", result);
 printf("strptime gives:n");
 printf ("date: %02d/%02d/%02dn",
  tm_ptr->tm_year % 100, tm_ptr->tm_mon+1, tm_ptr->tm_mday);
 printf("time: %02d:%02dn",
  tm_ptr->tm_hour, tm->ptr->tm_min);
 exit(0);
}

Когда вы откомпилируете и выполните программу strftime.c, то получите следующий результат:

$ ./strftime
strftime gives: Saturday 09 June, 08:16 AM
calling strptime with: Thu 26 July 2007, 17:53 will do fine
strptime concurred up to: will do fine
strptime gives:
date: 07/07/26
time: 17:53

Как это работает

Программа strftime получает текущее местное время с помощью вызовов функций time и localtime. Затем она преобразует его в удобочитаемую форму с помощью функции strftime с подходящим аргументом форматирования. Для демонстрации применения функции strptime программа задает строку, содержащую дату и время, затем вызывает strptime для извлечения необработанных значений времени и даты и выводит их на экран. Спецификатор преобразования %R функции strptime — это сокращенное обозначение комбинации %Н:%M.

Важно отметить, что для успешного просмотра даты функции strptime необходима точная строка формата. Обычно она не может точно обработать даты, считываемые из строк, введенных пользователями, до тех пор, пока не будет строго выверен формат.

Возможно, при компиляции программы strftime.c вы получите предупреждение компилятора. Причина в том, что по умолчанию в библиотеке GNU не объявлена функция strptime. Для устранения проблемы следует явно запросить средства стандарта X/Open, добавив следующую строку перед заголовочным файлом time.h:

#define _XOPEN_SOURCE

Оглавление книги


Генерация: 0.604. Запросов К БД/Cache: 2 / 0
поделиться
Вверх Вниз