Pull to refresh

Boost это просто. Часть 2. Boost.Date_time

Reading time15 min
Views37K
Как вы уже поняли, в данной статье речь пойдет о библиотеке Boost.Date_Time. Библиотеке по работе со временем.
&nbsp
&nbsp
&nbsp

В статье мы рассмотрим следующие части библиотеки:

  • Gregorian
  • Posix Time
  • Local Time



Введение


Сейчас наверно сложно найти человека, который не столкнулся бы с необходимостью использования времени в своих целях, а точнее его цифрового представления. Цели могут быть совершенно разными, от простых замеров интервалов времени, в которые выполняется некий участок кода(совершенно не верный метод профайлинга, между прочим, но сейчас не об этом), до поддержки полноценных календарей в своих приложениях, которые должны быть актуальны для любого человека на земном шаре. Все эти манипуляции можно выполнить с помощью довольно аскетичной и простой в использовании, но в тоже время мощной библиотеки Boost.Date_Time. В библиотеке учитываются: високосный год, високосная секунда, летнее время и т.д
Разберемся с некоторой терминологией, используемой в библиотеке:
В библиотеке существую три глобальных типа разделения времени:
  1. Time Point — некая точка во временном континууме, например дата Вашего рождения
  2. Time Duration — интервал времени, не привязанный ни к какой точке временного континуума
  3. Time Interval — интервал времени, привязанный к некой точке во временном континууме

Так же необходимо помнить о разрешающей способности каждого метода получения времени, разрешающая способность(Resolution) показывает — какая минимальная единица времени может быть использована при анализе имеющегося у вас объекта времени. Т.е это не что иное, как степень точности полученной временной точки. Наконец, перейдем к непосредственному знакомству с возможностями библиотеки.

boost::gregorian


Все примеры, используемые в данной статье подразумевает использование using namespace boost::<соответствующий_namespace> в начале каждого компилируемого юнита.
Данный компонент представляет нам Грегорианский календарь и все мыслимые возможности работы с ним. Он в свою очередь делится на следующие компоненты:

Разберем назначение каждой из них:

boost::gregorian::date — предназначен для простого хранения даты – точки во временном континууме. Поддерживает создание даты из строки определенного формата, а также посредством класса boost::date_time::day_clock, основанного на стандартном механизме С\С++ связанным с time_t
Помимо хранения даты(которая не может быть изменена у объекта, кроме как посредством operator=) у класса есть методы* для получения конкретных частей даты(year(), month(), day() etc.), преобразования внутреннего представления даты в строку, определенного формата(to_simple_string(),to_iso_string(),to_iso_extended_string()) и конвертации в(и обратно) стандартную С\С++ структуру tm(to_tm(),date_from_tm())

*- я не привожу описание каждой функции, более того я не буду, приводит полный список имеющихся функций, их список Вы можете посмотреть в ссылках, соответствующих конкретному классу. Функций довольно много и они довольно просты в использовании, так же я опускаю наличие параметров функции, если считаю это незначительным в данный момент.
Пример:
date xGPWStart(1941, Jun, 22);
date xNowdays = day_clock::local_day();
std::cout << "The Great Patriotic War was started in " << xGPWStart << std::endl;
std::cout << "And the current data is " << xNowdays;


* This source code was highlighted with Source Code Highlighter.


Вывод:
The Great Patriotic War was started in 1941-Jun-22
And the current data is 2009-Jul-26

boost::gregorian::date_duration — служит для подсчета дней, используя boost::gregorian::date для вычислений.
Для удобства работы с date_duration существуют три класса: months_duration, years_duration и weeks_duration(так же есть typedef’ы на эти типы, представленные для удобства: months, years и weeks соответственно), которые могут быть прибавлены или вычтены из date_duration чтобы получить желаемый результат. Существует подводный камень, связанный с этими тремя классами. Если использовать их в своих вычислениях, то Вы можете получить результат, который Вы не ожидаете. Приведу пример:
date xSomeDay(1999, Jan, 28);
date xDayInNextMonth;
std::cout << "That's right: " << ( xDayInNextMonth = xSomeDay + months(1) ) << std::endl;
std::cout << "And that's not: " << xDayInNextMonth + months(1);

