LoginSignup
7
8

More than 5 years have passed since last update.

C++ & ISO8601 : 時刻文字列 "2016-01-25T15:30:15.123+09:00" を出力する例

Last updated at Posted at 2016-01-25

目的

  • C++ で ISO8601 形式の時刻文字列を出力する例を提示したい。
    • 1秒未満の値も扱いたい(ミリ秒、マイクロ秒など)
  • 当然クロスプラットフォームとして linux, osx, windows(mingw) などで同じ出力を得たい。

期待される出力例

ミリ秒まで出力させた場合には次のような出力を得たい。

2016-01-25T15:30:15.123+09:00

実装例

  • usagi/chrono/to_string_iso8601.hxx
/// @file
/// @brief time_point to iso8601 string converter

#pragma once

#ifdef _WIN32
#include "time_zone_difference.hxx"
#endif
#include <chrono>
#include <string>
#include <sstream>
#include <iomanip>
#include <cmath>
#include <iostream>

namespace usagi
{
  namespace chrono
  {
    // 実際の usagi ライブラリーでは少し細工をしているが、
    // この記事では std::chrono 互換の clock で default_clock を与える事を示すに留める
    using default_clock = std::chrono::system_clock;

#ifdef _WIN32
    // TDM-GCC-5.1.0 is not support %F and %T
    //   note: mingw is supported. but we cannot predicate TDM or not.
    constexpr auto format_date_time = "%Y-%m-%dT%H:%M:%S";
#else
    constexpr auto format_date_time = "%FT%T";
#endif
    constexpr auto format_time_zone = "%z";

    namespace detail
    {
      template
      < typename T = usagi::chrono::default_clock::time_point
      , typename U = std::chrono::seconds
      >
      auto get_sub_seccond_string( const T& time_point )
        -> std::string
      {
        constexpr auto ratio_in_real = static_cast< long double >( U::period::num ) / U::period::den;
        constexpr auto sub_second_digits = -std::log10( ratio_in_real );

        if ( sub_second_digits < 1 )
          return "";

        const auto full = std::chrono::duration_cast< std::chrono::duration< long double > >( time_point.time_since_epoch() );
        const auto sec  = std::chrono::duration_cast< std::chrono::seconds >( time_point.time_since_epoch() );

        const auto sub_second_in_real = ( full - sec ).count();

        std::stringstream s;
        s << std::fixed << std::setprecision( sub_second_digits ) << sub_second_in_real;
        constexpr auto in_dot = 1;
        return s.str().substr( in_dot );
      }
    }

    /// @brief time_point to iso8601 string
    /// @tparam T clock type
    /// @param t time_point
    /// @return iso8601 string
    template
    < typename TIME_POINT = default_clock::time_point
    , typename SECOND_UNIT = std::chrono::seconds
    >
    auto to_string_iso8601_gmt
    ( const TIME_POINT& t = TIME_POINT::clock::now()
    )
    -> std::string
    {
      using namespace std::chrono;
      const auto& ct = TIME_POINT::clock::to_time_t ( t );
      const auto gt = std::gmtime( &ct );
      std::stringstream r;
      r << std::put_time( gt, format_date_time )
        << detail::get_sub_seccond_string< TIME_POINT, SECOND_UNIT >( t )
        << "Z"
        ;
      return r.str();
    }

