Welcome to the Monastery PerlMonks

### Determining the dayname for the last day of any given month

by McDarren (Abbot)
 on Jun 19, 2006 at 11:10 UTC 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 (Vicar) 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 (Vicar) 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
4. the day should then be added
the reminder of 7 is the weekday starting with Sat==0

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

Uses perl core util "date", that's what -I- would do. It would drive home the idea that a diff tech might be a solution to the current problem. But you may not subscribe to the ideology. Also, backticks are nonos in webstuffs.

Edited: davorg - deleted content restored

This is wrong. That's for today's date. Yes, date is an external unix core util. Using multiple programs is the unix way.

Uses perl core util "date"

date isn't a "perl core util", it's an external operating system command. It's a bad idea to use external commands if they aren't necessary because a) you can never be sure what external commands will be available if you have to move your program to another platform and b) opening a new shell unnecessarily is inefficient. Perl contains everything you need to handle dates. There is no need to use external programs.

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

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

There are times when I do shell out because I am certain your points a) and b) aren't going to bite me or are outweighed by ease/speed of implementation (I am a unix admin and not a developer after all:) but there is another issue with calling external programs which can hurt as much- the output of any external command you call is not in your control and can change. Usually this will be to the detriment of your program which is relying on the output format to parse out the required data. Click, Boom! Ouch!

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

As I had said, it all depends on what you are trying to achieve. If you're on deadline for a temporary hack, and there is such a thing- then you should be aware of your enviroment. Perl was originally a collection of unix hacks and tools, wasn't it?

You don't code a distribution app that depends on unix tools. But on day to day problems in the office, maintaining a hosting server, whatever- It's shooting yourself in the foot if you imagine perl is the only tool in your box.

The original post was what would *i* do. I got tangled up and wrote a solution that does not address the question.

Create A New User
Node Status?
node history
Node Type: perlquestion [id://556225]
Approved by GrandFather
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (3)
As of 2017-08-19 03:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
Who is your favorite scientist and why?

Results (310 votes). Check out past polls.

Notices?