* This source code was highlighted with Source Code Highlighter.

That's right: 1999-Feb-28
And that's not: 1999-Mar-31

Такое поведение обусловлено особенностью класса months_duration, который будет всегда использовать конец месяца в арифметических операциях, если изначальный объект указывал на одно из возможных чисел, которыми оканчиваются месяцы(28, 29, 30, 31). Будьте внимательны при использовании данного типа, кстати month_iterator лишен этого недостатка(преимущества?), но о нем поговорим позже.

boost::gregorian::date_period — класс представлен для удобного представления интервала между двумя датами, может быть использован для определения вхождения определенной даты во временной интервал(contains()), пересечения интервалов(intersects(), intersection()), смежности дат(is_adjacent()) и определения отношения расположения одной даты относительно другой(is_after(), is_before()). Кроме того, существуют методы для комбинации интервалов(merge(), span()) и их изменения(shift(), expand()). Важно помнить, что последний дегь в периоде не входит в весь период, т.е в периоде 1-Jan-1999\10-Jan-1999 последним днем будет 9 января, а не 10.
Пример:
date_period xGPWDuration( date(1941, Jun, 22), date(1945, May, 9) );
date_period xStalinLifeYears( date(1878, Dec, 18), date(1953, Mar, 6) ); date_period xJukovsIncorrectLifeYears( date(1896, Dec, 6), date(1974, Jun, 14) );
std::cout << "The Great Patriotic War duration is " << xGPWDuration << std::endl;
std::cout << "Was the GPW inside the Stalin's life years? " << std::boolalpha << xStalinLifeYears.contains(xGPWDuration) << std::endl;
std::cout << "Jukov's incorrect life years is " << xJukovsIncorrectLifeYears << std::endl;
xJukovsIncorrectLifeYears.expand( days(5) );
std::cout << "Jukov's correct life years is " << xJukovsIncorrectLifeYears << std::endl;
//Last day isn't included in the interval
date_period xFirstPeriod( date(1999, Jan, 1), date(1999, Jan, 10) );
date_period xSecondPeriod( date(1999, Jan, 10), date(1999, Jan, 12) );
std::cout << "Does these periods intersect? " << std::boolalpha << xFirstPeriod.intersects(xSecondPeriod) << std::endl;

* This source code was highlighted with Source Code Highlighter.

Вывод:
The Great Patriotic War duration is [1941-Jun-22/1945-May-08]
Was the GPW inside the Stalin's life years? true
Jukov's incorrect life years is [1896-Dec-06/1974-Jun-13]
Jukov's correct life years is [1896-Dec-01/1974-Jun-18]
Does these periods intersect? false

boost::gregorian::date_iterator — как должно быть понятно из названия – это типичный итератор, предназначенный для “движения” по датам. date_iterator сам по себе не интересен, т.к является абстрактным классом, более интересны его классы-наследники: day_iterator, week_iterator, month_iterator, year_iterator.
В качестве примера используем пример из date_duration, в котором мы получали некорректную дату (из-за подводных камней с месяцами). Как я и упоминал раньше, в date_iterator подобных проблем нет:
month_iterator xSomeDay(date(1999, Jan, 28));
std::cout << "That's right: " << *++xSomeDay << std::endl;
std::cout << "And that's too!: " << *++xSomeDay;

* This source code was highlighted with Source Code Highlighter.

That's right: 1999-Feb-28
And that's too!: 1999-Mar-28