    template
    < typename TIME_POINT = default_clock::time_point
    , typename SECOND_UNIT = std::chrono::seconds
    >
    auto to_string_iso8601_jst
    ( const TIME_POINT& t = TIME_POINT::clock::now()
    )
    {
      using namespace std::chrono;
      const auto& ct = TIME_POINT::clock::to_time_t ( t );
      auto lt = *std::localtime( &ct );
#ifdef _WIN32
      // MSVC++ ( and mingw; MSVCRT ) put an invalid `%z` time zone string, then
      // write convertible code and it requred a time zone difference.
      const auto z = time_zone_difference();
#endif
      std::stringstream r;
      r << std::put_time( &lt, format_date_time )
        << detail::get_sub_seccond_string< TIME_POINT, SECOND_UNIT >( t )
#ifdef _WIN32
        << ( std::signbit( z.count() ) ? "-" : "+" )
        << std::setw( 2 ) << std::setfill( '0' )
        << std::to_string( std::abs( duration_cast< hours >( z ).count() ) )
        << ":"
        << std::setw( 2 ) << std::setfill( '0' )
        << std::to_string( std::abs( duration_cast< minutes >( z ).count() ) % minutes::period::den )
#else
        << std::put_time( &lt, format_time_zone )
#endif
        ;

      return r.str();
    }
  }
}
  • usagi/chrono/time_zone_difference.hxx
/// @file
/// @brief time zone difference

#pragma once

#include <chrono>
#include <ctime>

namespace usagi
{
  namespace chrono
  {
    template < typename T = std::chrono::minutes >
    static auto time_zone_difference()
    {
      const auto current_time = time ( 0 );
      auto local_tm = *gmtime ( &current_time );
      const auto utc_time = mktime ( &local_tm );
      return std::chrono::duration_cast< T >
        ( std::chrono::duration< double > ( difftime ( current_time, utc_time ) )
        );
    }
  }
}
  • to_time_t.hxx
/// @file
/// @brief time_point to time_t converter

#pragma once

#include <chrono>

namespace usagi
{
  namespace chrono
  {
    template < typename T >
    inline static auto to_time_t ( const T& t )
    {
      using std::chrono::system_clock;
      using std::chrono::duration_cast;
      const auto& dt = t - T::clock::now();
      const auto& et = system_clock::now() + duration_cast< system_clock::duration > ( dt );
      return std::time_t ( system_clock::to_time_t ( et ) );
    }
  }
}

使い方

#include <usagi/chrono/to_to_string_iso8601.hxx>
#include <iostream>

auto main() -> int
{
  using namespace usagi::chrono;
  using namespace std::chrono;
  // 現在時刻を GMT で表示
  std::cout << to_string_iso8601_gmt() << "\n";
  // 現在時刻を JST で表示
  std::cout << to_string_iso8601_jst() << "\n";
  // 123456789 秒後の time_point を明示的に渡す例
  std::cout << to_string_iso8601_jst( steady_clock::now() + seconds( 123456789 ) ) << "\n";
  // ミリ秒まで表示(正確かどうかは別の問題)
  std::cout << to_string_iso8601_gmt< high_resolution_clock::time_point, milliseconds >() << "\n";
  // マイクロ秒まで表示(正確かどうかは別の問題)
  std::cout << to_string_iso8601_jst< high_resolution_clock::time_point, microseconds >() << "\n";
  // ナノ秒まで表示(正確かどうかは別の問題)
  std::cout << to_string_iso8601_jst< high_resolution_clock::time_point, nanoseconds >() << "\n";
  // 秒未満の桁を1/100単位(2桁)まで表示(正確かどうかは別の問題)
  std::cout << to_string_iso8601_jst< high_resolution_clock::time_point, duration< double, std::ratio< 1, 100 > > >() << "\n";
}

result

2016-01-25T07:14:22Z
2016-01-25T16:14:22+0900
2019-12-24T13:47:31+0900
2016-01-25T07:14:22.561Z
2016-01-25T16:14:22.561232+0900
2016-01-25T16:14:22.561250137+0900
2016-01-25T16:14:22.56+0900

wandbox

Note

  • タイムゾーンの時刻差分を表示してくれるはずの %z がいわゆる windows 環境では %Z な挙動を示すのでクロスプラットフォーム対象に windows なやつがいると地味にコストがかかる。
  • ついで、いわゆる windows 環境では %F %T も利かないので地味にこまごまと書く手間がかかる。
  • ↑↑こうしたいわゆる windows 環境の問題は MSVCRT を使う場合(mingw経由も含め)に発症するようだ。
7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8