2014-11-12 14 views
11

ISO 8601 biçiminde bir tarih ve saat belirtmeli, içinde milisaniye olabilir ya da olmayabilir ve bir struct tm ondan yanı sıra belirtilmiş olabilecek herhangi bir milisaniye değerini elde etmek istiyorum (hangisi) dizede bulunmazsa sıfır olarak kabul edilebilir).ISO 8601 tarihini (isteğe bağlı milisaniyelerle) C++ içindeki bir struct tm'ye nasıl ayrıştırırım?

Dizenin doğru biçimde olup olmadığının yanı sıra kullanıcı tarafından belirlenen bir dizenin struct tm ve milisaniye değerlerine dönüştürülüp dönüştürülmediğinin saptanmasında ne işe yarayacak?

Eğer bu milisaniye sorunu olmasaydı, muhtemelen C işlevini strptime() kullanabilirdim, ancak saniyeler ondalık nokta içerdiğinde bu işlevin tanımlı davranışının ne olduğunu bilmiyorum.

Son bir uyarı olarak, eğer mümkün ise, sadece Boost'da bulunan işlevlere bağımlı olmayan bir çözümü tercih ederim (ancak C++ 11'i kabul etmekten mutluluk duyuyorum) önkoşul).

giriş gibi bir şey oluyor: bu durumda,

2014-11-12T19:12:14.505Z 

veya

2014-11-12T12:12:14.505-5:00 

Z

, UTC gösterir, ancak herhangi bir zaman dilimi kullanılmış olabilir ve olarak ifade edilecektir GMT'den bir + veya - saat/dakika ofseti. Saniyeler alanının ondalık kısmı isteğe bağlıdır, ancak aslında orada olabileceği gerçeği, strptime() veya std::get_time()'u kullanamama nedenidir; dize.

+0

Öneriler sunmak için o tarih biçiminin nasıl göründüğüne bakmak zorunda mıyız? –

+2

C++ 11'i kullanabilir misiniz? [std :: get_time] (http://en.cppreference.com/w/cpp/io/manip/get_time) – Barry

+0

