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:
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
$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
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
sprintf make sure the method it uses is what is required.
Other format specifications that I consider useful are
%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
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
Blog - Email - Twitter