2013-05-27 14 views
11

Desidero stampare o estrarre i valori di anno/mese/giorno.Come convertire std :: chrono :: time_point in std :: tm senza utilizzare time_t?

Non voglio utilizzare time_t a causa del problema dell'anno 2038, ma tutti gli esempi che ho trovato su Internet lo utilizzano per convertire time_point in tm.

C'è un modo semplice per convertire da time_point a tm (preferibilmente senza boost)?


Un'implementazione come timesub da libc sarebbe stata la mia ultima risorsa: http://www.opensource.apple.com/source/Libc/Libc-262/stdtime/localtime.c


Edit: Dopo aver letto i link proposti e fare un po 'più ricerca, sono venuto a la seguente conclusione.

  1. Utilizzare time_t dove è lungo 64 bit è ok (per la maggior parte degli scopi).
  2. Utilizzo di Boost.Date_Time per il codice portatile.

È interessante notare che Boost.Date_Time può essere una libreria di sola intestazione. Fonte: http://www.boost.org/doc/libs/1_53_0/more/getting_started/unix-variants.html#header-only-libraries

+0

Una domanda correlata [Cosa dovremmo fare per preparare il 2038?] (Http://stackoverflow.com/q/36239/341970) – Ali

+0

Un secondo problema con il metodo di passare attraverso 'time_t' è che tutto il le funzioni standard per la conversione di 'time_t' in' tm' non sono thread-safe. –

risposta

2

Non c'è niente per supportare le date del calendario nella Libreria standard a parte le funzioni della libreria C basate su time_t.

opzioni sono, in ordine di mia preferenza:

  • Boost.Date_Time, che si dice si preferisce evitare
  • Alcuni altri di terze parti biblioteca data/ora (non ho un raccomandazioni dal momento che mi piacerebbe utilizzare Boost)
  • Modificare un'implementazione open-source di gmtime()
  • Usa this algorithm, dopo aver verificato che sia corretta.
20

Risposta aggiornata con algoritmi migliori, collegamento alla descrizione dettagliata degli algoritmi e conversione completa in std::tm.


Vorrei stampare o valori di anno/mese/giorno di estratto. C'è un modo semplice per convertire da time_point a tm (preferibilmente senza boost)?

La prima cosa da notare è che std::chrono::time_point è basato su modelli non solo sulla duration, ma anche sull'orologio. L'orologio implica un'epoca. E diversi orologi possono avere epoche diverse.

Ad esempio, sul mio sistema, std::chrono::high_resolution_clock e std::chrono::steady_clock hanno un'epoca di: ogni volta che il computer si avvia.Se non si conosce l'ora di avvio del computer, non c'è modo di convertire tale time_point in qualsiasi sistema di calendario.

Detto questo, si erano probabilmente parlando solo di std::chrono::system_clock::time_point, in quanto questo time_point, e solo questo time_point, è necessario per avere un rapporto deterministico con la (gregoriano) calendario civile.

Come risulta, ogni implementazione di std::chrono::system_clock di cui sono a conoscenza sta utilizzando unix time. Questo ha un'epoca di Capodanno 1970 trascurando secondi bisestili.

Questo non è garantito dallo standard. Tuttavia si può approfittare di questo fatto se si vuole con le seguenti formule disponibili all'indirizzo:

chrono-Compatible Low-Level Date Algorithms

Prima di tutto, di avvertimento, sto usando l'ultimo C++ 1A progetto, che include fantastici nuovi strumenti constexpr. Se hai bisogno di annullare alcuni degli attributi constexpr per il tuo compilatore, fallo semplicemente.

Dato gli algoritmi trovate al link qui sopra, è possibile in grado di convertire un std::chrono::time_point<std::chrono::system_clock, Duration> ad un std::tm, senza utilizzare time_t con la seguente funzione:

template <class Duration> 
std::tm 
make_utc_tm(std::chrono::time_point<std::chrono::system_clock, Duration> tp) 
{ 
    using namespace std; 
    using namespace std::chrono; 
    typedef duration<int, ratio_multiply<hours::period, ratio<24>>> days; 
    // t is time duration since 1970-01-01 
    Duration t = tp.time_since_epoch(); 
    // d is days since 1970-01-01 
    days d = round_down<days>(t); 
    // t is now time duration since midnight of day d 
    t -= d; 
    // break d down into year/month/day 
    int year; 
    unsigned month; 
    unsigned day; 
    std::tie(year, month, day) = civil_from_days(d.count()); 
    // start filling in the tm with calendar info 
    std::tm tm = {0}; 
    tm.tm_year = year - 1900; 
    tm.tm_mon = month - 1; 
    tm.tm_mday = day; 
    tm.tm_wday = weekday_from_days(d.count()); 
    tm.tm_yday = d.count() - days_from_civil(year, 1, 1); 
    // Fill in the time 
    tm.tm_hour = duration_cast<hours>(t).count(); 
    t -= hours(tm.tm_hour); 
    tm.tm_min = duration_cast<minutes>(t).count(); 
    t -= minutes(tm.tm_min); 
    tm.tm_sec = duration_cast<seconds>(t).count(); 
    return tm; 
} 

anche notare che il std::chrono::system_clock::time_point su tutte le implementazioni esistenti è una durata nel fuso orario UTC (trascurando secondi intercalari). Se si desidera convertire time_point utilizzando un altro fuso orario, sarà necessario aggiungere/sottrarre l'offset di durata del fuso orario a std::chrono::system_clock::time_point prima di convertirlo in una precisione di days. Se si desidera inoltre tenere conto dei secondi bisestili, regolare il numero appropriato di secondi prima del troncamento a days utilizzando this table e sapere che l'ora di unix è allineata con UTC ora.

Questa funzione può essere verificata con:

#include <iostream> 
#include <iomanip> 

void 
print_tm(const std::tm& tm) 
{ 
    using namespace std; 
    cout << tm.tm_year+1900; 
    char fill = cout.fill(); 
    cout << setfill('0'); 
    cout << '-' << setw(2) << tm.tm_mon+1; 
    cout << '-' << setw(2) << tm.tm_mday; 
    cout << ' '; 
    switch (tm.tm_wday) 
    { 
    case 0: 
     cout << "Sun"; 
     break; 
    case 1: 
     cout << "Mon"; 
     break; 
    case 2: 
     cout << "Tue"; 
     break; 
    case 3: 
     cout << "Wed"; 
     break; 
    case 4: 
     cout << "Thu"; 
     break; 
    case 5: 
     cout << "Fri"; 
     break; 
    case 6: 
     cout << "Sat"; 
     break; 
    } 
    cout << ' '; 
    cout << ' ' << setw(2) << tm.tm_hour; 
    cout << ':' << setw(2) << tm.tm_min; 
    cout << ':' << setw(2) << tm.tm_sec << " UTC."; 
    cout << setfill(fill); 
    cout << " This is " << tm.tm_yday << " days since Jan 1\n"; 
} 

int 
main() 
{ 
    print_tm(make_utc_tm(std::chrono::system_clock::now())); 
} 

che per me attualmente stampa i:

2013-09-15 18:16:50 UTC Sun. Si tratta di 257 giorni dal gennaio 1

In caso chrono-Compatible Low-Level Date Algorithms va offline, o viene spostato, qui ci sono gli algoritmi utilizzati in make_utc_tm. Ci sono spiegazioni approfondite di questi algoritmi al link sopra. Sono ben testati e hanno una gamma straordinariamente ampia di validità.

// Returns number of days since civil 1970-01-01. Negative values indicate 
// days prior to 1970-01-01. 
// Preconditions: y-m-d represents a date in the civil (Gregorian) calendar 
//     m is in [1, 12] 
//     d is in [1, last_day_of_month(y, m)] 
//     y is "approximately" in 
//     [numeric_limits<Int>::min()/366, numeric_limits<Int>::max()/366] 
//     Exact range of validity is: 
//     [civil_from_days(numeric_limits<Int>::min()), 
//     civil_from_days(numeric_limits<Int>::max()-719468)] 
template <class Int> 
constexpr 
Int 
days_from_civil(Int y, unsigned m, unsigned d) noexcept 
{ 
    static_assert(std::numeric_limits<unsigned>::digits >= 18, 
      "This algorithm has not been ported to a 16 bit unsigned integer"); 
    static_assert(std::numeric_limits<Int>::digits >= 20, 
      "This algorithm has not been ported to a 16 bit signed integer"); 
    y -= m <= 2; 
    const Int era = (y >= 0 ? y : y-399)/400; 
    const unsigned yoe = static_cast<unsigned>(y - era * 400);  // [0, 399] 
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1; // [0, 365] 
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;   // [0, 146096] 
    return era * 146097 + static_cast<Int>(doe) - 719468; 
} 

// Returns year/month/day triple in civil calendar 
// Preconditions: z is number of days since 1970-01-01 and is in the range: 
//     [numeric_limits<Int>::min(), numeric_limits<Int>::max()-719468]. 
template <class Int> 
constexpr 
std::tuple<Int, unsigned, unsigned> 
civil_from_days(Int z) noexcept 
{ 
    static_assert(std::numeric_limits<unsigned>::digits >= 18, 
      "This algorithm has not been ported to a 16 bit unsigned integer"); 
    static_assert(std::numeric_limits<Int>::digits >= 20, 
      "This algorithm has not been ported to a 16 bit signed integer"); 
    z += 719468; 
    const Int era = (z >= 0 ? z : z - 146096)/146097; 
    const unsigned doe = static_cast<unsigned>(z - era * 146097);   // [0, 146096] 
    const unsigned yoe = (doe - doe/1460 + doe/36524 - doe/146096)/365; // [0, 399] 
    const Int y = static_cast<Int>(yoe) + era * 400; 
    const unsigned doy = doe - (365*yoe + yoe/4 - yoe/100);    // [0, 365] 
    const unsigned mp = (5*doy + 2)/153;         // [0, 11] 
    const unsigned d = doy - (153*mp+2)/5 + 1;        // [1, 31] 
    const unsigned m = mp + (mp < 10 ? 3 : -9);       // [1, 12] 
    return std::tuple<Int, unsigned, unsigned>(y + (m <= 2), m, d); 
} 

template <class Int> 
constexpr 
unsigned 
weekday_from_days(Int z) noexcept 
{ 
    return static_cast<unsigned>(z >= -4 ? (z+4) % 7 : (z+5) % 7 + 6); 
} 

template <class To, class Rep, class Period> 
To 
round_down(const std::chrono::duration<Rep, Period>& d) 
{ 
    To t = std::chrono::duration_cast<To>(d); 
    if (t > d) 
     --t; 
    return t; 
} 

Aggiornamento

Più di recente ho avvolto gli algoritmi di cui sopra fino in una libreria data/ora liberamente disponibile documented and available here. Questa libreria semplifica l'estrazione di un anno/mese/giorno da std::system_clock::time_point e persino di ore: minuti: secondi: secondi frazionari. E tutto senza passare per time_t.

Ecco un semplice programma che utilizza la libreria di testa-solo al di sopra di stampare la data e l'ora corrente nel fuso orario UTC, alla precisione di qualsiasi system_clock::time_point offerte (in questo caso microsecondi):

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


int 
main() 
{ 
    using namespace date; 
    using namespace std; 
    using namespace std::chrono; 
    auto const now = system_clock::now(); 
    auto const dp = time_point_cast<days>(now); 
    auto const date = year_month_day(dp); 
    auto const time = make_time(now-dp); 
    cout << date << ' ' << time << " UTC\n"; 
} 

che ha appena uscita per me:

2015-05-19 15:03:47.754002 UTC 

Questa libreria si trasforma in modo efficace std::chrono::system_clock::time_point in un tipo di data-ora facile da usare.

1

ho usato Howard Hinnant's date library per scrivere una funzione che converte da time_point a struct tm:

template <typename Clock, typename Duration> 
std::tm to_calendar_time(std::chrono::time_point<Clock, Duration> tp) 
{ 
    using namespace date; 
    auto date = floor<days>(tp); 
    auto ymd = year_month_day(date); 
    auto weekday = year_month_weekday(date).weekday_indexed().weekday(); 
    auto tod = make_time(tp - date); 
    days daysSinceJan1 = date - sys_days(ymd.year()/1/1); 

    std::tm result; 
    std::memset(&result, 0, sizeof(result)); 
    result.tm_sec = tod.seconds().count(); 
    result.tm_min = tod.minutes().count(); 
    result.tm_hour = tod.hours().count(); 
    result.tm_mday = unsigned(ymd.day()); 
    result.tm_mon = unsigned(ymd.month()) - 1u; // Zero-based! 
    result.tm_year = int(ymd.year()) - 1900; 
    result.tm_wday = unsigned(weekday); 
    result.tm_yday = daysSinceJan1.count(); 
    result.tm_isdst = -1; // Information not available 
    return result; 
} 

tal modo vengono saltate time_t con appostato Y2038 problema su sistemi a 32 bit. Questa funzione è stata conferita a questo GitHub wiki, dove spero che altri possano contribuire con altri utili esempi e ricette.

+0

Dov'è il fuso orario? Non riesco più a prendere nulla senza un fuso orario esplicito o locale troppo serio. – Lothar

+0

@Lothar, ho semplicemente pensato che fosse il momento in ora locale. –

Problemi correlati