2010-09-24 5 views
49

Ввод: строки с указанием даты и дополнительного времени. Различные представления были бы хорошими, но необходимыми. Строки поставляются пользователем и могут быть искажены. Примеры:Как анализировать дату/время из строки?

  • "2004-03-21 12:45:33" (я считаю это расположение по умолчанию)
  • "2004/03/21 12:45:33" (опционально макет)
  • "23.09.2004 04:12:21" (немецкий формат, опционально)
  • "2003-02-11" (время может отсутствовать)

Необходимый результат: секунды с эпохи (1970/01/01 00:00:00) или некоторые другие фиксированные точки.

Бонус: Кроме того, чтение UTC-смещения местного системного времени было бы замечательным.

Вход считается предполагаемым местным временем на рассматриваемой машине. Выход должен быть UTC. Система - только Linux (нужны Debian Lenny и Ubuntu).

Я попытался использовать boost/date_time, но должен признать, что я не могу обернуть голову вокруг документации. Следующие работы без необходимого перехода от системы местного времени в UTC:

std::string date = "2000-01-01"; 
boost::posix_time::ptime ptimedate = boost::posix_time::time_from_string(date); 
ptimedate += boost::posix_time::hours(Hardcoded_UTC_Offset);// where to get from? 
struct tm = boost::posix_time::to_tm(ptimedate); 
int64_t ticks = mktime(&mTmTime); 

Я думаю boost::date_time могут обеспечить необходимое смещение UTC, но я не знаю, как.

+1

Я считаю, что вам придется разбирать их самостоятельно (возможно, с духом), потому что однозначный номер месяца в «2004-3-21» не может быть проанализирован ни одним из спецификаторов формата ввода-вывода в формате времени http: //www.boost.org/doc/libs/1_44_0/doc/html/date_time/date_time_io.html#date_time.format_flags – Cubbi

+0

@Cubbi: если это единственная проблема, гораздо проще проверить это и вставить 0 в чтобы привести дух в картину. –

+0

@Cubbi - вы можете обрабатывать пользовательские форматы ввода и вывода в 'boost :: date_time' -' boost :: spirit' здесь переполняется –

ответ

59

Хотя я не знаю, как форматировать однозначный ввод месяца в boost, я могу это сделать после двузначного редактирования:

