- Feature Articles
- CodeSOD
- Error'd
- Forums
-
Other Articles
- Random Article
- Other Series
- Alex's Soapbox
- Announcements
- Best of…
- Best of Email
- Best of the Sidebar
- Bring Your Own Code
- Coded Smorgasbord
- Mandatory Fun Day
- Off Topic
- Representative Line
- News Roundup
- Editor's Soapbox
- Software on the Rocks
- Souvenir Potpourri
- Sponsor Post
- Tales from the Interview
- The Daily WTF: Live
- Virtudyne
Admin
<FONT style="BACKGROUND-COLOR: #efefef">The least thing that could have been done is to start daysInMonth at 28. </FONT>
Admin
My shortcut, when a library function is unavailable, is to go to the first of the next month and subtract a day and check the day number.... (And yes, I handle december correctly. :)
Admin
Doin' it the Angry way...
CREATE FUNCTION dbo.DaysInMonth
(
@iYear smallint, @iMonth tinyint
)
RETURNS tinyint
AS
BEGIN
DECLARE @iDays tinyint
DECLARE @vDate varchar(10), @dtDate smalldatetime
SET @vDate = CONVERT(varchar(4), @iYear) + '-' + RIGHT('00' + CONVERT(varchar(2), @iMonth), 2) + '-01'
IF ISDATE(@vDate) < 1
BEGIN
/*Undefined*/
SET @iDays = NULL
END
ELSE
BEGIN
SET @dtDate = CONVERT(smalldatetime, @vDate)
SET @iDays = CONVERT(tinyint, DATEDIFF(day, @dtDate, DATEADD(month, 1, @dtDate)))
END
RETURN @iDays
END
Admin
Pretty cool, angry... I like the month handling "RIGHT('00'" part, that's a nice solution. Was kinda suprised not to see something similar with the @iYear...
Of course, since you're checking with ISDATE anyway you could probably skip that step..
Admin
$daysInMonth=1;
Where does this constant 1 come from? You may as well choose any number in the range 1 through 28 for this value. The only difference is the running time.
Admin
Blue,
See what happens when you hurry and type stuff up...
Nice catch; I meant to have it on both. Not formatting the year properly leaves open numerous "new" WTF's...such as how should "4-02-01" be interpreted...LOL
Here's the amended version...
CREATE FUNCTION dbo.DaysInMonth
(
@iYear smallint, @iMonth tinyint
)
RETURNS tinyint
AS
BEGIN
DECLARE @iDays tinyint
DECLARE @vDate varchar(10), @dtDate smalldatetime
SET @vDate = RIGHT('0000' + CONVERT(varchar(4), @iYear), 4) + '-' + RIGHT('00' + CONVERT(varchar(2), @iMonth), 2) + '-01'
IF ISDATE(@vDate) < 1
BEGIN
/*Undefined*/
SET @iDays = NULL
END
ELSE
BEGIN
SET @dtDate = CONVERT(smalldatetime, @vDate)
SET @iDays = CONVERT(tinyint, DATEDIFF(day, @dtDate, DATEADD(month, 1, @dtDate)))
END
RETURN @iDays
END
Admin
Here's my way :
(it's in Java, but its easy to code in whatever)
The month here is zero based... That's kind of odd, but that's the Java API way...
Admin
Or you could just use (assuming it's T-SQL):
CREATE FUNCTION dbo.DaysInMonth
(
@iYear smallint, @iMonth tinyint
)
RETURNS tinyint
AS BEGIN
RETURN DATEPART(day, DATEADD(year, @iYear - 1900, DATEADD(month, @iMonth, -1)))
END
Admin
sigh
You mean start at 29. 28 always returns true and thus is a useless test. Of course, other elements would need to be adjusted.
Admin
Anonymous,
"You're fired!".
Your code is totally broken, and if I have to tell you why, your employer is probably the one crying.
The cutesy solution is not always best.[N]
Mr. Angry DBA
Admin
int days_in_month( int month, int year ) /* month goes from 1 to 12 */
{
const long long c = 1151794505154789279;
return ( c >> ( ( month - 1 ) * 5 ) ) & 0x1f +
( month == 2 && is_leap_year( year ) ? 1 : 0 );
}
Admin
I really like the original code - sure, it loops a lot, but it's only gonna loop about 30 times, which is peanuts (how long can checkdate take, anyway?) And it's trivial to see what it's doing. It took about 2 minutes to write, and there's no reason to change it unless the profiler indicates it's a performance problem, which I doubt unless it turns out you're calling it in a tight loop elsewhere - in which case the correct solution might even be to hoist it out of the loop instead of having to rewrite the function.
Admin
LOL. [:D]
Yeah, this makes sense. Especially since the first 28 iterations are pointless.
Mr. Angry DBA
Admin
Interesting method, but false economy.
const long long c = 1151794505154789279; takes 8 bytes.
const char c[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; takes 12, but...
return ( c >> ( ( month - 1 ) * 5 ) ) & 0x1f + ....
will be at least 4 bytes longer than
return (c[month-1] + ...)
In fact, subtracting 1 from month, probably takes more than one byte, so the best, in both size and speed (and possibly readability) would be:
const char c[12] = {0, 31,28,31,30,31,30,31,31,30,31,30,31};
return (c[month] + ...)
Admin
jef,
I'm too lazy to load your code into the IDE and run it, but damn.... If it does work, you've gone to the extreme of brevity at the expense of maintainability for those that come after you!
Admin
And shouldn't that be "return $daysInMonth -1;". As it is, it should return one higher than the number of days in the month.
Admin
Make that array a private static array in the class and it won't have to be defined every time you execute this method.
Admin
Admin
OOOps... You're right... I was thinking that I had to change it when I did the copy'n'paste, but apparently I forgot.......
Admin
Admin
SET DATEFORMAT ymd
SELECT dbo.DaysInMonth(2005, 2) -- Your version, not mine
-- 28
SET DATEFORMAT ydm
SELECT dbo.DaysInMonth(2005, 2)
-- 31
Admin
This is already built into PHP anyway and can be done in one line:
$days_in_month = date('t',mktime(0,0,0,$month,1,$year));
Admin
Admin
If was referring to the fact that your code returns integer values between 28 and 31 for months that do not exist, such as (2004, 17).
An option such as the interpretation if string literals into dates is usually a system-wide decision made at design time, and functions will be written with this in mind as a matter of course. It is generally not something that is 'tweaked' on each method call. Even so, I didn't have to toggle any option bits to break your algorithm, so...what month is 17, anyhow?
Mr. Angry DBA
Admin
Behold, a friend of mine worked out a way to do this entirely in bitshifting. The leap year handling could be improved, and two months are wrong, but I'm sure a few more tweaks would make it work.
#define BIT(value, x) ((value & (1 << x)) >> x)
int num_days(int m) {
int days;
days = (((BIT(m,3) ^ BIT(m,2)) & BIT(m,1) & BIT(m,0)) | (!BIT(m,3) & BIT(m,2) & !BIT(m,0))) ? 30 : 31;
days = m == 2 ? 28 : days;
return days;
}
btw, has anyone run the original function? It always returns one more day than the month has! You need to subtract one somewhere every time. (Maybe it was made to be more like java's Calendar wtfs.)
Admin
Point taken - it doesn't validate input. My revised version:
<!--StartFragment --> CREATE FUNCTION dbo.DaysInMonth
(
@iYear smallint, @iMonth tinyint
)
RETURNS tinyint
AS BEGIN
RETURN CASE WHEN @iMonth <= 12 THEN
DATEPART(day, DATEADD(year, @iYear - 1900, DATEADD(month, @iMonth, -1)))
END
END
It's my personal opinion that:
1. A task this simple shouldn't require 27 lines of code
2. If you have integers and have to use string operations to turn them into a date, there's something missing in the language. A DateSerial function like what Access/VBA have would be useful here.
You obviously disagree, which is fine. My revised function still doesn't handle invalid year values while yours does, so at least in that respect it has an advantage.
Admin
int year;
int month;
year = 2005;
month = 2;
Console.WriteLine(DateTime.DaysInMonth(year, month).ToString());
why do you guys make it so complicated?
Admin
Mr. Angry DBA -- the quick 1 line solution is the best solution in T-SQL. you can add a check to ensure months from 1-12 pretty easily if you wish, obviously. Your method, as demonstrated, relies on an implicit datetime conversion that is dependant on the sytem setting and is overall much longer and more complex than it needs to be.
Also, it is often desirable (depending on the specs) to allow for function to handle months <1 or >12. (i.e., the DateSerial() function in VB). This greatly simplifies "date math" to not worry about crossing year boundaries -- you can safely add 6 to a particular month to get that date plus 6 months w/o special handling for months after June.
Admin
hey Anonymous -- sign up for an account so we know which posts are yours! (or sign them). As stated in my last post, I completely 100% agree with your method. (my blog has very similiar examples)
Very often in computer science, the simpliest solution is the best.
Admin
Obviously, if there is a library available which already has the function, then it makes sense to use it. Kind of implied. But then again, after seeing all the WTF's at this site (both the highlighted items and about 75% of the responses) you can't take anything for granted, I suppose! [:(]
Admin
Jeff & the rest,
It's always best to know the benefits and limits of the language and technology you choose for any task. If you don't have a choice, and you're stuck using that language or technology, it's best to research as much as possible so you're more familar with it.
But these WTF's are funny. Makes my day go by so much better.
Kay
Admin
In .NET it's simple:
System.Globalization.Calendar.GetDaysInMonth
You can even localize it for specific calendars supported in .NET.
System.Globalization.TaiwanCalendar
System.Globalization.KoreanCalendar
System.Globalization.HebrewCalendar
System.Globalization.HijriCalendar
System.Globalization.ThaiBuddhistCalendar
.NET 2.0 has some new fancy calendars.
The only reason I know is my company is developing a product to calc holidays all arround the world.
Schneider
Admin
Of course, any method which can't handle months outside the range 1..12 will have problems when you're dealing with a company that uses 13-month years.
Admin
Can you explain 13-month issue more? Is this a business logic or localization issue?
Schneider
Admin
I'm one of those anonymous guys who doesn't want to get clowned.
I have absolutely nothing to contribute to this conversation...but I wish I did. This stuff is way over my head.
I don't even understand this particular WTF.
How embarassing is that?
I just felt like posting and taking up space in this forum.
Admin
We're talking calendar months, obviously ... this is a function to return the # of days in a month! What does that have to do with accounting periods ??
Admin
To the anonymous poster who doesn't understand the WTF:
When you visit this site and look at the code, do you understand any of it? I mean, does your mind start to swim and your thoughts get cloudy when you see a line of code?
It really shouldn't hurt your brain to look at code and dissect it for its meaning. Most of the programmers in here like to take apart code and see how it works.
Based on your comments, it sounds like you weren't meant to program. You sound like the type of person who would find for loops "confusing"...
If you search the internet you may be able to find some kiddie programmer sites to visit and contribute to. Some where you won't get clowned on.
Cause you sure clowned yourself here! hahaha
Admin
Not only is this function brain-dead, it is wrong. Off by one - unless it is the case that checkdate uses a zero-based day, which is unlikely.
Consider July.
Checkdate Jul-31-2005 succeeds, so one gets added to $daysInMonth.
Checkdate Jul-32-2005 fails. The loop quits, and 32 is returned as the number of days in July. Wrong.
Admin
The VB6 way:
Public Function DaysInMonth(ByVal Year As Integer, ByVal Month As Integer) As Integer
DaysInMonth = Day(DateSerial(Year, Month + 1, 0))
End Function
Admin
But that's VB! It must be a WTF! Everybody knows VB is shit and people who use it are morons! Oh, wait, my mistake...
(Man, this forum software bites ass.)
Admin
I think 24 bits should be enough information for this one.
We could write this easily and a bit more unreadable something like this:
int days_of_month(int m, int y) {
return 28+((15662003+((!(y%4)&&y%100)||!(y%400)?4:0)>>(2*(m-1)))&3);
}
Admin
Admin
*weeping* [:'(] This is my fifth time trying to post my comment. Trying in IE this time...
Why use 2* at this point? Might as well use <<1
int days_of_month(int m, int y) {
return 28+((15662003+((!(y%4)&&y%100)||!(y%400)?4:0)>>((m-1)<<1))&3);
}
Admin
I'm not even sure this is right, but... here's my.... contribution. [6]
int days_of_month(int m, int y) {
return m==2?y%4&&!(y%100)||y%400?29:28:30+(5550>>m&1);
}
Admin
oh... assuming a 1 to 12 value for month.
Admin
Uh...does the original wtf code even work? I mean - if I'm not mistaken, it checks the given date, then increments the day until the check fails. That means, after the loop, $dayInMonth equals the first day that does not pass the check, which should be one more than the month actually has. Does checkdate expect 2005,0,0 for Jan 1st, 2005 or would this function actually claim that January has 32 days?
Admin
That's what I thought too. Nonsense code with a wrong result :-)
Admin
Guh, foon and anonymous, I already mentioned that - it's not so much an error as a design flaw, similar to how you have to add one to java's month to get a useful number. How on earth the creator decided that subtracting one from every single instance of the function was easier than returning $daysInMonth - 1; I'll never know.
I am, possibly incorrectly, assuming that its clients aren't trying to pass off 32-day months to the world. That wtf would awe me. ("We can't figure out where the error is. Just mentally subtract one when you see the dates.")
Admin
Well, I imagine applications dealing with religious time spans (fasting, resting, etc.) would benefit greately from those functions, and I can also think of some scientific applications that could make use of those as well.
Admin
I was working at a local university some time ago, and a student developer in our employ had a very clever way of determining the number of days in the month.
His logic was as follows (pseudo-code):
if MonthNumber <=7 thenif MonthNumber = 2 then
if isLeapYear(Year) then return 29 else return 28
else
if isOdd(MonthNumber) then return 31 else return 30
else
if isOdd(MonthNumber) then return 30 else return 31
The number of days flips between 31 and 30 depending upon whether the month is before or after July.
In a way, the number of days in a month is now "programmatically hard-coded" into the application.