Алгоритмы по работе с датами — набор разнообразных классов и функций, для различных манипуляций над датами. Каждый класс имеет метод get_data() который позволяет получить дату, сгенерированную данным классом. Классы предоставляют нам следующий функционал:
  • Получить первый, последний или произвольный день для заданного месяца и недели(first_day_of_the_week_in_the_month(), last_day_of_the_week_in_the_month(), nth_day_of_the_week_in_the_month). День недели для поиска задается.
  • Задать частичную дату(без указания года)(partial_date()). А потом получать полную дату, с помощью get_data()
  • Вычислить первый день недели до или после заданной даты(first_day_of_the_week_before(), first_day_of_the_week_after()). День недели для вычисления задается

Функции предоставляют следующий функционал:
  • Вычислить кол-во дней начиная от текущей даты, до следующего или предыдущего, заданного, дня недели(days_until_weekday(), days_before_week_day()).
  • Сгенерировать дату, которая будет является датой следующего или предыдущего, заданного, дня недели. Новая дата будет сгенерирована, относительно предварительно заданной даты.

Примеры:
last_day_of_the_week_in_month xLastFriday(Friday, Jul);
  partial_date xJunTen(10, Jun);
  std::cout << "What is the date of the last friday in the July 2009? " << xLastFriday.get_date(2009) << std::endl;
  std::cout << "Just dusplay 10 Jun of 2009 " << xJunTen.get_date(2009) << std::endl;
  std::cout << "How much days from now till next friday? " << days_until_weekday( day_clock::local_day(), greg_weekday(Friday) )<< std::endl;

* This source code was highlighted with Source Code Highlighter.

Вывод:
What is the date of the last friday in the July 2009? 2009-Jul-31
Just dusplay 10 Jun of 2009 2009-Jun-10
How much days from now till next friday? 5

boost::gregorian::gregorian_calendar — предоставляет полезный набор статических функций для работы с датами.
Вместо описания функций, приведу пример их использования(функции просты и их название говорит само за себя):
  std::cout << "What the day of the GPW begining? " << DayToString( gregorian_calendar::day_of_week( gregorian_calendar::ymd_type(1941, Jun, 22) ) ) << std::endl;
  std::cout << "And what is the number of this day frome the epoch start? " << gregorian_calendar::day_number( gregorian_calendar::ymd_type(1941, Jun, 22) ) << std::endl;
  std::cout << "And what is the number of this day frome the epoch start? " << gregorian_calendar::day_number( gregorian_calendar::ymd_type(1400, Jan, 1) ) << std::endl;
  std::cout << "What is the last day in the February 1941? " << gregorian_calendar::end_of_month_day(1941, Feb) << std::endl;
  std::cout << "What is the date of the 3333333 day from the epoch start? " << date( gregorian_calendar::from_day_number(3333333) ) << std::endl;
  std::cout << "Is the 2004 year a leap year? " << std::boolalpha << gregorian_calendar::is_leap_year(2004) << std::endl;

* This source code was highlighted with Source Code Highlighter.

Вывод:
What the day of the GPW begining? Sunday
And what is the number of this day frome the epoch start? 2430168
And what is the number of this day frome the epoch start? 2232400
What is the last day in the February 1941? 28
What is the date of the 3333333 day from the epoch start? 4414-Apr-03
Is the 2004 year a leap year? true

Эмпирическим путем было получено, что для функций day_number() и from_day_number() минимальными значениями являются 1400-Jan-1 и 2232400 соответственно. Если пытаться использовать дату ранее 1400-Jan-1, то получим исключение. То же справедливо и для количества дней.

boost::posix_time


Данный компонент предоставляет нам удобный метод работы с точками во времени, но в отличие от boost::gregorian boost::posix_time предоставляет возможность работы с временными точками более низкого разрешения (вплоть до наносекунд), высокая часть разрешения(дата) реализуется с помощью boost::gregorian. Компонент особенно удобен для задач, в которых необходима высокая точность получения времени(например строка записи в лог файле). Он делится на следующие части:

Разберем назначение каждой части:

boost::posix_time::ptime — представляет собой точку во временном континууме. Очень похожа на boost::gregorian:date но с разрешением до микросекунд. При создании экземпляра класса только по gregorian:date, “низкоразрешающая” часть устанавливается в полночь(все нули).
Пример использования:
ptime xTime(date(1961, Apr, 12), hours(9) + minutes(7));
std::cout << "Did you know that Gagrin said \"Poehali\" at " << xTime << "\n";
ptime xTimeStr( time_from_string("1961-04-12 09.07.00.0000") );
std::cout << "And the same time point constructed from a string: " << xTimeStr << "\n";
std::cout << "Current time with second resolution: " << second_clock::local_time() << "\nAnd with microsecond:" << microsec_clock::local_time();

* This source code was highlighted with Source Code Highlighter.

Вывод:
Did you know that Gagrin said «Poehali» at 1961-Apr-12 09:07:00
And the same time point constructed from a string: 1961-Apr-12 09:07:00
Current time with second resolution: 2009-Jul-29 16:41:51
And with microsecond:2009-Jul-29 16:41:51.087000


boost::posix_time::time_duration — представляет собой длительность во времени, которая не привязана к конкретной дате. Максимальное разрешение длительности ограничено наносекундами, если библиотека собрана с макросом BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG, и микросекундами, по умолчанию. Из объекта может быть получена информация о количестве секунд\микросекунд\миллисекунд\наносекунд(при соответствующей сборке) которые содержатся в текущей временной длительности.
Пример:
time_duration xTime(1,2,3);
std::cout << "Print time: " << xTime << "\n";
std::cout << "Print increased time: " << xTime + hours(3) + seconds(2) + minutes(6) + milliseconds(15) + microseconds(25) << "\n";
std::cout << "Print total seconds: " << xTime.total_seconds() << " milliseconds: " <<
          xTime.total_milliseconds() << " microseconds: " << xTime.total_microseconds() << "\n";

* This source code was highlighted with Source Code Highlighter.

Вывод:
Print time: 01:02:03
Print increased time: 04:08:05.015025
Print total seconds: 3723 milliseconds: 3723000 microseconds: 3723000000

boost::posix_time::time_period — представляет собой отрезок во времени, класс схож gregorian::date_period, но имеет более низкую разрешающую способность. Функционал класса позволяет определять вхождение(contains()), пересечение(intersects()) и длину(length()) интервалов. Так же существует возможность расширения(expand()), смещения(shift()) и объединения(merge()) интервалов.
Пример:
  ptime xDoomsday( date(2012, Jan, 1) );
  time_period xArmageddonLast(xDoomsday, hours(1));
  time_period xChakNorrisSmoke(xDoomsday, minutes(1));
  std::cout << "Doomsday was during: " << xArmageddonLast<< "\n";
  std::cout << "Chak Norris was smoking at " << xChakNorrisSmoke << "\n";
  std::cout << "Did Chak Norris smoke during Doomsday breathtaking?" << std::boolalpha <<xArmageddonLast.contains(xChakNorrisSmoke);


* This source code was highlighted with Source Code Highlighter.

Вывод:
Doomsday was during: [2012-Jan-01 00:00:00/2012-Jan-01 00:59:59.999999]
Chak Norris was smoking at [2012-Jan-01 00:00:00/2012-Jan-01 00:00:59.999999]
Did Chak Norris smoke during Doomsday breathtaking?true

boost::posix_time::time_iterator — предназначен(как наверное все догадались :) ) для итерации по времени. Приятной особенностью данного итератора является возможность задания временного интервала, который будет использоваться при итерации, т.е. насколько и в каких единицах будет изменяться текущая точка во временном континууме при каждой итерации. В качестве временных единиц могут быть использованы все единицы от часа до наносекунд (если собрано с соответствующим флагом)
Приведу маленький пример:
ptime xTime(date(2012, Jan, 1));
time_iterator xIt(xTime, hours(6));
std::cout << "6 hours after Domsday has come!!!" << *++xIt;

* This source code was highlighted with Source Code Highlighter.

Вывод:
6 hours after Domsday has come!!!2012-Jan-01 06:00:00

