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

Ok, I asked this question in the chatterbox, but didn't quite understand/get what I was looking for, so I guess I'll have to post here. I have a few instances in a rather large program where I need to change the format of certain variables. What I'm trying to do is have it pass in the variable I need reformatted, and have it return that same scalar variable reformated. Can this be done, and if so how? For example I'm calling a date format function:

TimeStamp($date_created); #$date_created is YYYYMMDD

Where I want $date_created to be directly reformatted to MMDDYYYY, without using a module. Right now I'm using 5 functions to do the same thing :(

sub TimeStamp($) # Pass the stamp in as YYYYMMDD { $_=$_[0]; if( /(\d{4})(\d{2})(\d{2})/ ) { #returns in MMDDYYYY $date_created = "$2/$3/$1" } else { warn "Invalid input to TimeStamp()" } }
Thanks for any ideas!

Replies are listed 'Best First'.
Re: Dynamically changing the value of a passed variable into a sub
by chromatic (Archbishop) on Nov 04, 2002 at 23:05 UTC

    Changing variables in place is usually a bad idea. It can be hard to debug and to modify, and it's a bit inflexible. I would rather write the code as such:

    my $formatted_date = time_stamp( $date_created ) or warn "Invalid date: '$date_created'"; sub time_stamp { my $date = shift; return "$2/$3/$1" if $date =~ /(\d{4}(\d{2})(\d{2})/; }
Re: Dynamically changing the value of a passed variable into a sub (see node below, this one got submitted in error)
by gjb (Vicar) on Nov 04, 2002 at 20:37 UTC

    You'll need to pass a reference to the scalar variable as a parameter to the subroutine.

    TimeStamp(\$data_created); sub TimeStamp { my ($dataRef) = @_; if ($$dateRef =~ /(\d{4})(\d{2})(\d{2})/) { $$dateRef = "$2/$3/$1"; } else { warn("..."); } }

    Since Perl has call-by-value semantics, i.e. a copy of the variable is passed to the subroutine rather than the value itself, you have to pass the address of the variable (i.e. \$date_created) to the subroutine.

    In the subroutine, you manipulate the value $$dateRef which is stored at address $dataRef.

    Hope this helps, -gjb-

      gjb is correct in suggesting that the variable can be passed by reference, so that the sub may modify it.

      However, his assertion that arguments are passed by value is not quite correct. In fact, arguments are passed "by alias", which is essentially under-the-hood references.

      When the docs say that the sub receives the argument list in @_, what they really mean is that each element of @_ is an alias to the actual argument.

      This means that modifying an element of @_ modifies the actual argument.

      So -- as a possible alternative -- instead of doing

      modify( \$val ); sub modify { my $ref = shift; $$ref ++; # or whatever }
      you can in fact do this:
      modify( $val ); sub modify { $_[0] ++; # or whatever. }
      However, just because you can do this, doesn't mean you should. This technique is considered by many to be Bad Programming.

      The other issue to be aware of -- for future reference -- is that constant values can not be modified*. If you try, perl will barf.

      *This is always true**, but having the modification down in a subroutine can make you forget.

      **That is, constant values can't be modified within Perl code. By calling low-level (C) code, e.g. via XS, anything is possible.

Re: Dynamically changing the value of a passed variable into a sub
by Jenda (Abbot) on Nov 04, 2002 at 20:52 UTC

    Well ... first you pass in a parameter and then you modify a global variable?

    Another problem is that you modify $_ without localizing it!

    The $_ = $_[0]; made a copy of the parameter and you then modified the copy, not the parameter itself. You either want to work on the $_[0] directly or use

    sub TimeStamp { for ($_[0]) { if( /(\d{4})(\d{2})(\d{2})/ ) { #returns in MMDDYYYY $_ = "$2/$3/$1" } else { warn "Invalid input to TimeStamp()"; } } } ... TimeStamp($date_created); # will modify $date_created

    Jenda

Re: Dynamically changing the value of a passed variable into a sub
by gjb (Vicar) on Nov 04, 2002 at 20:38 UTC

    You'll need to pass a reference to the scalar variable as a parameter to the subroutine.

    TimeStamp(\$data_created); sub TimeStamp { my ($dateRef) = @_; if ($$dateRef =~ /(\d{4})(\d{2})(\d{2})/) { $$dateRef = "$2/$3/$1"; } else { warn("..."); } }

    Since Perl has call-by-value semantics, i.e. a copy of the variable is passed to the subroutine rather than the value itself, you have to pass the address of the variable (i.e. \$date_created) to the subroutine.

    In the subroutine, you manipulate the value $$dateRef which is stored at address $dataRef.

    Hope this helps, -gjb-

      Thanks to everyone who helped out with this one, I always had trouble with pointers in C++, and I guess it rubbed off onto references in Perl. I used gjb's code example, and it worked great. But I'm wondering if I could have changed the line my($dateRef) = @_; to my(dateRef) = $_[0];

      Also thanks for pointing out my bad programming with the scope of the $_[0] in my first example.

      bW
      Learning something new everyday!

Re: Dynamically changing the value of a passed variable into a sub
by BrowserUk (Patriarch) on Nov 05, 2002 at 00:21 UTC

    Why not simply have the function return the modified date and assign it back to the variable you have passed?

    sub TimeStamp($) # Pass the stamp in as YYYYMMDD { local $_=$_[0]; if( /(\d{4})(\d{2})(\d{2})/ ) { #returns in MMDDYYYY "$2/$3/$1"; } else { warn "Invalid input to TimeStamp()" } }

    and call it thusly

    $date_created = TimeStamp($date_created);

    You can even use this in places like if statements.

    if ( $date_created = TimeStamp($date_created) eq 'somedate' ) { # do something special. }

    Also, I noticed that you are using a prototype, you might want to read this thread for reasons why you shouldn't be using them unless you have very specific reasons for doing so.


    Nah! Your thinking of Simon Templar, originally played by Roger Moore and later by Ian Ogilvy
Re: Dynamically changing the value of a passed variable into a sub
by Enlil (Parson) on Nov 04, 2002 at 20:41 UTC
    From my understanding of the question you want take $date_created and change the format, would this work for what you are asking:
    unless ($date_created =~ s!^(\d{4})(\d{2})(\d{2})$!$2/$3/$1!) { warn " +Invalid Input: $date_created" }
    I have also added the $ and ^ to the regex so as to make sure something like $date_created=SA123454 does not match. But just because it is an 8 digit number does not mean that it is a date, so other checks might be necassary as well.

    Update: I think I misunderstood the question and thus think gjb has the answer you are looking for above.

    -enlil

Re: Dynamically changing the value of a passed variable into a sub
by broquaint (Abbot) on Nov 05, 2002 at 12:07 UTC
    While I agree with chromatic in that dirtying your parameters is a bad idea, here's another solution for completeness
    sub TimeStamp($) # Pass the stamp in as YYYYMMDD { if( $_[0] =~ /(\d{4})(\d{2})(\d{2})/ ) { #returns in MMDDYYYY $_[0] = "$2/$3/$1"; } else { warn "Invalid input to TimeStamp()" } }
    This will change the variable that is passed to it as the @_ variable is really just a list of aliases (see. rinceWind's node for more info on the magicality of @_).
    HTH

    _________
    broquaint