Mahesh
Mahesh

Reputation: 69

Perl - How to get date of Previous wednesday from the given date without using DateTime

All,

I want to find out the date of previous wednesday from the given date.

Eg. I have date as "20150804" and i would need "20150729".

DateTime is not available and i cannot install it as well.

I looked at few examples but they were using DateTime.

Can you please redirect me where i can get some help.? Thanks.

I am planning to code something like below.

Code:

#!/opt/perl-5.8.0/bin/perl

use warnings;
use strict;

my $dt="20150804";
my $prevWednesday=getPrevWednesday($dt);

sub getPrevWednesday()
{
    my $givenDt=shift;
    ...
}

Upvotes: 4

Views: 688

Answers (4)

simbabque
simbabque

Reputation: 54333

Here is a more elegant solution that does not do bruteforce.

use strict;
use warnings;
use Time::Local 'timelocal';
use POSIX 'strftime';

my $dt = "20150804";
say getPrevWednesday($dt);

# note you do not want () here, 
# see http://perldoc.perl.org/perlsub.html#Prototypes
sub getPrevWednesday {
    my $givenDt = shift;

    # parse the string into a unix timestamp
    my ( $year, $month, $day ) = $givenDt =~ /(....)(..)(..)/;
    my $timestamp = timelocal( 0, 0, 12, $day, $month - 1, $year );

    # get the day of week, ignore the rest
    my ( undef, undef, undef, undef, undef, undef, $wday ) =
        localtime $timestamp;

    # because we start the week with Sunday on day 0
    # and to get to the previous Wednesday from Sunday it's
    # 4 days (Wednesday is 3) we can add 4 to the
    # number of this day, divide by 7, take the leftover (modulo)
    # and then subtract that many days
    # (86_400 is one day in seconds)

    #       v- -6 ------
    #                           6 % 7 = 6
    #                   +4 -----v
    #                   v
    # 0 1 2 3 4 5 6 0 1 2 3 4 5 6
    # S M T W T F S S M T W T F S
    my $prev_wed = $timestamp - ( ( $wday + 4 ) % 7 * 86_400 );

    # go one week back if we got the same day
    $prev_wed -= ( 7 * 86_400 ) if $prev_wed == $timestamp;

    # debug output
    warn "in: " . localtime($timestamp) . "\n";
    warn "out: " . localtime($prev_wed) . "\n\n";

    # put it back into your format
    return strftime('%Y%m%d', localtime $timestamp);
}

Output:

# STDOUT
20150804

# STDERR
in: Tue Aug  4 12:00:00 2015
out: Wed Jul 29 12:00:00 2015

Upvotes: 2

Dave Cross
Dave Cross

Reputation: 69274

There's always the brute force approach.

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;

use POSIX 'strftime';

my $ONE_DAY = 24 * 60 * 60;

# Get now
my $time = time;

# Subtract days until you get to a Wednesday
do {
  $time -= $ONE_DAY;
} until (localtime($time))[6] == 3;

# Format
say strftime '%Y%m%d', localtime $time;

But if you're working in a Perl environment where you can't install modules from CPAN, then it is always worth working to get that restriction removed. Modern Perl programming is often a case of plumbing together the right series of CPAN modules. If you don't have access to CPAN then you're just making your life much harder than it needs to be.

If you really can't get the restriction lifted, then look for another job. It's not worth dealing with people who impose such pointless restrictions.

Update: Just noticed that you're also using a prehistoric version of Perl. You'll need to remove the use 5.010 and replace the say with print. And brush up your CV :-/

Update 2: choroba's solution is better. It deals with any date in the correct format. Mine just deals with the current date. The advice about fixing your working environment still holds though.

Upvotes: 2

Håkon Hægland
Håkon Hægland

Reputation: 40758

Using Time::Piece :

use feature qw(say);
use strict;
use warnings;

use Time::Piece;
use Time::Seconds;
my $str = '20150804';
my $fmt = '%Y%m%d';
my $t = Time::Piece->strptime($str, $fmt);
do {
    $t = $t - ONE_DAY;
} until ( $t->day eq 'Wed');
say $t->strftime($fmt);

Upvotes: 2

choroba
choroba

Reputation: 241918

Another brute force approach, this time using another core module Time::Local.

#!/usr/bin/perl
use warnings;
use strict;

use Time::Local;

sub prev_wednesday {
    my $date = shift;
    my ($year, $month, $day) = $date =~ /(....)(..)(..)/;
    my $time = timelocal(0, 0, 12, $day, $month - 1, $year);
    do { $time -= 60 * 60 * 24 } until (localtime $time)[6] == 3; # <- Wednesday
    my ($y, $m, $d) = (localtime $time)[5, 4, 3];
    return sprintf "%4d%02d%02d\n", 1900 + $y, $m + 1, $d;
}

print $_, ' ', prev_wednesday($_), for qw( 20150804 20150805 20150806
                                           20150101 20000301 20010301 );

Upvotes: 5

Related Questions