13
\$\begingroup\$

Write a function which takes a date and returns the day of the week of the next February 29th after that date.

  • The input is a string in the ISO Extended format: YYYY-MM-DD (e.g. May 27th, 2010 would be "2010-05-27").

  • The output is a string which is the name of the day of the week (e.g. "Monday"). Capitalization doesn't matter, but do give the full name in English.

  • If the given date is February 29th, then return the day of the week of the next Feburary 29th.

  • Use the calculations for the Proleptic Gregorian Calendar (so, it uses the Gregorian leap year calculations for it's entire length). Don't worry about the Julian Calendar or when the switch from Julian to Gregorian occurred. Just assume Gregorian for everything.

  • The function should work for at least the range of "0001-01-01" - "2100-01-01".

  • Feel free to use whatever standard libraries your language of choice provides, but do not use 3rd party libraries unless you want to include that code as part of your solution.

  • Shortest code (fewest characters) wins.

Examples:

  • func("0001-01-01") -> "Sunday"
  • func("1899-12-03") -> "Monday"
  • func("1970-01-01") -> "Tuesday"
  • func("1999-07-06") -> "Tuesday"
  • func("2003-05-22") -> "Sunday"
  • func("2011-02-17") -> "Wednesday"
  • func("2100-01-01") -> "Friday"

(and no, you don't have to name the function func)

Hints:

  • Remember that years ending in 00 which aren't divisable by 400 aren't leap years.
  • January 1st, 0001 is a Monday.
\$\endgroup\$

12 Answers 12

7
\$\begingroup\$

Windows PowerShell, 65

Fairly straightforward.

filter f{for($d=date $_;($d+='1').day*$d.month-58){}$d.dayofweek}

As usual, we can shave off two bytes if we are willing to wait a long time till completion:

filter f{for($d=date $_;($d+=9).day*$d.month-58){}$d.dayofweek}

Test:

> '0001-01-01','1899-12-03','1970-01-01','1999-07-06','2003-05-22','2011-02-17','2100-01-01'|f
Sunday
Monday
Tuesday
Tuesday
Sunday
Wednesday
Friday

History:

  • 2011-02-18 00:06 (65) First attempt.
\$\endgroup\$
  • \$\begingroup\$ What path is necessary for this to work? I have GnuWin date in my path, which is breaking it. \$\endgroup\$ – Peter Taylor Feb 22 '11 at 12:31
  • \$\begingroup\$ @Peter: It might clash with date. Just remove GNUWin32 from the PATH and it should work. Or change date to Get-Date (that kind of fallback behavior only works when no command is found – to check, just use gcm date). Anyway, I don't consider that a particular problem with this script as GNUWin32 is not part of any standard Windows installation. \$\endgroup\$ – Joey Feb 22 '11 at 16:46
  • \$\begingroup\$ The reason I asked was because I already tried using get-date and got the error message Method invocation failed because [System.Management.Automation.PSObject] doesn't contain a method named 'op_Addition'. Does it need PS2 or .Net 4 or something? \$\endgroup\$ – Peter Taylor Feb 22 '11 at 16:53
  • \$\begingroup\$ @Peter: I tried PowerShell v2. .NET 4 doesn't play into it since PowerShell is linked to the 2.0 runtime. In any case, there shouldn't be a PSObject coming back from Get-Date but a System.DateTime. \$\endgroup\$ – Joey Feb 22 '11 at 17:19
  • \$\begingroup\$ It's very weird. $d=Get-Date "0400-01-01"; $d++ gives an error message about ++ not being defined on DateTime. The same replacing ++ with +=1 or +="1" or +='1' gives the error message about PSObject. And just +"1" works. \$\endgroup\$ – Peter Taylor Feb 22 '11 at 17:29
5
\$\begingroup\$

Ruby 1.9, 85 characters

f=->a{require"date";d=Date.parse(a,0,0)+1;d+=1until d.day*d.month==58;d.strftime"%A"}

Straightforward solution. Call the function with f[args].

  • Edit: (87 -> 97) Fixed the 0001-01-01 testcase.
  • Edit 2: (97 -> 91) Date.parse allows specifying the date of the calendar reform as well.
  • Edit 3: (91 -> 87) Use a lambda instead of a function. Thanks Dogbert!
  • Edit 4: (87 -> 85) Remove unnecessary spaces. Thanks again, Dogbert!
\$\endgroup\$
  • 4
    \$\begingroup\$ Fails the testcase for "0001-01-01". Ruby's Date is too powerful, it takes Julian dates into account. \$\endgroup\$ – steenslag Feb 17 '11 at 23:16
  • \$\begingroup\$ @Ventero What does def* do? Never seen it before. \$\endgroup\$ – steenslag Feb 17 '11 at 23:17
  • \$\begingroup\$ @steenslag: Thanks, fixed that testcase. def* just defines a function called *. That way I don't need a space between def and the function name. \$\endgroup\$ – Ventero Feb 17 '11 at 23:22
  • 1
    \$\begingroup\$ Couldn't you just define a lambda instead? a=->{...} \$\endgroup\$ – Dogbert Feb 17 '11 at 23:29
  • 1
    \$\begingroup\$ Also, there's no need of any space after strftime. \$\endgroup\$ – Dogbert Feb 17 '11 at 23:42
3
\$\begingroup\$

T-SQL 166 185 Characters

CREATE PROCEDURE f29 (@d DATETIME) AS
DECLARE @i int
SET @i=YEAR(DATEADD(M,-2,@d)+3)
SET @i=@i+(4-@i%4)
IF @i%100=0 AND @i%400<>0 SET @i=@i+4
SELECT DATENAME(W,CAST(@i AS CHAR)+'-2-29')

I was already messing with T-SQL date functions, so I figured why not...

Original solution was incorrect...

Here's what I actually have to do to get that strategy to work:

CREATE PROCEDURE f29 (@d DATE) AS
DECLARE @i int
SET @i = YEAR(@d)
BEGIN TRY 
SET @i=YEAR(DATEADD(D, 3, DATEADD(M,-2,@d)))
END TRY
BEGIN CATCH
END CATCH
SET @i=@i+(4-@i%4)
IF @i%100=0 AND @i%400<>0 SET @i=@i+4
SELECT DATENAME(W,CAST(@i AS CHAR)+'-2-29')
\$\endgroup\$
  • \$\begingroup\$ Given that I create an overflow subtracting 2 months from 0001-01-01 even if I do use the correct datatype I give up on creating something short and valid for this question. O_o. Trixsy questionzes. \$\endgroup\$ – mootinator Feb 17 '11 at 23:00
  • \$\begingroup\$ I love seeing these T-SQL solutions. :) \$\endgroup\$ – Steve Feb 18 '11 at 1:42
3
\$\begingroup\$

C#, 176

Func<String,String>f=(d)=>{DateTime n;for(n=DateTime.Parse(d).AddDays(307);!(DateTime.IsLeapYear(n.Year));n=n.AddYears(1)){}return new DateTime(n.Year,2,29).ToString("dddd");};
\$\endgroup\$
  • \$\begingroup\$ You can save 8 bytes by using a normal function definition: string f(string d){...} \$\endgroup\$ – Joey Feb 18 '11 at 5:31
  • \$\begingroup\$ .ToString("dddd") prints the date in the current locale, though, not in English. \$\endgroup\$ – Joey Feb 18 '11 at 5:42
  • \$\begingroup\$ 165 chars => string f(string d){var n=DateTime.Parse(d).AddDays(307);while(!(DateTime.IsLeapYear(n.Year)))n=n.AddYears(1);return(new DateTime(n.Year,2,29)).DayOfWeek.ToString();} \$\endgroup\$ – Stephan Schinkel Aug 27 '14 at 8:09
  • \$\begingroup\$ Further improved to 127 characters: string f(string d){var n=DateTime.Parse(d).AddDays(1);return DateTime.IsLeapYear(n.Year)&&n.DayOfYear==60?n.DayOfWeek+"":f(n+"");} \$\endgroup\$ – Patrik Westerlund Aug 29 '14 at 14:43
3
\$\begingroup\$

Bash, 96 bytes

Thanks Peter... Highly golfed version, 96 bytes:

i=1;until [ `date -d"$1 $i days" +%m-%d` = "02-29" ];do((i++));done
date -d "${1} ${i} days" +%A

Old version, 229 bytes

#!/bin/bash
from=$1
i=1
while [ "$isFeb" = "" ] || [ "$is29" = "" ]
do
isFeb=`date -d "${from} ${i} days" | grep Feb`
is29=`date -d "${from} ${i} days" +%Y-%m-%d | grep "\-29"`
((i++))
done
((i--))
date -d "${from} ${i} days" +%A

SAMPLE I/O

:~/aman>./29Feb.sh 0001-01-01
Sunday
:~/aman>./29Feb.sh 1899-12-03
Monday
:~/aman>./29Feb.sh 1970-01-01
Tuesday
\$\endgroup\$
  • \$\begingroup\$ might have to set TZ if no default timezone is set, I learnt cyberciti.biz/faq/… while doing this on some online IDE \$\endgroup\$ – Aman ZeeK Verma Feb 18 '11 at 13:34
  • 1
    \$\begingroup\$ Are you going to golf this? ;p You can replace everything before the final line with i=0;d=;until [ `date -d"$1 $i days" +%m-%d` = "02-29" ];do((i++));done \$\endgroup\$ – Peter Taylor Feb 18 '11 at 21:15
  • \$\begingroup\$ :-) as we already discussed bout my raw-ness over bash!... For some reason, I don't fiddle much with bash.. they are so error prone!.. Thanks for ur suggestion.. updated! \$\endgroup\$ – Aman ZeeK Verma Feb 18 '11 at 22:00
  • 1
    \$\begingroup\$ Rawness with bash is no excuse for using four-letter variable names :P \$\endgroup\$ – Peter Taylor Feb 18 '11 at 22:25
  • \$\begingroup\$ yes sir, I got your point. \$\endgroup\$ – Aman ZeeK Verma Feb 18 '11 at 22:49
