// ACCESSORS
void RecordStringFormatter::operator()(bsl::ostream& stream,
                                       const Record& record) const

{
    const RecordAttributes& fixedFields = record.fixedFields();
    bdlt::Datetime                timestamp   = fixedFields.timestamp();

    if (k_ENABLE_PUBLISH_IN_LOCALTIME ==
                                       d_timestampOffset.totalMilliseconds()) {
        int localTimeOffsetInSeconds =
            bdlt::LocalTimeOffset::localTimeOffset(timestamp).totalSeconds();
        timestamp.addSeconds(localTimeOffsetInSeconds);
    } else if(k_DISABLE_PUBLISH_IN_LOCALTIME ==
                                       d_timestampOffset.totalMilliseconds()) {
        // Do not adjust 'timestamp'.
    } else {
        timestamp += d_timestampOffset;
    }

    // Step through the format string, outputting the required elements.

    const char* iter = d_formatSpec.data();
    const char* end  = iter + d_formatSpec.length();

    // Create a buffer on the stack for formatting the record.  Note that the
    // size of the buffer should be slightly larger than the amount we reserve
    // in order to ensure only a single allocation occurs.

    const int BUFFER_SIZE        = 512;
    const int STRING_RESERVATION = BUFFER_SIZE -
                                   bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT;

    char fixedBuffer[BUFFER_SIZE];
    bdlma::BufferedSequentialAllocator stringAllocator(fixedBuffer,
                                                      BUFFER_SIZE);
    bsl::string output(&stringAllocator);
    output.reserve(STRING_RESERVATION);

#if defined(BSLS_PLATFORM_CMP_MSVC)
#define snprintf _snprintf
#endif

    while (iter != end) {
        switch (*iter) {
          case '%': {
            if (++iter == end) {
                break;
            }
            switch (*iter) {
              case '%': {
                output += '%';
              } break;
              case 'd': {
                char buffer[32];
                timestamp.printToBuffer(buffer, sizeof buffer);

                output += buffer;
              } break;
              case 'I': // fall through intentionally
              case 'i': {
                // use ISO8601 "extended" format

                char buffer[32];

                snprintf(buffer,
                         sizeof(buffer),
                         "%04d-%02d-%02dT%02d:%02d:%02d",
                         timestamp.year(),
                         timestamp.month(),
                         timestamp.day(),
                         timestamp.hour(),
                         timestamp.minute(),
                         timestamp.second());
                output += buffer;

                if ('I' == *iter) {
                    snprintf(buffer,
                             sizeof(buffer),
                             ".%03d",
                             timestamp.millisecond());

                    output += buffer;
                }

                if (0 == d_timestampOffset.totalMilliseconds()) {
                    output += 'Z';
                }
              } break;
              case 'p': {
                appendToString(&output, fixedFields.processID());
              } break;
              case 't': {
                appendToString(&output, fixedFields.threadID());
              } break;
              case 's': {
                output += Severity::toAscii(
                                 (Severity::Level)fixedFields.severity());
              } break;
              case 'f': {
                output += fixedFields.fileName();
              } break;
              case 'F': {
                const bsl::string& filename = fixedFields.fileName();
                bsl::string::size_type rightmostSlashIndex =
#ifdef BSLS_PLATFORM_OS_WINDOWS
                    filename.rfind('\\');
#else
                    filename.rfind('/');
#endif
                if (bsl::string::npos == rightmostSlashIndex) {
                    output += filename;
                }
                else {
                    output += filename.substr(rightmostSlashIndex + 1);
                }
              } break;
              case 'l': {
                appendToString(&output, fixedFields.lineNumber());
              } break;
              case 'c': {
                output += fixedFields.category();
              } break;
              case 'm': {
                bslstl::StringRef message = fixedFields.messageRef();
                output.append(message.data(), message.length());
              } break;
              case 'x': {
                bsl::stringstream ss;
                int length = fixedFields.messageStreamBuf().length();
                bdlb::Print::printString(ss,
                                        fixedFields.message(),
                                        length,
                                        false);
                output += ss.str();
              } break;
              case 'X': {
                bsl::stringstream ss;
                int length = fixedFields.messageStreamBuf().length();
                bdlb::Print::singleLineHexDump(ss,
                                              fixedFields.message(),
                                              length);
                output += ss.str();
              } break;
              case 'u': {
                typedef ball::UserFields Values;
                const Values& userFields = record.userFields();
                const int numUserFields  = userFields.length();

                if (numUserFields > 0) {
                    bsl::stringstream ss;
                    Values::ConstIterator it = userFields.begin();
                    ss << *it;
                    ++it;
                    for (; it != userFields.end(); ++it) {
                        ss << " " << *it;
                    }
                    output += ss.str();
                }
              } break;
              default: {
                // Undefined: we just output the verbatim characters.

                output += '%';
                output += *iter;
              }
            }
            ++iter;
          } break;
          case '\\': {
            if (++iter == end) {
                break;
            }
            switch (*iter) {
              case 'n': {
                output += '\n';
              } break;
              case 't': {
                output += '\t';
              } break;
              case '\\': {
                output += '\\';
              } break;
              default: {
                // Undefined: we just output the verbatim characters.

                output += '\\';
                output += *iter;
              }
            }
            ++iter;
          } break;
          default: {
            output += *iter;
            ++iter;
          }
        }
    }

#if defined(BSLS_PLATFORM_CMP_MSVC)
#undef snprintf
#endif

    stream.write(output.c_str(), output.size());
    stream.flush();

}
// ACCESSORS
void RecordStringFormatter::operator()(bsl::ostream& stream,
                                       const Record& record) const