Ne hakkında ['strptime()'] (http://linux.die.net/man/ 3/strptime)? – cdhowie

cevap

11

Sen C 'ın sscanf (http://www.cplusplus.com/reference/cstdio/sscanf/) bunu ayrıştırmak için kullanabilirsiniz: Eğer böyle çağrılabilir (http://www.cplusplus.com/reference/string/string/c_str/) std::string varsa

const char *dateStr = "2014-11-12T19:12:14.505Z"; 
int y,M,d,h,m; 
float s; 
sscanf(dateStr, "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s); 

:

std::string dateStr = "2014-11-12T19:12:14.505Z"; 
sscanf(dateStr.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s); 

kaldırabileceği olursa sscanf dönüş değeri kullanmanız gereken farklı saat dilimleri - ayrıştırılan bağımsız değişken sayısı:

int tzh = 0, tzm = 0; 
if (6 < sscanf(dateStr.c_str(), "%d-%d-%dT%d:%d:%f%d:%dZ", &y, &M, &d, &h, &m, &s, &tzh, &tzm)) { 
    if (tzh < 0) { 
     tzm = -tzm; // Fix the sign on minutes. 
    } 
} 

Ve sonra tm (http://www.cplusplus.com/reference/ctime/tm/) yapı doldurabilirsiniz:

tm time; 
time.tm_year = y - 1900; // Year since 1900 
time.tm_mon = M - 1;  // 0-11 
time.tm_mday = d;  // 1-31 
time.tm_hour = h;  // 0-23 
time.tm_min = m;   // 0-59 
time.tm_sec = (int)s; // 0-61 (0-60 in C++11) 
Ayrıca ile yapılabilir

std::get_time (http://en.cppreference.com/w/cpp/io/manip/get_time) @Barry yaşlı için yorumda how do I parse an iso 8601 date (with optional milliseconds) to a struct tm in C++?

+0

dateStr bir struct tm veren bir dize literal için bir işaretçi ve bu nedenle tür ** const ** char * olmalıdır. – antred

+0

@antred emin, benim hatam! – k06a

+0

Bu, saat diliminin soruda belirtildiği gibi her zaman olamayacağı "Z" olacağını varsayar. –

8

Yeni cevap belirtildiği gibi C++11 beri soru. Gerekçe: güncellenmiş araçlar.

Bu free, open source library'u kullanarak, bir tm üzerinde milisaniye hassasiyetini tutabilmenin avantajına sahip olan bir std::chrono::time_point<system_clock, milliseconds>'a ayrılabilir. Ve gerçekten ihtiyacınız varsa, system_clock::to_time_t aracılığıyla C API'sine devam edebilirsiniz (yol boyunca milisaniye kaybetme).

#include "date.h" 
#include <iostream> 
#include <sstream> 

date::sys_time<std::chrono::milliseconds> 
parse8601(std::istream&& is) 
{ 
    std::string save; 
    is >> save; 
    std::istringstream in{save}; 
    date::sys_time<std::chrono::milliseconds> tp; 
    in >> date::parse("%FT%TZ", tp); 
    if (in.fail()) 
    { 
     in.clear(); 
     in.exceptions(std::ios::failbit); 
     in.str(save); 
     in >> date::parse("%FT%T%Ez", tp); 
    } 
    return tp; 
} 

int 
main() 
{ 
    using namespace date; 
    using namespace std; 
    cout << parse8601(istringstream{"2014-11-12T19:12:14.505Z"}) << '\n'; 
    cout << parse8601(istringstream{"2014-11-12T12:12:14.505-5:00"}) << '\n'; 
} 

Bu çıkışlar:

2014-11-12 19:12:14.505 
2014-11-12 17:12:14.505 

Not hem çıkışlar UTC olduğunu. parse yerel saatini -5:00 kaymasını kullanarak UTC'ye dönüştürdü. Gerçek zamanlı olarak yerel saatini istiyorsanız, ayrıştırmak ancak ofseti yoksaymak üzere date::local_time<milliseconds> adlı bir türe ayrıştırmanın bir yolu da vardır. Biri, ofseti, istenirse (aşırı yüklenme ile minutes& kullanarak) chrono::minutes'a ayrıştırabilir.

Ayrıştırmanın kesinliği, biçim dizesindeki bayraklar yerine, girdiğiniz chrono::time_point hassaslığı tarafından denetlenir. Ve ofset, %z ile +/-hhmm veya %Ez ile +/-[h]h:mm biçiminde olabilir.

+2

Gerçekten yararlı. Kitaplığınızdan çok etkilendim. Umarım gelecekteki bir standart haline getirecektir! – stj

0

İlk başta sscanf() yoluna giderken, IDE'yi CLion'a geçirdikten sonra, sscanf()'u değiştirmek için std::strtol() işlevinin kullanılmasını önerdi.

Bunun, yalnızca sscanf() sürümü ile aynı sonucu elde etmenin bir örneği olduğunu unutmayın. Her şekilde daha kısa, evrensel ve doğru olmak değil, herkesi "saf C++ çözümü" yönüne işaret etmek. Bir API'dan aldığım ve henüz evrensel olmayan zaman damgası dizgilerine dayanıyor (benim durumumun YYYY-MM-DDTHH:mm:ss.sssZ biçimini kullanma ihtiyacı var), farklı olanları işlemek için kolayca değiştirilebilir.

Kodu göndermeden önce, std::strtol() kullanmadan önce yapılması gereken bir şey var: dizginin kendisini temizleme, böylece basamaklı olmayan tüm işaretleri kaldırma ("-", ":", "T", "Z") , "."), çünkü std::strtol() olmadan rakamları yanlış yoldan ayrıştırırsınız (negatif ay veya gün değerleri olmadan bitebilirsiniz).

Bu küçük snippet, bir ISO-8601 dizesini (yukarıda belirtildiği gibi, yukarıda belirtildiği gibi) alır ve bunu milisaniye cinsinden epoch süresini temsil eden std::time_t sonucuna dönüştürür. Buradan std::chrono-type nesnesine gitmek oldukça kolaydır.

std::time_t parseISO8601(const std::string &input) 
{ 
    // prepare the data output placeholders 
    struct std::tm time = {0}; 
    int millis; 

    // string cleaning for strtol() - this could be made cleaner, but for the sake of the example itself... 
    std::string cleanInput = input 
     .replace(4, 1, 1, ' ') 
     .replace(7, 1, 1, ' ') 
     .replace(10, 1, 1, ' ') 
     .replace(13, 1, 1, ' ') 
     .replace(16, 1, 1, ' ') 
     .replace(19, 1, 1, ' '); 

    // pointers for std::strtol() 
    const char* timestamp = cleanInput.c_str(); 
    // last parsing end position - it's where strtol finished parsing the last number found 
    char* endPointer; 
    // the casts aren't necessary, but I just wanted CLion to be quiet ;) 
    // first parse - start with the timestamp string, give endPointer the position after the found number 
    time.tm_year = (int) std::strtol(timestamp, &endPointer, 10) - 1900; 
    // next parses - use endPointer instead of timestamp (skip the part, that's already parsed) 
    time.tm_mon = (int) std::strtol(endPointer, &endPointer, 10) - 1; 
    time.tm_mday = (int) std::strtol(endPointer, &endPointer, 10); 
    time.tm_hour = (int) std::strtol(endPointer, &endPointer, 10); 
    time.tm_min = (int) std::strtol(endPointer, &endPointer, 10); 
    time.tm_sec = (int) std::strtol(endPointer, &endPointer, 10); 
    millis = (int) std::strtol(endPointer, &endPointer, 10); 

    // convert the tm struct into time_t and then from seconds to milliseconds 
    return std::mktime(&time) * 1000 + millis; 
} 

Değil temiz ve en evrensel, ama iş sscanf() gibi C tarzı fonksiyonları başvurmadan bitmiş olur.

İlgili konular