Padding Numbers with Zero in Perl
September 14, 2016
Today I saw a piece of Perl code to format a date and time that uses what I consider a clusmy way to do zero padding. I have seen this method or a variation thereof before; it checks if a number is smaller than 10 and if this is the case it converts the number to a string, adding a zero in front of it. Often when there are several variables this code is just repeated, a violation of Don't Repeat Yourself (DRY).
Below follows an example to show this technique. Please don't use it in your own code.
#!/usr/bin/perl
use strict;
use warnings;
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst )
= localtime( time );
$year += 1900; # $year contains years since 1900; make it actual years
$mon++; # $mon is zero-based; make it one-based
# Zero padding; the clumsy way
$mday = '0' . $mday if $mday < 10;
$mon = '0' . $mon if $mon < 10;
# Same method, but now using string interpolation
$hour = "0$hour" if $hour < 10;
$min = "0$min" if $min < 10;
my $stamp = "$year-$mon-$mday-$hour$min";
print "$stamp\n";
Perl, being very often used as a data wrangling language, comes with two functions for formatting strings which emulate (and extend) two functions from the C programming language with the same names: printf
and sprintf
. The former writes its output to STDOUT and the latter returns a formatted string.
To match the above code we're going to use sprintf
and assign the result to $stamp
. Just assume the print
statement was added to the above code for debugging purposes; in actual code the value of $stamp
could be used to, for example, create a directory or as part of a filename.
The first argument to sprintf
is a format which controls how the following arguments are converted. To convert a decimal number the format specification %d
is used. The (minimum) formatting width can be specified by using a non-zero number inserted between the %
and d
. Default spaces are used for padding, for example:
$ perl -e 'printf "%5d\n", 13'
13
This displays three spaces followed by the numer 13 for a total of 5 characters.
Note that the width is a mimimum. If the value to be converted requires more space the result is not truncated:
perl -e 'printf "%5d\n", 12335919'
12335919
When a zero follows the %
character zeroes are used for padding instead of spaces, for example:
$ perl -e 'printf "%08d\n", 237'
00000237
However, if you want to print floating point numbers use %d
instead of %f
,
for example:
$ perl -e 'printf "%012f\n", 3.1415927'
00003.141593
Probably more useful than padding with zeroes to the start is the control over the number of digits after the decimal point and rounding, for example:
$ perl -e 'printf "%8.4f\n", 3.1415927'
3.1416
The number 8 in the above example is again the (minimum) width of the converted value, not the number of digits before the decimal point. Hence why just two spaces are used for padding.
Note that there are various methods for rounding, so if you're going to use
printf
or sprintf
make sure the method it uses is what is required.
Other format specifications that I consider useful are %x
and %X
to print
hexadecimal numbers, and %b
to print binary. Each can also be padded with zeroes:
$ perl -e 'printf "%08x\n", 123689'
0001e329
$ perl -e 'printf "%#08x\n", 123689'
0x01e329
$ perl -e 'printf "%08X\n", 123689'
0001E329
perl -e 'printf "%08b\n", 19'
00010011
See perldoc -f sprintf
for a complete list of possible conversions.
Rewriting the Perl program given earlier using sprintf
results in:
#!/usr/bin/perl
use strict;
use warnings;
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst )
= localtime( time );
my $stamp = sprintf '%04d-%02d-%02d-%02d%02d',
$year + 1900, $mon + 1, $mday, $hour, $min;
print "$stamp\n";
But if all we want is formatting dates we can do even better than this, using the Time::Piece
module, for example, which comes shipped with perl
since version 5.9.5:
$ perl -MModule::CoreList \
-e 'print Module::CoreList->first_release( "Time::Piece" ), "\n"'
5.009005
Using this module the Perl program given earlier becomes:
#!/usr/bin/perl
use strict;
use warnings;
use Time::Piece;
my $tp = Time::Piece->new();
my $stamp = $tp->strftime( '%Y-%m-%d-%H%M' );
print "$stamp\n";
See the FreeBSD man page of strftime for a complete list of conversion specifications.
I recommend to read David Farrell's Solve almost any datetime need with Time::Piece to learn more about the Time::Piece
module. And, of course, read the official documentation on the command line using perldoc Time::Piece
.