2
\$\begingroup\$

Perl, no date library: 160 159 155

sub f{($y,$m)=split/-/,@_[0],2;$y++if($m>'02-28');$y=($y+3)%400>>2;$y+=$y&&!($y%25);@r=(Tues,Wednes,Thurs,Fri,Satur,Sun,Mon);@r[(5*$y-($y/25&3))%7]."day";}

The real benefit of these date libraries is pushing off the length of the names of the days to someone else.

On the other hand, I think this is the only solution so far which works regardless of locale.

\$\endgroup\$
2
\$\begingroup\$

DATE and some BASHy glue (90)

The function:

f(){ while :;do $(date -d$1+1day +'set - %F %A 1%m%d');(($3==10229))&&break;done;echo $2;}

Testing:

$ for x in 0001-01-01 1899-12-03 1970-01-01 1999-07-06 2003-05-22 2011-02-17 2100-01-01 ; do f $x ; done
Sunday
Monday
Tuesday
Tuesday
Sunday
Wednesday
Friday
\$\endgroup\$
1
\$\begingroup\$

D: 175 Characters

S f(S)(S s){auto d=Date.fromISOExtString(s)+days(1);while(d.month!=Month.feb||d.day!=29)d+=days(1);return["Sun","Mon","Tues","Wednes","Thurs","Fri","Sat"][d.dayOfWeek]~"day";}

