What whould you expect from
string.format("%02c",string.byte"-") or
string.format("%02c",string.byte"+")?
zero-padding on the left of a "%c" is undefined in C because zero padding is only defined when the field contains digits, and the placement of a sign (at start or end of the field before the character itself will affect the count of zeroes to fill the field; note that a sign may not always be present); the "%c" placeholder does not define the behavior when there is an optional sign or when the character itself is a sign.
But most implementation assume that the value given to %c behaves like a digit.
Note that "%2c" uses *right* padding with spaces, while "%2d", %2o", "%2x", "%2f" uses *left* padding with spaces for formatting numeric values (decimal, octal or hexadecimal integers or floats).
If %c behaves the same when you use the "0" flag, the zeroes would be padded to the right with "%0*c", while "%0*d" is padding to the left.
"%c" does not have any semantic related to numeric values, but acts like "%s" with right padding only (and only with spaces)
So with "%02c", the zero flag should be simply ignored and the character should be displayed with a trailing padding space (which should be the standard). Padding zeroes (on the left or the right) is clearly non-standard, giving unpredictable results, as long as the "0" flag is not formally standardized for "%c" and "%s" placeholders (which just define a minimum and maximum width with right padding with spaces; the maximum significant width being specified after the dot as in "%5.8c" or "%5.8c"; note that "%5.8c" would mean a maximum of 8 significant characters and a maximum of 5 characters, the value 8 is ignored because %c generates only one character which is always satisfied; now "%5.0c" would take 0 characters from input, so the character would not be displayed at all, and only the first width specified before the dot will have effect and will be the number of spaces generated: the result will always be 5 spaces independantly of the value "%5.0c" or "%0.5s" would generate the same thing, the first one taking an integer (interpreted as a char) from the varargs (usually on the stack but not necessarily, it could be in an buffer allocated elsewhere, e.g. with "v[s][n]printf(char *format, void *varargs)"), the second taking a (char*) pointer from varargs which is never accessed, but that may have a different size in the parameter stack so that advancing the pointer of varargs could be different).