Local Time System


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


boost::local_time:: posix_time_zone представляет собой набор данных и правил для представления часовых поясов(смещение относительно GMT, правила перехода на летнее время и обратно, название часового пояса и его аббревиатура). Объект данного типа создается на основании строки, формат строки является стандартизированным POSIX форматом(IEEE Std 1003.1) для часовых поясов.
В общем виде эта строка выглядит следующим образом:
std offset dst [offset],start[/time],end[/time]
std — Аббревиатура часового пояса.
offset — Смещение относительно GMT.
dst — Аббревиатура часового пояса, во время действия летнего времени.
[offset] — Показывает насколько изменяется время (в часах), при переходе на летнее время. Опциональный параметр.
start и end — Задают интервал действия летнего времени.
[/time] — Задает точное время в пределах дня, в которое начинается переход на летнее время или летнее время прекращает свое действие.
offset и time имеют следующий формат: [+|-]hh[:mm[:ss]] {h=0-23, m/s=0-59}
start и end могут быть представлены в одном из следующих форматов:
  • Mm.w.d {month=1-12, week=1-5 (5 всегда последняя), day=0-6}
  • Jn {n=1-365 29 февраля – не учитывается}
  • n {n=0-365 Учитывает 29 февраля в високосные годы}

Каждую часть этой строки вы может получить отдельно, с помощью методов данного класса. Не вижу смысла приводить здесь их имена, они достаточно прозрачны и отражают суть их назначения, так что обратитесь к документации, за их списком.
В качестве примера я использовал часовой пояс GMT+3(Московское время):
posix_time_zone xZone("MSK+3MSD+01,M3.5.0/02:00,M10.5.0/02:00");
std::cout << "Dailight period in 2009 started at " << xZone.dst_local_start_time(2009) << "\nAnd it will finish at " << xZone.dst_local_end_time(2009);


* This source code was highlighted with Source Code Highlighter.


Вывод:
Dailight period in 2009 started at 2009-Mar-29 02:00:00
And it will finish at 2009-Oct-25 02:00:00


boost::local_time::tz_database — удобный класс для хранения множества различных часовых поясов. При создании объекта, создается пустая база данных (громко сказано конечно :) ), после чего она может быть вручную заполнена, с помощью метода add_record() или же прочитана из csv(comma separated values) файла, пример такого файла(с большим количеством записей) содержится в %boost%\libs\date_time\data\date_time_zonespec.csv
Формат записи внутри этого файла, должен удовлетворять следующему стандарту:
«ID»,«STD ABBR»,«STD NAME»,«DST ABBR»,«DST NAME»,«GMT offset»,«DST adjustment»,«DST Start Date rule»,«Start time»,«DST End date rule»,«End time»

Где:
ID – Содержит строку, однозначно идентифицирующую данный часовой пояс.
STD ABBR, STD NAME, DST ABBR, DST NAME — Эти поля заполняются строками с именами и аббревиатурами стандартного и летнего времен, зачастую имена и аббревиатуры идентичны.
GMT offset — Смещение во времени, относительно Гринвича. Его формат: {+|-}hh:mm[:ss]
DST adjustment — Смещение относительно GMT offset во время действия “летнего времени”. Формат тот же, что и у GMT offset.
DST Start Date rule — Строка описывающая день в году, который возвещает начало периода “летнего времени”. Так же имеет свой формат(сколько можно форматов разных?):
weekday;day-of-week;month
Где:
weekday — Порядковое числительное, индицирующее какой по счету день в месяце, нас интересует.
day-of-week — день недели.
month — месяц.
Пример:
-1;0;3 – Начало “летнего времени” в России(последнее воскресенье марта)