More Legibly:

S f(S)(S s)
{
    auto d = Date.fromISOExtString(s) + days(1);

    while(d.month != Month.feb || d.day != 29)
        d += days(1);

    return ["Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Sat"][d.dayOfWeek] ~ "day";
}

It's very easy to write in D, but it's definitely not going to win any code golfing contests. Still, outside of code golfing, I'd much rather have it easy to write and understand but long than have it terse but hard to write and understand.

\$\endgroup\$
1
\$\begingroup\$

Java 8 - 252 chars

Golfed:

import java.time.*;
import java.time.format.*;
public class S{public static void main(String[] a){LocalDate d=LocalDate.parse(a[0]).plusDays(1);while(d.getMonthValue()!=2||d.getDayOfMonth()!=29){d=d.plusDays(1);}System.out.println(d.getDayOfWeek());}}

Ungolfed:

import java.time.*;
import java.time.format.*;
public class S {
    public static void main(String[] a) {
        LocalDate d = LocalDate.parse(a[0]).plusDays(1);

        while(d.getMonthValue()!=2 || d.getDayOfMonth()!=29) {
            d = d.plusDays(1);
        }
        System.out.println(d.getDayOfWeek());
    }
}
\$\endgroup\$
  • \$\begingroup\$ I accept downvotes but can you please explain "the necro is real" and link to a FAQ that explains your point? I didn't think I would win but thought it would be interesting to see how Java could do, esp. given Java 8. I view code golf as having "weight classes" versus an outright competition. Java will rarely beat Ruby or Python outright. \$\endgroup\$ – Michael Easter Aug 28 '14 at 23:57
  • \$\begingroup\$ The OP prohibits 3rd party libraries, so in 2011, a Java solution would be unpleasant, as one couldn't use the popular Joda Time library. However, Java 8 (released in March 2014) contains the JSR-310 DateTime library - en.wikipedia.org/wiki/… . My reason is that I thought Java 8 would be fun, and potentially interesting to some readers. (If you aren't one of those, fine: there is still a reason for the post.) \$\endgroup\$ – Michael Easter Aug 29 '14 at 3:18
