Home > other >  C Win32 Padding zeros to an int via to_wstring
C Win32 Padding zeros to an int via to_wstring

Time:02-08

I am trying to learn C Win32. I built a timer class for a game environment. Typical game timer and it works well. I have this function that I would like to display the current game time with.

std::wstring Time::DisplayGameTime()
{
    std::wstring Label = L"Current Time: ";

    std::wstring daysText = L" "   std::to_wstring(Days()); //Returns int
    std::wstring monthsText = L", "   std::to_wstring(Months()); //Returns int
    std::wstring yearsText = L", "   std::to_wstring(Years()); // Returns int
    std::wstring hoursText = L" "   std::to_wstring(Hours()); // Returns int
    std::wstring minutesText = L" : "   std::to_wstring(Minutes()); // Returns int

    std::wstring message = Label   daysText   monthsText   yearsText   hoursText   minutesText;
    return message;
}

This works well too, except the minutes integer only prints one digit until it reaches 10. I would like to pad a leading zero to it like you would find in a normal time clock. Since I am using std::to_wstring, I cannot use format specifiers like in a swprintf_s buffer. I have tried to figure out how to use a wstringstream for this but none of what I've tried has worked.

The goal is to use this function to convert to LPCWSTR later on for DrawText to display to the Window.

CodePudding user response:

You can use the traditional imperative "brute-force" way of solving this:

int DaysVal = Days();
std::wstring W = (DaysVal < 10 ? L"0" : L"")   std::to_wstring(DaysVal);

Or.. use std::stringstream with std::setw and std::setfill (from <iomanip>).

std::wstringstream WS;
WS << std::setw(2) << std::setfill(L'0') << Days();
std::wstring W = WS.str();

PS: Features from <iomanip> also work on std::cout and other streams, so std::(w)stringstream is unnecessary with I/O!

EDIT: And if someone's looking for a solution utilizing snprintf():

char CB[128];
std::snprintf(CB, sizeof(CB), "%.*i", NumDigits, Num);

Using precision (%.*i) is superior to using width (%*i), since the second one operates on total of characters and not number of digits!

EDIT(2): To be future-proof, I'm also including a solution using std::format():

I haven't played with {fmt} for too long, so I have no idea how to achieve the same result as with printf(). For now, it should suffice (but expect to have one digit less with negative numbers):

std::wstring W = fmt::format(L"{: 0{}}", Num, NumDigits);

CodePudding user response:

Writing your output to a std::wstringstream object is very similar to writing to the std::wcout stream; you use consecutive calls to the << operator; then, when you are finished, you can get a copy of the underlying wstring object using the .str() member function.

You can control the width and padding character for each field, individually, using the std::setw and std::setfill functions – you can put calls to these, if required, before each field whose output you want to adjust.

In your case, you can use the default formats for all fields except the last, where you would set the width to 2 and the fill character to L'0':

#include <sstream>
#include <string>
#include <iomanip>

std::wstring Time::DisplayGameTime()
{
    std::wstringstream message;
    message
        << L"Current Time: "
        // D,M,Y,H using default format ...
        << L" " << Days()
        << L", " << Months()
        << L", " << Years()
        << L" " << Hours()
        // Add Minute using zero-padded, fixed width of 2 ...
        << L" : " << std::setw(2) << std::setfill(L'0') << Minutes();
    return message.str();
}

CodePudding user response:

As you discovered, to_wstring doesn't allow you to provide formatting options. If you want to keep using to_wstring you'll have to apply the formatting yourself. There are many options to do so, so I'll just pick one at random:

  • Always prepend a "0"
  • Then return the right-most 2 digits

Something like this:

auto const minutesText = []() {
    // Always prepend a "0"
    auto const minute { L"0"   std::to_wstring(Minutes()) };
    // Return the right-most 2 digits
    return minute.substr(minute.length() - 2);
}();

If you can use C 20 things are far easier, and usually a lot faster, too. C 20 introduced a formatting library that combines a succinct formatting description language (similar to Python's) with the type safety of C ' I/O streams.

It makes the following implementation possible:

#include <format>

std::wstring Time::DisplayGameTime()
{
    return std::format(L"Current Time:  {}, {}, {} {} : {:02}",
                       Days(), Months(), Years(),
                       Hours(), Minutes());
}

std::format is similar to the printf family of functions in taking a formatting string, plus zero or more arguments to be interpolated. The formatting string describes the final output as well as where the arguments are placed and how they are formatted.

The first four placeholders do not contain any formatting specification, and produce results similar to what to_wstring would have. The final placeholder {:02} encodes both the field width (2) as well as the fill character (0) to use when the output doesn't produce enough characters. It could have been spelled {:0>2} to specify right-aligned text, but that is the default integer values so I omitted it.

Using the C 20 formatting library requires a C 20 compiler (that implements it). Visual Studio 2019 and Visual Studio 2022 do. Either one requires setting the /std:c -latest compiler option. At this time, the C 20 formatting library does not appear under the /std:c 20 compiler option. The C Language Standard can be set from the IDE as well.


Bonus material: Why does std::format do that? - Charlie Barto - CppCon 2021: A look at Microsoft's implementation that—among other things—explains why std::format is orders of magnitude faster than I/O streams. This is not introductory material.

  •  Tags:  
  • Related