kangaroobin has asked for the wisdom of the Perl Monks concerning the following question:

In a while loop, $month keeps being set to things like jan or October. And I want these to become 01 and 10 respectively. Right now, I have 12 regular expressions—one for each month individually:

$month =~ s/jan.*/01/i; $month =~ s/feb.*/02/i; $month =~ s/mar.*/03/i; $month =~ s/apr.*/04/i; $month =~ s/may.*/05/i; $month =~ s/jun.*/06/i; $month =~ s/jul.*/07/i; $month =~ s/aug.*/08/i; $month =~ s/sep.*/09/i; $month =~ s/oct.*/10/i; $month =~ s/nov.*/11/i; $month =~ s/dec.*/12/i;

Is there a shorter or more efficient way to write this? Perhaps in one line?Just wonderin'.

Replies are listed 'Best First'.
Re: Quick search-and-replace month names for numbers
by Corion (Patriarch) on Mar 05, 2008 at 16:52 UTC

    I'd do it in the following way, observing that the order of matches does not matter:

    my @months = (qw(jan feb mar apr may jun jul aug sep oct nov dec)); my $i = 1; my %month_num = map { $_ => sprintf '%02d', $i++ } @months; my $month_re = join "|", @months; $month =~ s/($month_re)\w*/$month_num{$1} || "$1"/ei;

    Update: Fixed bug noted by jwkrahn below.

    Update2: Fixed bugs noted by pc88mxer - the OP wanted a two digit number, and also changed \w+ to \w* because otherwise jan won't match anymore

      You are using non-capturing parentheses so $1 will contain nothing.

      Also, \w+ means month names must be at least four chars long. Also, doesn't handle upper or mixed case, nor does it properly handle long month names as the OP seems to want.

      perl -wMstrict -e "my @months = (qw(jan feb mar apr may jun jul aug sep oct nov dec)); my $i = 1; my %month_num = map { $_ => $i++ } @months; my $month_re = join '|', @months; print qq(o/p: \n); for (@ARGV) { print qq( $_: ); s/($month_re)\w+/$month_num{$1} || $1/ei; print qq($_ \n) }" jan JaN january jane o/p: jan: jan JaN: JaN january: 1 jane: 1

      Following is better, but still doesn't handle long month names properly (IMO).

      perl -wMstrict -e "my @months = (qw(jan feb mar apr may jun jul aug sep oct nov dec)); my $i = 1; my %month_num = map { $_ => $i++ } @months; my $month_re = join '|', @months; print qq(o/p: \n); for (@ARGV) { print qq( $_: ); s/($month_re)\w*/$month_num{lc $1} || $1/ei; print qq($_ \n) }" jan JaN january jane o/p: jan: 1 JaN: 1 january: 1 jane: 1

      Here's my suggestion. Not a one-liner, but seems to fill the requirement.

      use warnings; use strict; MAIN: { my $month_rx = month_names_regex(); while (<DATA>) { s{ ($month_rx) }{ "$1 (@{[ month_number($1) ]})" }xmseg; print; } } BEGIN { # compile-time initialized closure for month-number regexes my %months; # start with long names of months, pair with month number strings. # month numbers: two digits with leading zero if needed. # all month names in common lower case. @months{ qw( january february march april may june july august september october november december ) } = map { sprintf '%02d', $_ } 1 .. 12; # generate short month names, pair with long month numbers. my $months_re = join ' | ', map { my ($short_name, $regex) = mon_split($_); $months{$short_name} = $months{$_}; $regex; } keys %months ; # return final long/short month name regex. sub month_names_regex { return qr{ \b (?: $months_re) \b }xmsi } # convert long/short month name to month number. sub month_number { my $name = shift; return $months{lc $name} } # printf "\$months_re is %s \n", month_names_regex(); # FOR DEBUG # print "$_ => $months{$_} \n" for keys %months; # FOR DEBUG sub mon_split { my ($mon_name, ) = @_; my ($head, $tail) = $mon_name =~ m{ \A (\w{3}) (\w*) \z }xms; die "malformed month name $mon_name" unless $head; $tail = qr{ (?: $tail)? }xmsi if $tail; return (lc $head, qr{ $head $tail }xmsi); } } # end compile-time initialized closure for month-number regexes __DATA__ jan january february feb mar march jUnE JuN JuLy JUL xMaY xmArx Marx xmay xmayx mayx xjunE xjunex xJUNEx xjun xJUNx jUnx and so on

      Output:

      jan (01) january (01) february (02) feb (02) mar (03) march (03) jUnE (06) JuN (06) JuLy (07) JUL (07) xMaY xmArx Marx xmay xmayx mayx xjunE xjunex xJUNEx xjun xJUNx jUnx and so on
Re: Quick search-and-replace month names for numbers
by starbolin (Hermit) on Mar 05, 2008 at 23:34 UTC

    I think you guys are making it too hard.

    #!/usr/bin/perl my %month = ( JAN=>'01', FEB=>'02', MAR=>'03', APR=>'04', MAY=>'05', JUN=>'06', JUL=>'07', AUG=>'08', SEP=>'09', OCT=>'10', NOV=>'11', DEC=>'12' ); map { s/^(\w{3}).*/$month{uc $1}/; print(( ($_)?$_:'undef' ), "\n"); } @ARGV; __END__ %./foo january Dec Apr MAY xJun [3:27pm] 01 12 04 05 undef


    s//----->\t/;$~="JAPH";s//\r<$~~/;{s|~$~-|-~$~|||s |-$~~|$~~-|||s,<$~~,<~$~,,s,~$~>,$~~>,, $|=1,select$,,$,,$,,1e-1;print;redo}

      Good, but it's possible to do it even simpler.

      perl -we 'use Date::Manip; for (@ARGV) { print UnixDate($_ . "/15","%m +\n") || "?\n"; }' january Dec 3 Apr MAY xJun

      Output:

      01 12 03 04 05 ?