#include <iostream> 
#include <boost/date_time.hpp> 
namespace bt = boost::posix_time; 
const std::locale formats[] = { 
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d %H:%M:%S")), 
std::locale(std::locale::classic(),new bt::time_input_facet("%Y/%m/%d %H:%M:%S")), 
std::locale(std::locale::classic(),new bt::time_input_facet("%d.%m.%Y %H:%M:%S")), 
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d"))}; 
const size_t formats_n = sizeof(formats)/sizeof(formats[0]); 

std::time_t pt_to_time_t(const bt::ptime& pt) 
{ 
    bt::ptime timet_start(boost::gregorian::date(1970,1,1)); 
    bt::time_duration diff = pt - timet_start; 
    return diff.ticks()/bt::time_duration::rep_type::ticks_per_second; 

} 
void seconds_from_epoch(const std::string& s) 
{ 
    bt::ptime pt; 
    for(size_t i=0; i<formats_n; ++i) 
    { 
     std::istringstream is(s); 
     is.imbue(formats[i]); 
     is >> pt; 
     if(pt != bt::ptime()) break; 
    } 
    std::cout << " ptime is " << pt << '\n'; 
    std::cout << " seconds from epoch are " << pt_to_time_t(pt) << '\n'; 
} 
int main() 
{ 
    seconds_from_epoch("2004-03-21 12:45:33"); 
    seconds_from_epoch("2004/03/21 12:45:33"); 
    seconds_from_epoch("23.09.2004 04:12:21"); 
    seconds_from_epoch("2003-02-11"); 
} 

отмечают, что выход секундной из-эпохи будет предполагающей дата была в UTC:

~ $ ./test | head -2 
ptime is 2004-Mar-21 12:45:33 
seconds from epoch are 1079873133 
~ $ date -d @1079873133 
Sun Mar 21 07:45:33 EST 2004 

Вы могли бы, вероятно, использовать boost::posix_time::c_time::localtime() от #include <boost/date_time/c_time.hpp>, чтобы получить это преобразование сделанный в предположении, что вход в текущий часовой пояс, но это довольно противоречиво: для меня, например, результат будет отличаться между сегодняшним и следующим месяцем, когда заканчивается летнее время.

+1

Понятно, как работать с гранулами. Использование localtime не является вариантом, если я это правильно понимаю, поскольку это дало бы мне DST-смещение сегодняшнего дня, а не заданную дату. –

+0

@Gabriel Schreiber: Возможно, вы могли сделать DST-смещение на заданную дату, делая противоположное тому, что 'utc_to_local()' делает в '/ usr/include/boost/date_time/c_local_time_adjustor.hpp', которое все равно будет использовать текущей зоны компьютера.Лучше всего, вероятно, что-то ближе к http://www.boost.org/doc/libs/1_44_0/doc/html/date_time/examples.html#date_time.examples.seconds_since_epoch – Cubbi

1

самое простое, портативное решение заключается в использовании scanf:

int year, month, day, hour, minute, second = 0; 
int r = 0; 

r = scanf ("%d-%d-%d %d:%d:%d", &year, &month, &day, 
      &hour, &minute, &second); 
if (r == 6) 
{ 
    printf ("%d-%d-%d %d:%d:%d\n", year, month, day, hour, minute, 
      second); 
} 
else 
{ 
    r = scanf ("%d/%d/%d %d:%d:%d", &year, &month, &day, 
      &hour, &minute, &second); 
    // and so on ... 

Инициализировать struct tm с int значениями и передать его mktime получить календарное время, как time_t. Для конверсий с часовым поясом, пожалуйста, see information на gmtime.

+7

C runtime 'scanf/printf' внедряет управление буфером и проблемы с безопасностью типов, которых можно избежать с помощью соответствующих библиотек C++. –

+0

Это не решает проблему local с utc. Кроме того, строка предоставляется пользователем и может быть недействительной/неверной. Я думаю, что это может быть проблемой с scanf? –

+0

@Gabriel, если строка неверна, scanf не вернет 6. о utc, я добавил дополнительную информацию в ответ. –

8

boost::gregorian имеет некоторые вещи, вам нужно без вас делать больше работы:

using namespace boost::gregorian; 
{ 
    // The following date is in ISO 8601 extended format (CCYY-MM-DD) 
    std::string s("2000-01-01"); 
    date d(from_simple_string(s)); 
    std::cout << to_simple_string(d) << std::endl; 
} 

Существует пример того, как использовать UTC смещает с boost::posix_timehere.

Вы можете обеспечить генерацию даты и времени из пользовательских форматов входных строк с использованием date_input_facet и time_input_facet. Существует учебник по I/O на this page, который поможет вам разобраться.

+1

Thx для фасета/учебника linx. Использование boost :: gregorian не решает проблему, потому что не обеспечивает разбора/представления времени. –

+0

@ Gabriel - вам нужно будет создать собственный синтаксический анализатор и форматировщик, используя эти инструменты для обработки всех ваших необходимых случаев. Если у вас нет неограниченных возможных входных форматов, это должно быть возможно с использованием парсера для каждого формата и обертки, которая идентифицирует тип формата и передает соответствующий парсер. –

+0

@Gabriel - обратите внимание, что когда я говорю синтаксический анализатор, это действительно ничего сложного, учитывая ваши параметры ввода 'string'. Просто определите каждый и создайте соответствующие конструкции Boost для правильного анализа в date_time. –

6

Если с-стиль приемлем: strptime() является путь, потому что вы можете указать формат, и он может принимать локаль во внимание:

tm brokenTime; 
strptime(str.c_str(), "%Y-%m-%d %T", &brokenTime); 
time_t sinceEpoch = timegm(brokenTime); 

Различные макеты должны быть проверены с возвращаемое значение (если возможно). Часовой пояс необходимо будет добавить, проверив системные часы (localtime_r() с помощью time(), tm_zone)

+0

strptime был опробован. Это неприемлемо, потому что он с радостью потерпит крах, если строка не будет правильно сформирована. –

+0

Я использую его, он не падает здесь, но опыт может отличаться. Мне нужно будет исследовать (для google), чтобы быть уверенным ... – stefaanv

+0

@ Gabriel: За исключением MacOS X Leopard, где strptime кажется сломанным, ничего особенного не найдено (getdate crashes, Qalculate удален strptime (2004)). Не могли бы вы дать некоторую информацию о системе, в которой он сбой? – stefaanv