cvs commit: perlfaq perlfaq4.pod

[email protected] (brian d foy) 22 Apr 2005 18:52:38 -0000
Newsgroups perl.cvs.perlfaq
Message-ID <[email protected]>
cvsuser     05/04/22 11:52:38

  Modified:    .        perlfaq4.pod
  Log:
  * How do I find yesterday's date?
  
     + Added a DateTime example
     + removed the lengthy discussion of daylight savings
  
  * How do I validate input?
  
     + Rewrote this very short answer to mention several module
     names
  
  * How do I expand function calls in a string?
  
     + I added an example using scalar context and an example
     that already returns a reference. I wanted to emphasize
     the notion that any code can go in the braces.
  
  * How do I remove consecutive pairs of characters?
  
     + rewrote answer to explain code samples
     + changed y/// to tr///
  
  *  How do I find matching/nesting anything?
  
     + changed y/// to tr///
  
  Revision  Changes    Path
  1.63      +93 -75    perlfaq/perlfaq4.pod
  
  Index: perlfaq4.pod
  ===================================================================
  RCS file: /cvs/public/perlfaq/perlfaq4.pod,v
  retrieving revision 1.62
  retrieving revision 1.63
  diff -u -r1.62 -r1.63
  --- perlfaq4.pod	7 Apr 2005 21:46:36 -0000	1.62
  +++ perlfaq4.pod	22 Apr 2005 18:52:38 -0000	1.63
  @@ -481,60 +481,30 @@
   
   =head2 How do I find yesterday's date?
   
  -If you only need to find the date (and not the same time), you
  -can use the Date::Calc module.
  -
  -	use Date::Calc qw(Today Add_Delta_Days);
  +(contributed by brian d foy)
   
  -	my @date = Add_Delta_Days( Today(), -1 );
  +Use one of the Date modules. The C<DateTime> module makes it simple, and
  +give you the same time of day, only the day before.
   
  +	use DateTime;
  +	
  +	my $yesterday = DateTime->now->subtract( days => 1 );
  +	
  +	print "Yesterday was $yesterday\n";
  +
  +You can also use the C<Date::Calc> module using its Today_and_Now
  +function.
  +
  +	use Date::Calc qw( Today_and_Now Add_Delta_DHMS );
  +	
  +	my @date_time = Add_Delta_DHMS( Today_and_Now(), -1, 0, 0, 0 );
  +	
   	print "@date\n";
  -
  -Most people try to use the time rather than the calendar to
  -figure out dates, but that assumes that your days are
  -twenty-four hours each.  For most people, there are two days
  -a year when they aren't: the switch to and from summer time
  -throws this off. Russ Allbery offers this solution.
  -
  -    sub yesterday {
  -		my $now  = defined $_[0] ? $_[0] : time;
  -		my $then = $now - 60 * 60 * 24;
  -		my $ndst = (localtime $now)[8] > 0;
  -		my $tdst = (localtime $then)[8] > 0;
  -		$then - ($tdst - $ndst) * 60 * 60;
  -		}
  -
  -Should give you "this time yesterday" in seconds since epoch relative to
  -the first argument or the current time if no argument is given and
  -suitable for passing to localtime or whatever else you need to do with
  -it.  $ndst is whether we're currently in daylight savings time; $tdst is
  -whether the point 24 hours ago was in daylight savings time.  If $tdst
  -and $ndst are the same, a boundary wasn't crossed, and the correction
  -will subtract 0.  If $tdst is 1 and $ndst is 0, subtract an hour more
  -from yesterday's time since we gained an extra hour while going off
  -daylight savings time.  If $tdst is 0 and $ndst is 1, subtract a
  -negative hour (add an hour) to yesterday's time since we lost an hour.
  -
  -All of this is because during those days when one switches off or onto
  -DST, a "day" isn't 24 hours long; it's either 23 or 25.
  -
  -The explicit settings of $ndst and $tdst are necessary because localtime
  -only says it returns the system tm struct, and the system tm struct at
  -least on Solaris doesn't guarantee any particular positive value (like,
  -say, 1) for isdst, just a positive value.  And that value can
  -potentially be negative, if DST information isn't available (this sub
  -just treats those cases like no DST).
  -
  -Note that between 2am and 3am on the day after the time zone switches
  -off daylight savings time, the exact hour of "yesterday" corresponding
  -to the current hour is not clearly defined.  Note also that if used
  -between 2am and 3am the day after the change to daylight savings time,
  -the result will be between 3am and 4am of the previous day; it's
  -arguable whether this is correct.
  -
  -This sub does not attempt to deal with leap seconds (most things don't).
  -
  -
  +	
  +Most people try to use the time rather than the calendar to figure out
  +dates, but that assumes that days are twenty-four hours each.  For
  +most people, there are two days a year when they aren't: the switch to
  +and from summer time throws this off. Let the modules do the work.
   
   =head2 Does Perl have a Year 2000 problem?  Is Perl Y2K compliant?
   
  @@ -570,9 +540,16 @@
   
   =head2 How do I validate input?
   
  -The answer to this question is usually a regular expression, perhaps
  -with auxiliary logic.  See the more specific questions (numbers, mail
  -addresses, etc.) for details.
  +(contributed by brian d foy)
  +
  +There are many ways to ensure that values are what you expect or
  +want to accept. Besides the specific examples that we cover in the
  +perlfaq, you can also look at the modules with "Assert" and "Validate"
  +in their names, along with other modules such as C<Regexp::Common>.
  +
  +Some modules have validation for particular types of input, such
  +as C<Business::ISBN>, C<Business::CreditCard>, C<Email::Valid>,
  +and C<Data::Validate::IP>.
   
   =head2 How do I unescape a string?
   
  @@ -586,21 +563,61 @@
   
   =head2 How do I remove consecutive pairs of characters?
   
  -To turn C<"abbcccd"> into C<"abccd">:
  -
  -    s/(.)\1/$1/g;	# add /s to include newlines
  +(contributed by brian d foy)
   
  -Here's a solution that turns "abbcccd" to "abcd":
  +You can use the substitution operator to find pairs of characters (or
  +runs of characters) and replace them with a single instance. In this
  +substitution, we find a character in C<(.)>. The memory parentheses
  +store the matched character in the back-reference C<\1> and we use
  +that to require that the same thing immediately follow it. We replace
  +that part of the string with the character in C<$1>.
  +
  +    s/(.)\1/$1/g;
  +
  +We can also use the transliteration operator, C<tr///>. In this
  +example, the search list side of our C<tr///> contains nothing, but
  +the C<c> option complements that so it contains everything. The
  +replacement list also contains nothing, so the transliteration is
  +almost a no-op since it won't do any replacements (or more exactly,
  +replace the character with itself). However, the C<s> option squashes
  +duplicated and consecutive characters in the string so a character
  +does not show up next to itself
   
  -    y///cs;	# y == tr, but shorter :-)
  +	my $str = 'Haarlem';   # in the Netherlands
  +    $str =~ tr///cs;       # Now Harlem, like in New York
   
   =head2 How do I expand function calls in a string?
   
  -This is documented in L<perlref>.  In general, this is fraught with
  -quoting and readability problems, but it is possible.  To interpolate
  -a subroutine call (in list context) into a string:
  +(contributed by brian d foy)
  +
  +This is documented in L<perlref>, and although it's not the easiest
  +thing to read, it does work. In each of these examples, we call the
  +function inside the braces of used to dereference a reference. If we
  +have a more than one return value, we can contruct and dereference an
  +anonymous array. In this case, we call the function in list context.
  +
  +    print "The time values are @{ [localtime] }.\n";
  +
  +If we want to call the function in scalar context, we have to do a bit
  +more work. We can really have any code we like inside the braces, so
  +we simply have to end with the scalar reference, although how you do
  +that is up to you, and you can use code inside the braces.
  +
  +	print "The time is ${\(scalar localtime)}.\n"
  +	
  +	print "The time is ${ my $x = localtime; \$x }.\n";
  +	
  +If your function already returns a reference, you don't need to create
  +the reference yourself.
  +
  +	sub timestamp { my $t = localtime; \$t }
  +	
  +	print "The time is ${ timestamp() }.\n";
  +	
  +In most cases, it is probably easier to simply use string
  +concatenation, which also forces scalar context.
   
  -    print "My sub returned @{[mysub(1,2,3)]} that time.\n";
  +	print "The time is " . localtime . ".\n";
   
   =head2 How do I find matching/nesting anything?
   
  @@ -609,15 +626,16 @@
   characters, a pattern like C</x([^x]*)x/> will get the intervening
   bits in $1. For multiple ones, then something more like
   C</alpha(.*?)omega/> would be needed.  But none of these deals with
  -nested patterns.  For balanced expressions using C<(>, C<{>, C<[>
  -or C<< < >> as delimiters, use the CPAN module Regexp::Common, or see
  -L<perlre/(??{ code })>.  For other cases, you'll have to write a parser.
  +nested patterns.  For balanced expressions using C<(>, C<{>, C<[> or
  +C<< < >> as delimiters, use the CPAN module Regexp::Common, or see
  +L<perlre/(??{ code })>.  For other cases, you'll have to write a
  +parser.
   
   If you are serious about writing a parser, there are a number of
   modules or oddities that will make your life a lot easier.  There are
   the CPAN modules Parse::RecDescent, Parse::Yapp, and Text::Balanced;
  -and the byacc program.   Starting from perl 5.8 the Text::Balanced
  -is part of the standard distribution.
  +and the byacc program.   Starting from perl 5.8 the Text::Balanced is
  +part of the standard distribution.
   
   One simple destructive, inside-out approach that you might try is to
   pull out the smallest nesting parts one at a time:
  @@ -1424,7 +1442,7 @@
       sub fisher_yates_shuffle {
           my $deck = shift;  # $deck is a reference to an array
           my $i = @$deck;
  -        while ($i--) {
  +        while (--$i) {
               my $j = int rand ($i+1);
               @$deck[$i,$j] = @$deck[$j,$i];
           }
  @@ -1460,15 +1478,15 @@
   Use C<for>/C<foreach>:
   
       for (@lines) {
  -	s/foo/bar/;	# change that word
  -	y/XZ/ZX/;	# swap those letters
  +		s/foo/bar/;	# change that word
  +		tr/XZ/ZX/;	# swap those letters
       }
   
   Here's another; let's compute spherical volumes:
   
       for (@volumes = @radii) {   # @volumes has changed parts
  -	$_ **= 3;
  -	$_ *= (4/3) * 3.14159;  # this will be constant folded
  +		$_ **= 3;
  +		$_ *= (4/3) * 3.14159;  # this will be constant folded
       }
   
   which can also be done with map() which is made to transform
  @@ -1482,7 +1500,7 @@
   case), you modify the value.
   
       for $orbit ( values %orbits ) {
  -	($orbit **= 3) *= (4/3) * 3.14159;
  +		($orbit **= 3) *= (4/3) * 3.14159;
       }
   
   Prior to perl 5.6 C<values> returned copies of the values,