Mostrar la fecha de compilación

Un dato importante cuando se está investigando un fallo o probando una nueva funcionalidad en un software es saber sobre qué versión del código estamos trabajando. Además del número de versión (más adecuado para versiones públicas) o el número de compilación, una forma de identificar la versión actual del código es usando la fecha de compilación del mismo. Este dato tiene varias ventajas inherentes: no necesita de ningún contador que haya que incrementar, viene de fábrica, y es natural usarlo tanto en conversaciones (en la compilación de hace dos días…) como al buscar el commit relacionado.

C y C++ proveen de tres macros relacionadas que son de utilidad:

  • __DATE__: devuelve la fecha de compilación del fichero en formato mmm dd yyyy (por ejemplo: Aug 17 2020).
  • __TIME__: devuelve la hora de compilación del fichero en formato hh:mm:ss (por ejemplo: 08:31:45).
  • __TIMESTAMP__: devuelve la fecha y hora de modificación del fichero en formato Ddd Mmm Date hh:mm:ss yyyy (por ejemplo Mon Aug 17 08:31:45 2020).

Nótese que __TIMESTAMP__ es la fecha/hora de modificación del fichero, mientas que __DATE__ / __TIME__ muestran la fecha/hora en la que se compiló (una explicación extendida puede encontrarse en Stack Overflow). Usualmente nos interesa es tener una referencia de los cambios, por lo que la fecha de modificación puede ser suficiente (de ahí que generalice el uso de fecha de compilación). Siempre podemos obligar a que el fichero se modifique como un paso de pre-build, por ejemplo mediante un touch.

Para una inspección rápida simplemente debemos mostrarlos cuando consideremos adecuado:

std::cout << "Source timestamp: " << __TIMESTAMP__ << '\n';

Ahora bien, para trazas más elaboradas, tales como mensajes al usuario que deban estar traducidos o simplemente para dejarlos en el formato estándar de fecha de nuestra empresa.

La siguiente función extrae los componentes de fecha y hora y construye una nueva cadena de texto en base al formato presentado (los especificadores de formato son estándar y están listados acá). Dejo al lector la modificación del código para usar __DATE__ y __TIME__ en lugar de __TIMESTAMP__.

std::string getFormattedBuildDatetime(const std::string &format)
{
  // Build timestamp, uses Boost to allow conversion from the compiler
  // __TIMESTAMP__ string to a custom format
  namespace bt = boost::posix_time;

  // Normalize timestamp
  std::string timestamp(__TIMESTAMP__);
  const auto it_dblspace = timestamp.find("  ");
  if (it_dblspace != std::string::npos) { // replace day with leading space by
                                          // day with leading zero
    timestamp.replace(it_dblspace, 2, " 0");
  }

  // Extract individual components
  std::istringstream is(timestamp);
  const std::locale ts_locale(std::locale::classic(), new bt::time_input_facet("%a %b %d %H:%M:%S %Y"));
  is.imbue(ts_locale);
  bt::ptime pt;
  is >> pt;

  // Format output
  std::ostringstream os;
  os.imbue(std::locale(std::locale::classic(), new bt::time_facet(format.c_str())));
  os << pt;

  return os.str();
}

Ejemplo de uso:

std::cout << "Formatted: " << getFormattedBuildDatetime("%Y/%m/%d %H:%M:%S") << '\n';

Un ejemplo completo puede encontrarse en GitHub.

Créditos

La imagen del reloj de la cabecera es obra de Prosymbols, en www.flaticon.es.