{
    const RecordAttributes& fixedFields = record.fixedFields();
    bdlt::DatetimeInterval  offset;

    if (k_ENABLE_PUBLISH_IN_LOCALTIME ==
                                       d_timestampOffset.totalMilliseconds()) {
        bsls::Types::Int64 localTimeOffsetInSeconds =
            bdlt::LocalTimeOffset::localTimeOffset(
                                       fixedFields.timestamp()).totalSeconds();
        offset.setTotalSeconds(localTimeOffsetInSeconds);
    } else if(k_DISABLE_PUBLISH_IN_LOCALTIME ==
                                       d_timestampOffset.totalMilliseconds()) {
        // Do not adjust 'offset'.
    } else {
        offset = d_timestampOffset;
    }

    bdlt::DatetimeTz timestamp(fixedFields.timestamp() + offset,
                               static_cast<int>(offset.totalMinutes()));

    // Step through the format string, outputting the required elements.

    const char* iter = d_formatSpec.data();
    const char* end  = iter + d_formatSpec.length();

    // Create a buffer on the stack for formatting the record.  Note that the
    // size of the buffer should be slightly larger than the amount we reserve
    // in order to ensure only a single allocation occurs.

    const int BUFFER_SIZE        = 512;
    const int STRING_RESERVATION = BUFFER_SIZE -
                                   bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT;

    char fixedBuffer[BUFFER_SIZE];
    bdlma::BufferedSequentialAllocator stringAllocator(fixedBuffer,
                                                      BUFFER_SIZE);
    bsl::string output(&stringAllocator);
    output.reserve(STRING_RESERVATION);

#if defined(BSLS_PLATFORM_CMP_MSVC)
#define snprintf _snprintf
#endif

    while (iter != end) {
        switch (*iter) {
          case '%': {
            if (++iter == end) {
                break;
            }
            switch (*iter) {
              case '%': {
                output += '%';
              } break;
              case 'd': // fall through intentionally
              case 'D': {
                const int fractionalSecondPrecision = 'd' == *iter ? 3 : 6;

                char buffer[32];
                timestamp.localDatetime().printToBuffer(
                                                    buffer,
                                                    sizeof buffer,
                                                    fractionalSecondPrecision);

                output += buffer;
              } break;
              case 'I':                                         // FALL THROUGH
              case 'O':                                         // FALL THROUGH
              case 'i': {
                // Use ISO8601 "extended" format.

                const int fractionalSecondPrecision = 'O' == *iter ? 6 : 3;

                bdlt::Iso8601UtilConfiguration config;
                config.setFractionalSecondPrecision(fractionalSecondPrecision);
                config.setUseZAbbreviationForUtc(true);

                char buffer[bdlt::Iso8601Util::k_DATETIMETZ_STRLEN + 1];

                int outputLength = bdlt::Iso8601Util::generateRaw(buffer,
                                                                  timestamp,
                                                                  config);

                if ('i' == *iter) {
                    // Remove milliseconds part.

                    enum { k_DECIMAL_SIGN_OFFSET = 19,
                           k_TZINFO_OFFSET       = k_DECIMAL_SIGN_OFFSET + 4 };
                    bslstl::StringRef head(buffer, k_DECIMAL_SIGN_OFFSET);

                    bslstl::StringRef tail(buffer + k_TZINFO_OFFSET,
                                           outputLength - k_TZINFO_OFFSET);

                    output += head + tail;
                }
                else {
                    output.append(buffer, outputLength);
                }
              } break;
              case 'p': {
                appendToString(&output, fixedFields.processID());
              } break;
              case 't': {
                appendToString(&output, fixedFields.threadID());
              } break;
              case 's': {
                output += Severity::toAscii(
                                 (Severity::Level)fixedFields.severity());
              } break;
              case 'f': {
                output += fixedFields.fileName();
              } break;
              case 'F': {
                const bsl::string& filename = fixedFields.fileName();
                bsl::string::size_type rightmostSlashIndex =
#ifdef BSLS_PLATFORM_OS_WINDOWS
                    filename.rfind('\\');
#else
                    filename.rfind('/');
#endif
                if (bsl::string::npos == rightmostSlashIndex) {
                    output += filename;
                }
                else {
                    output += filename.substr(rightmostSlashIndex + 1);
                }
              } break;
              case 'l': {
                appendToString(&output, fixedFields.lineNumber());
              } break;
              case 'c': {
                output += fixedFields.category();
              } break;
              case 'm': {
                bslstl::StringRef message = fixedFields.messageRef();
                output.append(message.data(), message.length());
              } break;
              case 'x': {
                bsl::stringstream ss;
                int length = static_cast<int>(
                                      fixedFields.messageStreamBuf().length());
                bdlb::Print::printString(ss,
                                        fixedFields.message(),
                                        length,
                                        false);
                output += ss.str();
              } break;
              case 'X': {
                bsl::stringstream ss;
                int length = static_cast<int>(
                                      fixedFields.messageStreamBuf().length());
                bdlb::Print::singleLineHexDump(ss,
                                              fixedFields.message(),
                                              length);
                output += ss.str();
              } break;
              case 'u': {
                typedef ball::UserFields Values;
                const Values& customFields = record.customFields();
                const int numCustomFields  = customFields.length();

                if (numCustomFields > 0) {
                    bsl::stringstream ss;
                    Values::ConstIterator it = customFields.begin();
                    ss << *it;
                    ++it;
                    for (; it != customFields.end(); ++it) {
                        ss << " " << *it;
                    }
                    output += ss.str();
                }
              } break;
              default: {
                // Undefined: we just output the verbatim characters.

                output += '%';
                output += *iter;
              }
            }
            ++iter;
          } break;
          case '\\': {
            if (++iter == end) {
                break;
            }
            switch (*iter) {
              case 'n': {
                output += '\n';
              } break;
              case 't': {
                output += '\t';
              } break;
              case '\\': {
                output += '\\';
              } break;
              default: {
                // Undefined: we just output the verbatim characters.

                output += '\\';
                output += *iter;
              }
            }
            ++iter;
          } break;
          default: {
            output += *iter;
            ++iter;
          }
        }
    }

#if defined(BSLS_PLATFORM_CMP_MSVC)
#undef snprintf
#endif

    stream.write(output.c_str(), output.size());
    stream.flush();

}