Start time — Время после полуночи, в которое вступает в силу “летнее время”. Формат тот же, что и у GMT offset.
DST End date rule — Строка описывающая день в году, который возвещает конец периода “летнего времени”. Формат идентичен DST Start Date rule.
End time — Аналог Start time, только для окончания “летнего времени”.
Пример:
tz_database xDb;
xDb.load_from_file("G:\\Program files\\boost\\boost_1_39_0\\\libs\\date_time\\data\\date_time_zonespec.csv");
const std::vector<std::string>& xAllRegions = xDb.region_list();
std::cout << "Print first 10 zone IDs from the boost time zone file:" << std::endl;
for(std::vector<std::string>::const_iterator it = xAllRegions.begin(); it != xAllRegions.begin() + 10; ++it)
    std::cout << *it << std::endl;
std::cout << "And time when daylight saving was started at 2009: " << xDb.time_zone_from_region("Europe/Moscow")->dst_local_start_time(2009) << std::endl;

* This source code was highlighted with Source Code Highlighter.


Вывод:
Print first 10 zone IDs from the boost time zone file:
Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmera
Africa/Bamako
Africa/Bangui
Africa/Banjul
Africa/Bissau
Africa/Blantyre
And time when daylight saving was started at 2009: 2009-Mar-29 02:00:00


boost::local_time::custom_time_zone — класс для создания описания часового пояса, но в отличие от прежде описанного boost::local_time::posix_time_zone , данный класс использует четыре других класса, при конструировании часового пояса. Это классы: time_duration , time_zone_names , dst_adjustment_offsets и dst_calc_rule. Так как класс не выделяется ничем выдающимся, то просто приведу пример его использования:
time_zone_names xNames("Moscow Standart Time", "MST", "Moscow Daylight Time", "MDT");
time_duration xGMTOffset(3, 0, 0);
dst_adjustment_offsets xRulesOffsets( time_duration(1,0,0), time_duration(2,0,0), time_duration(3,0,0) );
//Mak daylight's rule
last_day_of_the_week_in_month xStartRule(Sunday, Mar);
last_day_of_the_week_in_month xEndRule(Sunday, Oct);

boost::shared_ptr<dst_calc_rule> xRules( new last_last_dst_rule(xStartRule, xEndRule) );
custom_time_zone xCustomTimeZone(xNames, xGMTOffset, xRulesOffsets, xRules);
  
std::cout << "The our time zone name is: " << xCustomTimeZone.std_zone_name() << "\n"
          << "It has an "<< xCustomTimeZone.base_utc_offset() << " offset from GMT.\n"
          << "And daylight period will end at "<< xCustomTimeZone.dst_local_end_time(2009) <<std::endl;
std::cout << "Posix string which represents our custom_time_zone object is:\n" << xCustomTimeZone.to_posix_string();

* This source code was highlighted with Source Code Highlighter.

Вывод:
The our time zone name is: Moscow Standart Time
It has an 03:00:00 offset from GMT.
And daylight period will end at 2009-Oct-25 03:00:00
Posix string which represents our custom_time_zone object is:
MST+03MDT+01,M3.5.0/02:00,M10.5.0/03:00

Классы boost::local_time::local_date_time и boost::local_time::local_time_period повторяют схожие классы из boost::posix_time, с привязкой к часовому поясу, поэтому я не буду их рассматривать.

Полезные особенности и заключение


В Boost.Data_time, помимо классов для создания дат, есть полезные утилиты, такие как: форматируемая обработка ввода\вывода и сериализация. Оба механизма достаточно просты в использовании и их описание я считаю избыточным т.к статья, итак, получилась довольно объемная.
В качестве заключения хотелось бы поблагодарить всех, кто осилил дочитать до этого места, а также пожелать удачи в использовании Boost.Date_time в своих проектах, надеюсь тех кто не использовал ее раньше, эта статья сподвигнет на использование(меня уже сподвигла :)).
P.S. На эту статью у меня ушел почти месяц, буду надеяться, что дальше буду писать чаще. Если Вы нашли ошибку в статье, то сообщите мне, пожалуйста, в личку, не стоит засорять этим комментарии.

Tags:
Hubs:
+31
Comments16

Articles

Change theme settings