1.15.2 Локальное время
Теперь рассмотрим вопросы, связанные с локальным временем. Это время зависит от часового пояса. К тому же, в нашей страной многих других странах имеется различие между летним и зимним локальным временем. В API Windowsесть тип структурыTTimeZoneInformation( _ТIМЕ_ZONE_INFORMATION), содержащей данные 6 смещении локального времени и датах перехода на летнее и зимнее время:
Typedef struct _ТIМЕ_ZONE_INFORMATION {
LONG Bias;
WCHAR StandartName[ 32 ];
SYSTEMTIME StandartDate;
LONG StandartBias;
WCHAR DaylightName[ 32 ];
SYSTEMTIME DaylightDate;
LONG DaylightBias;
} ТIМЕ_ZONE_INFORMATION , *P ТIМЕ_ZONE_INFORMATION,
*L ТIМЕ_ZONE_INFORMATION;
Информация, содержащаяся в этой структуре, определяет соотношение между стандартом согласованного универсального времени (CoordinatedUniversalTime—UTCили время.по Гринвичу) и локальным временем. Основное полеBiasопределяет соответствие между этими временами. В поле содержится смещение локального времени в минутах по отношению кUTC. Например, для МосквыBias= -180. Т.е. московское время опережаетUTCна 3 часа. В общем случае:
UTC= локальное время + смещение.
Поле StandartName— строка с нулевым завершающим символом, дающая название стандартного (зимнего) времени, принятого в системе. Например, для Москвы это может быть "Московское время (зима)". Данное поле не используется операционной системой и не является обязательным. В качествеStandartNameможет быть задана пустая строка. Но если полеStandartNameзадано при Вызове функцииSetTimeZoneInformation, то оно будет прочитано без изменений при последующем вызове функцииGetTimeZoneInformation.
Поле StandartDateявляется структурой описанного ранее типаTSystemTime, содержащей дату и время перехода с летнего на стандартное время. Если такая дата не указана, то полеwMonthструктурыStandartDateдолжно быть равно 0. Если полеStandartDateзаполнено, то должно быть заполнено и описанное далее полеDaylightDate.
Структура StandartDateможет содержать информацию в двух форматах. Во-первых, это может быть точная дата перехода:wYear— год,wMonth— месяц,wDay— день,wHour— час,wMinute— минута,wSecond— секунда,wMillisecond— Миллисекунда. Однако обычно переход не привязан к определенной дате, а привязан к определенному дню недели определенного месяца. Например, к последнему воскресенью какого-то месяца. СтруктураStandartDateможет заполняться в таком формате. В этом случае ее полеwYearравно 0, полеwMonthуказывает месяц, полеwDayOfWeekуказывает день недели, а полеwDayопределяет, в который по счету день месяца происходит переход времени. ПолеwDay.может принимать значения от 1 (первый день) до 5 (последний день). Например, если переход на зимнее время осуществляется в 3 часа ночи последнего воскресенья октября, тоwYear==0, wMonth = 10,wDayOfWeek= 0 (на Западе воскресенье считается первым днем недели, а не последним как у насwDay= 5wHour==3.
Если задано значение поля StandartDate, то полеStandartBiasможет содержать дополнительное смещение, складываемое сBiasв период зимнего времени. Обычно это значение равно 0.
Поле DaylightName- строка с нулевым завершающим символрм, дающая название летнего времени, принятого в системе. Например, для Москвы это может быть "Московское время (лето)". Данное полене используется операционной системой и не является обязательным. В качествеDaylightNameможет быть Задана пустая строка. Но если полеDaylightNameзадано при вызове функцииSetTimeZoneInformation, то оно будет прочитано без изменений при последующем вызове функцииGetTimeZoneInformation.
Поле DaylightDateопределяет структуру типаTSystemTime, содержащую дату и время перехода с зимнего на летнее время. Если такая дата не указана, то полеwMonthструктурыDaylightDateравно 0. Если полеDaylightDateзаполнено, то должно быть заполнено и полеStandartDate. СтруктураStandartDateможет содержать информацию в двух форматах, описанных выше для поляStandartDate. Например, если переход на летнее время осуществляется в 2 часа ночи последнего воскресенья марта, тоwYear=0,wMonth=3,wDayOfWeek= 0,wDay=5,wHour=2.
Если задано значение поля DaylightDate, то полеDaylightBiasсодержит дополнительное смещение, складываемое сBiasв период летнего времени. Обычно это значение равно -60 — сдвиг на 1 час.
Работу со структурами типа TTimeZoneInformationобеспечивают функцииGetTimeZoneInformationиSetTimeZoneInformation:
DWORD GetTimeZoneInformation(
OUT LPTIME_ZONE_INFOMATION lpTimeZoneInformation);
BOOL SetTimeZoneInformation(
CONST TIME_ZONE_INFORMATION * lpTimeZoneInformation);
Функция GetTimeZoneInformationдает доступ к информации о поясном времени, на которое настроена система. Эту информацию функция извлекает из настроекWindowsи заносит в структуру, указанную параметромlpTimeZoneInformation. ФункцияSetTimeZoneInformationзаносит аналогичную информацию в систему.
При успешном вызове GetTimeZoneInformationфункция возвращает одно из следующих значений:
|
TIME_ZONE_ID_UNKNOWN |
Операционная система не может определить временной пояс. Это может быть, если перед этим при вызове функции SetTimeZoneInformationвlpTimeZoneInformationбыло задано только поле смещенияBias. |
|
TIME_ZONE_ID_STANDART |
Операционная система работает в соответствии со значением поля StandartDateзаписиlpTimeZoneInformation, т.е. по стандартному (зимнему) времени. |
|
TIME_ZONE_ID_DAYLIGHT |
Операционная система работает в сoответствии со значением поляDaylightDateзаписиlpTimeZoneInformation, т.е. по летнему времени. |
Если вызов GetTimeZoneInformationпривел к ошибке, возвращается значение 0xFFFFFFFF. ФункцияSetTimeZoneInformationвозвращает в случае ошибкиfalse.
Рассмотрим примеры применения функций локального времени. Пусть в переменной Т типа TDateTimeхранятся дата и время некоторого события по московскому времени. И вы хотите в переменнуюTLocтипаTDateTimeзанести локальные дату и время вашего часового пояса, соответствующие этому событию. Такой пересчет можно сделать следующим кодом:
TTimeZoneInformation lpTimeZoneInformation;
System::TDateTime T, TLoc=0;
if (GetTimeZoneInformation(&lpTimeZoneInformation)==
TIME_ZONE_ID_STANDART)
TLoc = T+ (-180. lpTimeZoneInformation.Bias –
lpTimeZoneInformation.StandartBias)/(60*24);
else
TLoc = T+ (-180. lpTimeZoneInformation.Bias –
lpTimeZoneInformation.DaylightBias)/(60*24);
ShowMessage(DateTimeToStr(T) + “/n” + DateTimeToStr(TLoc));
Оператор if вызывает функцию GetTimeZoneInformation, которая заполняет структуру lpTimeZoneInformation типа TTimeZoneInformation информацией о локальном времени. Анализируется значение, возвращенное функцией, и определяется, функционирует ли в данный момент зимнее или летнее время. В зависимости от этого определяется сдвиг локального времени. Для определения локального времени, соответствующего значению Т, формируется суммарный сдвиг, который делится на (60 * 24) — т.е. на число минут в сутках. Для формирования сдвига сначала вычитается 180. Так как смещение московского времени -180, то тем самым время Т приводится к согласованному универсальному времени. Затем к этому времени добавляется локальный сдвиг, величина которого зависит от того, функционирует ли в данный момент зимнее или летнее время. Последний оператор отображает сообщение, содержащее исходное московское и вычисленное локальное время.
Приведенный код предполагал, что на компьютере задана полная информация о переходе на летнее и зимнее время, т.е. в структуре типа TTimeZoneInformationзаданы значения полейStandartDateиDaylightDate. В этом случае, если функцияGetTimeZoneInformationне вернулаTIME_ZONE_ID_STANDART, значит время летнее. Но в некоторых случаях информация о зимнем и летнем времени может быть не полная. Так что в общем случае вместо содержащегося в приведенном коде оператораifлучше использоватьswitch:
Switch (GetTimeZoneInformation(&lpTimeZoneInformation))
{
case TIME_ZONE_ID_STANDART:
TLoc = T + (-180. lpTimeZoneInformation.Bias –
lpTimeZoneInformation.StandartBias)/(60*24);
ShowMessage(DateTimeToStr(T) + “\n” + DateTimeToStr(TLoc));
break;
case TIME_ZONE_ID_DAYLIGHT:
TLoc = T + (-180. lpTimeZoneInformation.Bias –
lpTimeZoneInformation.DaylightBias)/(60*24);
ShowMessage(DateTimeToStr(T) + “\n” + DateTimeToStr(TLoc));
break;
case TIME_ZONE_ID_UNKNOWN:
TLoc = T + (-180. lpTimeZoneInformation.Bias)/(60*24);
ShowMessage(DateTimeToStr(T) + “\n” + DateTimeToStr(TLoc));
break;
default:
ShowMessage(“Error”);
Exit(0);
}
Теперь рассмотрим решение обратной задачи — перевода текущего локального времени в московское. Это можно сделать следующим кодом:
TTimeZoneInformation lpTimeZoneInformation;
System::TDateTime T=0, TMos=0;
if (GetTimeZoneInformation(&lpTimeZoneInformation)==
TIME_ZONE_ID_STANDART)
TMos = T+ (lpTimeZoneInformation.Bias +
lpTimeZoneInformation.StandartBias +180.)/(60*24);
else
TLoc = T+ (lpTimeZoneInformation.Bias +
lpTimeZoneInformation.DaylightBias+180.)/(60*24);
ShowMessage(DateTimeToStr(T) + “\n” + DateTimeToStr(TMos));
Код аналогичен приведенному ранее. Текущее время определяется функцией Now. Далее прибавлением к полученному значению соответствующего локального смещения время приводится к стандартному, а прибавлением 180 стандартное время приводится к московскому.
В данном примере вместо функции Nowможно было бы воспользоваться описанной ранее функциейGetLocalTime. Но тогда бы код заметно усложнился:
TSystemTime lpSystemTime;
GetLocalTIme(&lpSystemTime);
T = SystemTimeToDataTime(lpSystemTime);
Для дат и времени, представленный в формате FILETIME(см. разд. 1.15.1), можно для взаимных преобразований локального времени и времениUTCиспользовать функцииLocalFileTimeToFileTimeиFileTimeToLocalFileTime:
BOOL LocalFileTimeToFileTime(CONST FILETIME *lpLocalTimeFile, LPFILETIME
lpFileTime);
BOOL FileTimeToLocalFileTime(CONST FILETIME *lpTimeFile, LPFILETIME
lpLocalFileTime);
Первая из них преобразует локальное время, записанное в структуре lpLocalTimeFile, во времяUTCв структуреlpFileTime, а вторая осуществляет обратное преобразование. В обеих функциях параметрыlpLocalTimeFileиlpFileTimeдолжны указывать на разные структуры.
Если исходные дата и время имеются в формате TDateTime, то это значение можно сначала преобразовать в форматSYSTEMTIMEфункциейDateTimeToSystemTime, затем полученный результат преобразовать функциейSystemTimeToFileTimeв форматFILETIME(функцииDateTimeToSystemTimeиSystemTimeToFileTimeописаны в разд. 1.15.1), после чего к результату можно применить функцииLocalFileTimeToFileTimeиFileTimeToLocalTimeFile. Цепочка получается довольно длинная. Так что ее имеет смысл использовать, только если результатом должно быть времяUTCв форматеFILETIME. Например, если в переменной Т типаTDateTimeзаписано локальное время, и надо получить соответствующее ему времяUTCв форматеFILETIME(подобный пример вы можете увидеть в разд. 3.6), это можно сделать следующим кодом:
FILETIME Local, UTC:
SYSTEMTIME st;
LARGE_INTEGER li;
DateTimeToSystemTime(T, st);
SystemTimeToFileTime(&st, &local);
LocalFileTimeToFileTime(&local, &UTC);
1.15.3 Измерение отрезков времени
В процессе разработки приложения иногда требуется зафиксировать интервалы времени, чтобы узнать, "например, сколько времени занимают вычисления в каком-то фрагменте вашей программы. Это можно сделать различными способами. Простейший вариант — зафиксировать функцией Timeначало и конец выполнения измеряемого фрагмента кода:
TDateTime T1, T2;
T1 = Time();
……//Фрагмент, время которого измеряется
T2 = Time();
AnsiString S;
DateTimeToString(S, “hh:nn:ss:zzz”, T2-T1);
Label1 ->Caption=S;
В переменные Т1 и Т2 записываются начальный и конечный моменты времени, а затем их разность заносится функцией DateTimeToStringв строкуS. Применена именно эта функция, а не более простаяDateTimeToStr, чтобы получить результат с точностью до миллисекунд. Если достаточно значение с точностью до секунды, то можно, конечно, воспользоваться функциейDateTimeToStrи отобразить результат оператором:
Label1 ->Caption = TimeToStr(T2-T1);
Для измерения отрезков времени с точностью до миллисекунд можно использовать функцию GetTickCount:
DWORDGetTickCount(VOID); .
Она возвращает число миллисекунд, прошедших с момента старта Windows. Ниже приведен рассмотренный выше пример, реализованный с помощью этой функции:
DWORD T0, T;
T0 = ……//Фрагмент, время которого измеряется
();
……//Фрагмент, время которого измеряется
T = GetTickCount();
Label1 ->Caption = T –T0;;
В этом примере в метку Label1 заносится число миллисекунд, потраченных на выполнение фрагмента кода.
Производить отсчеты времени с высокой точностью можно с помощью функций QueryPerformanceCounterиQueryPerformanceFrequency:
BOOL QueryPerformanceCounter(
OUT LARGE_INTEGER *lpPerformanceCount);
BOOL QueryPerformanceFrequency(OUT LARGE_INTEGER *lpFequency);
Они работают, со счетчиком повышенной точности, который, правда, имеется не на каждом компьютере. Если такого счетчика нет, функции возвращают 0.
Функция QueryPerformanceCounterфиксирует в переменной типаLARGE_INTEGER, на которую указывает параметрlpPerformanceCount, текущее значение счетчика. А функцияQueryPerformanceFrequencyзаносит в переменную, на которую указывает параметрlpFequency, частоту счётчика в герцах, т.е. число увеличений счетчика в секунду. Таким образом, реализация рассмотренного выше пример с помощью этих функций может иметь вид:
LARGE_INTEGER f, Coun0, Count1, dCount;
If(!QueryPerformanceFrequency(&f))
{
Label1 ->Caption= “Изменение невозможно”;
return;
}
QueryPerformanceCounter(&Count0);
……//Фрагмент, время которого измеряется
QueryPerformanceCounter(&Count1);
dCount.QuadPart = Count1.QuadPart – Count0.QuadPart;
Label1 ->Caption = dCount.QuadPart * 1000 / f.QuadPart;
В приведенном примере результат измерения отрезка времени выводится в метку Label1 в миллисекундах. Если в последнем операторе заменить множитель 1000 множителем 1000000, то результат будет выводиться в микросекундах. Так что можно измерять очень короткие интервалы времени, недоступные рассмотренным ранее функциям.
Следует отметить, что все рассмотренные методы позволяют достоверно измерять не очень маленькие, и не очень большие интервалы времени. Для больших интервалов, превышающих квант времени, выделяемый на процесс, начинают вмешиваться системные процессы. Если эти процессы связаны с фрагментом, длительность которого измеряется, то все нормально. Но в измеряемый интервал вклиниваются и процессы, связанные с переключением системы на другие задачи. В этих случаях более достоверно учесть время, потраченное навычисления именно в данном фрагменте кода, можно функциями GetProcessTimesиGetThreadTimes. Эти функции, подробно рассмотренные в разд. 3.7 и 3.8, дают затраты времени данного процесса или данного потока. Не буду повторять описание функций, а сразу приведу вариант их использования все в том же примере:
__int64 FileTimeToint64(FILETIME F)
{
Return Int64ShllMod32(F.dwHighDateTime, 32) | F.dwLowDateTime;
}
………….
FILETIME lpCreationTime, lpExitTime, lpKernelTime0, lpUserTime0, lpKernelTime1, lpUserTime1;
GetThreadTimes(GetCurrentThread(), &lpCreationTime, &lpExitTime, &lpKernelTime0, &lpUserTime0);
……//Фрагмент, время которого измеряется
GetThreadTimes(GetCurrentThread(), &lpCreationTime, &lpExitTime, &lpKernelTime1, &lpUserTime1);
Label1 ->Caption = (FileTimeToint64(lpUserTime1) - FileTimeToint64(lpUserTime0))/10000;
В этом коде введена описанная в разд. 1.15.1 функция FileTimeToint64, которая преобразует данные типаFILETIMEв целое 64-разрядное число. ФункцияGetThreadTimesвызывается до начала выполнения фрагмента, и после него. В переменныеlpUserTime0 иlpUserTime1 эта функция заяосит время, затраченное потоком в соответствующие моменты на выполнение кода. Разность этих времен, выраженная в миллисекундах, передается в меткуLabel1. Передаваемый результат можно было бы делить не на 10000, а на 10. Тогда результат отображался бы в микросекундах.
Если требуется знать полное время с учетом системных затрат, то последний оператор надо заменить следующим:
Label1 ->Caption = (FileTimeToint64(lpKernelTime1) –
FileTimeToint64(lpKernelTime0))+
FileTimeToint64(lpUserTime1) –
FileTimeToint64(lpUserTime0)/10000;
При любых измерениях интервалов времени, затрачиваемых на выполнение фрагмента кода, полезно задать самые высокие приоритеты процессу и потоку, в котором данный фрагмент выполняется. Это минимизирует добавку к измеряемому интервалу времени затрат на системные процессы. Так что пе-ред определением времени Начала выполнения фрагмента Полезно вставить операторы:
// запоминание приоритетов
DWORD PriorProc = GetPriorityClass(GetCurrentProcess());
DWORD PriorThread = GetThreadPriority(GetCurrentThread());
// установка высоких
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
Первые два оператора запоминают текущие значения приоритетов» в следующие операторы устанавливают максимальные значения приоритетов. Функции, использованные в этом коде, рассмотрены в разд. 3,7 И 3.8. ,
После окончания фрагментами определения времениокончания надо восстановить прежние значения приоритетов операторами:
// восстановление приоритетов
SetPriorityClass(GetCurrentProcess(), PriorProc);
SetThreadPriority(GetCurrentThread(), PriorThread);
