Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Determining the dayname for the last day of any given month

by McDarren (Abbot)
on Jun 19, 2006 at 11:10 UTC ( [id://556225]=perlquestion: print w/replies, xml ) Need Help??

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

This afternoon, a co-worker asked me: "how do i get the last day of the month in perl ?"

After clarifying with him that he wanted the "name of the day of the last day of any given month/year", and then having a quick fossick about on CPAN, I threw the following example together using Calendar::Functions:

#!/usr/bin/perl -w use strict; use Calendar::Functions qw(month_days dotw dotw3); while (1) { print "Enter year (yyyy) and month (mm), separated by a space:"; chomp(my $input = <STDIN>); last if !$input; my ($year, $month) = split / /, $input; my $dayname = dotw(dotw3(month_days($month, $year), $month, $year) +); print "The last day of $month/$year will be a $dayname\n"; }

I'm curious to know how others would approach this little task?

(There are a few related Q&A's, - this one in particular - but none are quite the same)

Update: many thanks for all the replies. As always - TIMTOWTDI. However, on this occassion - for pure simplicity - the pony has to go to herkum for his DateTime solution :)

Thanks,
Darren :)

Replies are listed 'Best First'.
Re: Determining the dayname for the last day of any given month
by Herkum (Parson) on Jun 19, 2006 at 11:37 UTC
    Take a look at Datetime.
    use DateTime; my $dt = DateTime->last_day_of_month( year => 2003, month => 10, hour => 1, minute => 30, second => 0, ); print "Last day of month is " . $dt->day_name() . "\n";
    This creates a new object that assumes that it is the last day of the month. Then you just call the day_name(), your done!
Re: Determining the dayname for the last day of any given month
by davorg (Chancellor) on Jun 19, 2006 at 11:30 UTC

    Using only Perl standard modules:

    use strict; use warnings; use Time::Local; use POSIX 'strftime'; while (1) { print "Enter year (yyyy) and month (mm), separated by a space: "; chomp(my $input = <STDIN>); last if !$input; next unless $input =~ /(\d{4})\s+(\d\d?)/; my ($year, $month) = ($1, $2); my ($y, $m) = ($year, $month); $y -= 1900; if ($m == 12) { $m = 0; ++$y; } my $first = timelocal(0, 0, 0, 1, $m, $y); my $last = $first - 24*60*60; print "The last day of $month/$year will be a ", strftime('%A', localtime $last), "\n"; }
    --
    <http://dave.org.uk>

    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

      Not all days are 24*60*60 seconds long, and $m suffers from off-by-one errors. Fix:

      my ($y, $m) = ($year-1900, $month-1); # Day before the first of next month: my $last = timelocal_nocheck(0, 0, 0, 1-1, $m+1, $y);

      You'll need to import timelocal_nocheck from Time::Local

      Updated. However, the fixed code doesn't work because timelocal_nocheck doesn't handle $m+1 as I expect.

        Not all days are 24*60*60 seconds long

        Well the ones on the first of the month all are :)

        But you're right, they aren't guaranteed to _always_ be. The easiest fix is probably to change the call to timelocal so it uses midday rather than midnight.

        $m suffers from off-by-one errors

        I don't think it does. The value you get from the user is in the range 1-12. We want the next month, but timelocal wants the number in the range 0-11. So we already have the correct number (except we need to do some adjustment if the month is 12). It might not be the clearest algorithm in the world, but it _is_ correct.

        --
        <http://dave.org.uk>

        "The first rule of Perl club is you do not talk about Perl club."
        -- Chip Salzenberg

