Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Calculating next business day (weekends/holidays taken into account)

by vxp (Pilgrim)
on Jun 24, 2009 at 20:04 UTC ( [id://774503]=perlquestion: print w/replies, xml ) Need Help??

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

The script I am writing is supposed to return the next business day (there's a list of holidays the script knows about, and it also calculates whether or not it's the weekend).

I haven't had any problems getting to this point (finding out whether a date passed to the script is one of the known holidays or a weekend) but now I'm stuck at determining the next business day.

if the date passed to the script is NOT a weekend date, and it's NOT listed in one of the hashes I'm using as a holiday, then the script is supposed to figure out the next business day (next day that is not the weekend, and does not have a holiday associated with it)

everything's wrriten and works, except for my get_next_business_day() sub. I'm drawing a blank right now. May be it's becaused it's been a long day :)

Was wondering if any of you guys would have comments / suggestions / solutions ?

#!/usr/bin/perl use Time::Local 'timelocal_nocheck'; my $date_to_check = shift; my $zone = shift; $zone = lc($zone); my $dayoff = 0; ############################# # was a proper date given? ############################# unless ($date_to_check =~ /^\d{8}$/) { print "The date must be in the yyyymmdd format. Example: 20090101\ +n"; exit(-1); } ############################################### # is the zone correct? was it even given? ############################################### unless (($zone =~ /^ny$/) || ($zone =~ /^hk$/) || ($zone =~ /^mo$/) || + ($zone =~ /^ln$/) || ($zone =~ /^tk$/ ) || ($zone =~ /^se$/ )) { print "The acceptable zones are:\nNY\nHK\nMO\nLN\nTK\nSE\n"; exit(-1); } ############################################### # does the date fall on a weekend? ############################################### my @weekday = qw(Sunday Monday Tuesday Wednesday Thursday Friday Satur +day); my $day_of_week = get_day($date_to_check); unless (($day_of_week =~ /Monday/) || ($day_of_week =~ /Tuesday/) || ( +$day_of_week =~ /Wednesday/) || ($day_of_week =~ /Thursday/) || ($day +_of_week =~ /Friday/) ) { print "$date_to_check falls on $day_of_week - weekend\n"; print "DAYOFF\n"; $dayoff = 1; } sub get_day { my $date = shift || return(0); my ($year,$mon,$mday) = $date =~ /(\d{4})(\d{2})(\d{2})/; my $epochtime = timelocal_nocheck(0, 0, 0, $mday, $mon-1, $year); my $day = (localtime($epochtime))[6]; return $weekday[$day]; } ########################################## # holiday definitions ########################################## my %ny_holidays = (); my %hk_holidays = (); my %mo_holidays = (); my %ln_holidays = (); my %tk_holidays = (); my %se_holidays = (); ####################################### # United States ####################################### $ny_holidays{'20090101'} = 'New Years Day'; $ny_holidays{'20090115'} = 'MLK'; $ny_holidays{'20090219'} = 'Presidents day'; $ny_holidays{'20090406'} = 'Good Friday'; $ny_holidays{'20090528'} = 'Memorial Day'; $ny_holidays{'20090704'} = 'Independence Day'; $ny_holidays{'20090903'} = 'Labor day'; $ny_holidays{'20091122'} = 'Thanksgiving'; $ny_holidays{'20091225'} = 'Christmas Day'; ####################################### # Hong Kong ####################################### $hk_holidays{'20090101'} = 'New Years Day'; $hk_holidays{'20090217'} = 'Day preceding Lunar New Years day'; $hk_holidays{'20090219'} = 'Second day of Lunar New Year'; $hk_holidays{'20090220'} = 'Third Day of Lunar New Year'; $hk_holidays{'20090405'} = 'Ching Ming festival'; $hk_holidays{'20090406'} = 'Good Friday'; $hk_holidays{'20090407'} = 'The day following Good Friday'; $hk_holidays{'20090409'} = 'Easter Monday'; $hk_holidays{'20090501'} = 'Labour day'; $hk_holidays{'20090524'} = 'Buddhas birthday'; $hk_holidays{'20090619'} = 'Tuen Ng Festival'; $hk_holidays{'20090702'} = 'The day following HK SAR Establishment Day +'; $hk_holidays{'20090926'} = 'The day following Chinese Mid-Autumn Festi +val'; $hk_holidays{'20091001'} = 'National Day'; $hk_holidays{'20091019'} = 'Cheung Yeung Festival'; $hk_holidays{'20091225'} = 'Christmas Day'; $hk_holidays{'20091226'} = 'The first weekday after Christmas Day'; ####################################### # Russia ####################################### $mo_holidays{'20090101'} = 'New Year'; $mo_holidays{'20090102'} = 'New Year'; $mo_holidays{'20090103'} = 'New Year'; $mo_holidays{'20090104'} = 'New Year'; $mo_holidays{'20090105'} = 'New Year'; $mo_holidays{'20090107'} = 'New Year'; $mo_holidays{'20090223'} = 'Fatherland Defense Day'; $mo_holidays{'20090308'} = 'Womens day'; $mo_holidays{'20090501'} = 'Labour day'; $mo_holidays{'20090509'} = 'Victory Day'; $mo_holidays{'20090612'} = 'Russias day'; $mo_holidays{'20091104'} = 'Unity day'; ####################################### # United Kingdom ####################################### $ln_holidays{'20090101'} = 'New Year'; $ln_holidays{'20090102'} = 'New Year'; $ln_holidays{'20090406'} = 'Good friday'; $ln_holidays{'20090409'} = 'Easter Monday'; $ln_holidays{'20090507'} = 'May Day'; $ln_holidays{'20090528'} = 'Spring Holiday'; $ln_holidays{'20090806'} = 'Summer holiday'; $ln_holidays{'20090827'} = 'Summer holiday'; $ln_holidays{'20091225'} = 'Christmas day'; $ln_holidays{'20091226'} = 'Boxing day'; ####################################### # Japan ####################################### $tk_holidays{'20090101'} = 'New Years Day'; $tk_holidays{'20090102'} = 'New Years Day'; $tk_holidays{'20090103'} = 'New Years Day'; $tk_holidays{'20090104'} = 'New Years Day'; $tk_holidays{'20090108'} = 'Coming of Age Day'; $tk_holidays{'20090211'} = 'National Foundation Day'; $tk_holidays{'20090212'} = 'Substitute Holiday for the National Founda +tion Day'; $tk_holidays{'20090321'} = 'Vernal Equinox Day'; $tk_holidays{'20090429'} = 'Greenery Day'; $tk_holidays{'20090430'} = 'Substitute Holiday for the Greenery Day'; $tk_holidays{'20090503'} = 'Constitution Memorial Day'; $tk_holidays{'20090504'} = 'National Holiday'; $tk_holidays{'20090505'} = 'Childrens Day'; $tk_holidays{'20090616'} = 'Marine Day'; $tk_holidays{'20090917'} = 'Respect for the Aged Day'; $tk_holidays{'20090923'} = 'Autumnal Equinox Day'; $tk_holidays{'20090924'} = 'Substitute Holiday for the Autumnal Equino +x Day'; $tk_holidays{'20091008'} = 'Health and Sports Day'; $tk_holidays{'20091103'} = 'National Culture Day'; $tk_holidays{'20091123'} = 'Labour Thanksgiving Day'; $tk_holidays{'20091223'} = 'Emperors birthday'; $tk_holidays{'20091224'} = 'Substitute Holiday for the Emperors Birthd +ay'; $tk_holidays{'20091228'} = 'New Years Holiday - Half Day Off'; $tk_holidays{'20091231'} = 'New Years Holiday'; ####################################### # Korea ####################################### $se_holidays{'20090101'} = 'New Year'; $se_holidays{'20090217'} = 'The day preceeding Lunar New Years Day'; $se_holidays{'20090219'} = 'The Second Day of the Lunar New Year'; $se_holidays{'20090301'} = 'Indepedence Movement Day'; $se_holidays{'20090501'} = 'Labour Day '; $se_holidays{'20090505'} = 'Childrens Day'; $se_holidays{'20090524'} = 'Buddhas Birthday'; $se_holidays{'20090606'} = 'Memorial Day'; $se_holidays{'20090717'} = 'Constitution Day'; $se_holidays{'20090815'} = 'Independence Day'; $se_holidays{'20090924'} = 'Thanksgiving Day'; $se_holidays{'20090925'} = 'Thanksgiving Day'; $se_holidays{'20090926'} = 'Thanksgiving Day'; $se_holidays{'20091003'} = 'National Foundation Day'; $se_holidays{'20091225'} = 'Christmas Day'; if ($zone =~ /ny/) { if (exists($ny_holidays{$date_to_check})) { $dayoff = 1; my $next_business_day = get_next_business_day($date_to_check); print "The next business day is: $next_business_day\n"; } } if ($zone =~ /hk/) { if (exists($hk_holidays{$date_to_check})) { $dayoff = 1; my $next_business_day = get_next_business_day($date_to +_check); print "The next business day is: $next_business_day\n" +; } } if ($zone =~ /mo/) { if(exists($mo_holidays{$date_to_check})) { $dayoff = 1; my $next_business_day = get_next_business_day($date_to +_check); print "The next business day is: $next_business_day\n" +; } } if ($zone =~ /ln/) { if(exists($ln_holidays{$date_to_check})) { $dayoff = 1; my $next_business_day = get_next_business_day($date_to +_check); print "The next business day is: $next_business_day\n" +; } } if ($zone =~ /tk/) { if(exists($tk_holidays{$date_to_check})) { $dayoff = 1; my $next_business_day = get_next_business_day($date_to +_check); print "The next business day is: $next_business_day\n" +; } } if ($zone =~ /se/) { if(exists($se_holidays{$date_to_check})) { $dayoff = 1; my $next_business_day = get_next_business_day($date_to +_check); print "The next business day is: $next_business_day\n" +; } } if ($dayoff == 1) { print "$date_to_check is a day off\n"; } elsif ($dayoff == 0) { print "$date_to_check is NOT a day off\n"; } sub get_next_business_day { my $date = shift; ###################### # ???????????????????? ###################### return $date; }

Replies are listed 'Best First'.
Re: Calculating next business day (weekends/holidays taken into account)
by grep (Monsignor) on Jun 24, 2009 at 20:18 UTC
    You need to increment the day by one and check if it's not a holiday/weekend day. But incrementing days gets hairy, so use one of the great date modules from cpan to do it. I use Date::Calc or for an OO module DateTime.

    In fact converting you entire script to use DateTime for dates would be a good thing. It'll end up being far more maintainable in the long run, and looking at what you have so far not to much of a change.

    grep
    One dead unjugged rabbit fish later...

      Unfortunately, I am not allowed to install anything on the box the script will be running from. That includes CPAN modules :/

      This is why I'm jumping through hoops there, with my get_day() sub :)

        Obligatory link: Yes, even you can use CPAN

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        Can you install it for your user account that will run the script? Then you can alter the 'lib' search path.

Re: Calculating next business day (weekends/holidays taken into account)
by johngg (Canon) on Jun 24, 2009 at 21:20 UTC

    Not actually addressing your problem but you could save a heap of typing when you initialise your holiday hashes. This

    my %ny_holidays = (); ... $ny_holidays{'20090101'} = 'New Years Day'; $ny_holidays{'20090115'} = 'MLK'; $ny_holidays{'20090219'} = 'Presidents day'; $ny_holidays{'20090406'} = 'Good Friday'; $ny_holidays{'20090528'} = 'Memorial Day'; $ny_holidays{'20090704'} = 'Independence Day'; $ny_holidays{'20090903'} = 'Labor day'; $ny_holidays{'20091122'} = 'Thanksgiving'; $ny_holidays{'20091225'} = 'Christmas Day';

    could be written like this

    my %ny_holidays = ( '20090101' => 'New Years Day', '20090115' => 'MLK', '20090219' => 'Presidents day', '20090406' => 'Good Friday', '20090528' => 'Memorial Day', '20090704' => 'Independence Day', '20090903' => 'Labor day', '20091122' => 'Thanksgiving', '20091225' => 'Christmas Day', );

    I hope this is of interest.

    Cheers,

    JohnGG

Re: Calculating next business day (weekends/holidays taken into account)
by Transient (Hermit) on Jun 24, 2009 at 20:13 UTC
    One way would be to encapsulate all of your checks inside a subroutine. Once you've determined the results of today, you can simply add to the date given (using proper date arithmetic of course) and re-check until you get a valid non-holiday/non-weeked date.
Re: Calculating next business day (weekends/holidays taken into account)
by vxp (Pilgrim) on Jun 25, 2009 at 18:55 UTC

    Solved!

    Changed the script a bit: everything is now a sub/function, using DateTime (although didn't go and clean up the non-DateTime code to use DateTime (yet) ), holidays are listed in a csv file for each region (instead of having huge hashes of static holidays), etc.

    the csv files are extremely simple. They are in the format of:

    datestamp,"Holiday name"

    best of all, it works. :)

    [vxp@vxp ~]$ ./zhopa.pl 20081231 mo The full path is /home/vxp/holidays/mo.csv Date passed to script: 20081231 Checking 20090101 20090101 is a day off, now will check 20090102 Checking 20090102 20090102 is a day off, now will check 20090103 Checking 20090103 20090103 is a day off, now will check 20090104 Checking 20090104 20090104 is a day off, now will check 20090105 Checking 20090105 20090105 is a day off, now will check 20090106 Checking 20090106 The next business day is: 20090106 [vxp@vxp ~]$

    Full code below.

      Hi, Thanks a lot for posting your code. I had similar problem in which I wanted the number of working days between two dates taking holidays also into account. I referred your code and modified it as per my script.Using Date::Calendar and reading data from custom file. My file is :

      # cat /home/otrsvpn/holiday.txt day1,23.10. day2,22.10. Gandhi Jayanti,02.10. Valentine's Day,14.02.

      And my code

      #!/usr/bin/perl use Date::Calendar; my $Holiday_File = "/home/otrsvpn/holiday.txt"; open FILE, $Holiday_File or die $!; my %holiday; my $holiday_ref = \%holiday; while (<FILE>) { chomp; my ($key, $val) = split /,/; $holiday{$key} .= exists $holiday{$key} ? "$val" : $val; } $calendar = Date::Calendar->new( $holiday_ref ); $days = $calendar->delta_workdays (2015,10,01,2015,10,30,1,0); print " \n There are $days days between 01-10-2015 and 30-10-2015 \n";

      This simply prints the number of working days(weekdays and holidays) between the mentioned two dates. Thanks Again

      # perl holiday.pl There are 18 days between 01-10-2015 and 30-10-2015

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://774503]
Approved by grep
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (4)
As of 2024-03-29 12:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found