1
\$\begingroup\$

Rebol - 78

f: func[d][system/locale/days/(until[d: d + 1 d/day * d/month = 58]d/weekday)]

Ungolfed:

f: func [d] [
    system/locale/days/(
        until [
            d: d + 1
            d/day * d/month = 58
        ]
        d/weekday
    )
]

Example usage (in Rebol console):

>> f 0001-01-01
== "Sunday"

>> f 2100-01-01
== "Friday"
\$\endgroup\$
1
\$\begingroup\$

PHP, 104 bytes

function f($s){for($y=$s-(substr($s,5)<"02-29");!date(L,$t=strtotime(++$y."-2-1")););return date(l,$t);}

breakdown

for($y=$s-;                 // "-" casts the string to int; decrement year if ...
    (substr($s,5)<"02-29")  // ... date is before feb 29
    !date(L,                        // loop while incremented $y is no leap year
        $t=strtotime(++$y."-2-1")   // $t=feb 01 in that year (same weekday, but shorter)
    );
);
return date(l,$t);          // return weekday name of that timestamp

date(L): 1 for leap year, 0 else
date(l): full textual representation of the day of the week

\$\endgroup\$
0
\$\begingroup\$

GNU coreutils, 71 bytes

f(){
seq -f "$1 %gday" 2921|date -f- +'%m%d%A'|sed 's/0229//;t;d;:;q'
}

Linear search of the next 8 years (worst case, given 2096-02-29). Sed finds and outputs the first line that matches 29th of February.

I was surprised to find that t;d;:;q was shorter than testing to see whether the digits remained (/[01]/d;q), despite being twice as many commands.

Tests

I added an extra line of tests for difficult corner cases:

for i in 0001-01-01.Sunday 1899-12-03.Monday \
    1970-01-01.Tuesday     1999-07-06.Tuesday \
    2003-05-22.Sunday      2011-02-17.Wednesday 2100-01-01.Friday \
    2000-02-28.Tuesday     2000-02-29.Sunday    2000-03-01.Sunday   2096-02-29.Friday
do
    printf "%s => %s (expected %s)\n" ${i%.*} $(f ${i%.*}) ${i#*.}
done
\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.