Re: Determining the dayname for the last day of any given month
by Skeeve (Parson) on Jun 19, 2006 at 11:52 UTC
    My approach using standard module Time::Local:
    #!/usr/bin/perl -w use strict; use Time::Local; while (1) { print "Enter year (yyyy) and month (mm), separated by a space:"; chomp(my $input = <STDIN>); last if !$input; my ($year, $month) = split / /, $input; my $dayname = (qw(Sun Mon Tue Wed Thu Fri Sat))[(gmtime(timegm(0,0 +,0,1,$month % 12,$year + $month / 12)-1))[6]]; print "The last day of $month/$year will be a $dayname\n"; }
    It works by substracting 1 second from the timestamp of 00:00:00 the first of the following month.

    s$$([},&%#}/&/]+}%&{})*;#$&&s&&$^X.($'^"%]=\&(|?*{%
    +.+=%;.#_}\&"^"-+%*).}%:##%}={~=~:.")&e&&s""`$''`"e
      After reaeding greenFox' comment I realised: Substracting isn't necessary!
      #!/usr/bin/perl -w use strict; use Time::Local; while (1) { print "Enter year (yyyy) and month (mm), separated by a space:"; chomp(my $input = <STDIN>); last if !$input; my ($year, $month) = split / /, $input; my $dayname = (qw(Sat Sun Mon Tue Wed Thu Fri))[(gmtime(timegm(0,0 +,0,1,$month % 12,$year + $month / 12)))[6]]; print "The last day of $month/$year will be a $dayname\n"; }
      Simply rotate the daynames...

      s$$([},&%#}/&/]+}%&{})*;#$&&s&&$^X.($'^"%]=\&(|?*{%
      +.+=%;.#_}\&"^"-+%*).}%:##%}={~=~:.")&e&&s""`$''`"e
Re: Determining the dayname for the last day of any given month
by greenFox (Vicar) on Jun 19, 2006 at 12:10 UTC

    Date::Calc's Day_of_Week function will do exactly what you want. I have the following snippet which I used once before when I could not install Date::Calc. Unfortunately I did not record it's provenance and I googled around and couldn't find it :( I seem to recall it coming from one of our esteemed monks though...

    # Return the day (1..7) that the first day of the given month/year fal +ls # on. Uses "Zeller's Confluence", which I don't claim to understand. # sub Day_of_Week { my($year, $month, $day) = @_; # $month in (1..12), $year as YYYY $month-=1; if ( $month < 2 ) { $month += 12; --$year; } my $z1 = (26 * ($month + 2)) / 10; my $z2 = int((125 * $year) / 100); my $day_of_week = ($z1 + $z2 - int($year / 100) + int($year / 400)) +% 7; return $day_of_week ? $day_of_week : 7; }

    It would need a wrapper to do what you want, subtract one (previous day) and wrap to seven if result zero...

    --
    Do not seek to follow in the footsteps of the wise. Seek what they sought. -Basho

Re: Determining the dayname for the last day of any given month
by ambrus (Abbot) on Jun 19, 2006 at 13:58 UTC

    The trick is that the last day of the month is the day before the first day of next month. So here's an example solution with Date::Manip:

    use Date::Manip; $input = "June 2006"; # these also work: #$input = "2006 june"; #$input = "today"; #$input = "June 2006"; $thismonth = UnixDate($input, "%B %Y"); $lastofmonth = DateCalc($thismonth, "+1 month -1 day"); $dow_lastofmonth = UnixDate($lastofmonth, "%A\n"); print $dow_lastofmonth;
    This prints Friday.

        I am not convinced by that node.

        I know that Date::Manip has disadvantages and is not always the module to use, but I don't see why it's wrong here. I don't think this task needs high speed or full i18n. I do like Date::Manip for such quick tasks like this, because it's easy to use.

        You say that the API is not consistent. There's only a handful of functions I have to use, and they aren't inconsistent IMO. (The functions are UnixDate, DateCalc, Date_Cmp, ParseDate, ParseDateDelta.) Having to use a few functions that can do almost anything (ok, I needed timezone conversion once) makes the module very easy to use which is more important than consistency in small and simple tasks like this.

        You are right that the module docs say

        If you're only doing fairly simple date operations (parsing common date formats, finding the difference between two dates, etc.), the other modules will almost certainly suffice.
        but contrary to this I still find the module good for these tasks.
Re: Determining the dayname for the last day of any given month
by Skeeve (Parson) on Jun 19, 2006 at 21:23 UTC
    Another solution without using any module:
    #!/usr/bin/perl -w use strict; while (1) { print "Enter year (yyyy) and month (mm), separated by a space:"; chomp(my $input = <STDIN>); last if !$input; my ($year, $month) = split / /, $input; my $m= $month % 12 + 1; my $y= $year+int($month/12); if ( $m<3) { $m+=12; --$y; } ++$m; my $dayname = (qw(Sat Sun Mon Tue Wed Thu Fri))[(int($m * 30.6) + +int($y * 365.25)-int($y / 100)+int($y / 400)) % 7]; print "The last day of $month/$year will be a $dayname\n"; }
    The formular used here works like this:
    1. if the month is January or february (1 or 2) shift it to the end of the preceeding year (month 13 and 14)
    2. add 1 to the month
    3. sum up the integer result of:
      1. month * 30.6
        30.6 gives it the correct toggle between 30 and 31. February is the last month, so no problem with 28/29
      2. year * 365.25
        this takes into account the leap years
      3. year / -100
        this subtracts the non-leap years
      4. year / 400
        this re-adds the non-non-leap years
    4. the day should then be added
    the reminder of 7 is the weekday starting with Sat==0

    s$$([},&%#}/&/]+}%&{})*;#$&&s&&$^X.($'^"%]=\&(|?*{%
    +.+=%;.#_}\&"^"-+%*).}%:##%}={~=~:.")&e&&s""`$''`"e
A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://556225]
Approved by GrandFather
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: (7)
As of 2024-04-23 18:21 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found