16
\$\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\$
0

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\$
7
  • \$\begingroup\$ What path is necessary for this to work? I have GnuWin date in my path, which is breaking it. \$\endgroup\$ Feb 22, 2011 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, 2011 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\$ Feb 22, 2011 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, 2011 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\$ Feb 22, 2011 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\$
7
  • 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, 2011 at 23:16
  • \$\begingroup\$ @Ventero What does def* do? Never seen it before. \$\endgroup\$
    – steenslag
    Feb 17, 2011 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, 2011 at 23:22
  • 1
    \$\begingroup\$ Couldn't you just define a lambda instead? a=->{...} \$\endgroup\$
    – Dogbert
    Feb 17, 2011 at 23:29
  • 1
    \$\begingroup\$ Also, there's no need of any space after strftime. \$\endgroup\$
    – Dogbert
    Feb 17, 2011 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\$
2
  • \$\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, 2011 at 23:00
  • \$\begingroup\$ I love seeing these T-SQL solutions. :) \$\endgroup\$
    – Steve
    Feb 18, 2011 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\$
4
  • \$\begingroup\$ You can save 8 bytes by using a normal function definition: string f(string d){...} \$\endgroup\$
    – Joey
    Feb 18, 2011 at 5:31
  • \$\begingroup\$ .ToString("dddd") prints the date in the current locale, though, not in English. \$\endgroup\$
    – Joey
    Feb 18, 2011 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\$ Aug 27, 2014 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\$ Aug 29, 2014 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\$
7
  • \$\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\$ Feb 18, 2011 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\$ Feb 18, 2011 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\$ Feb 18, 2011 at 22:00
  • 1
    \$\begingroup\$ Rawness with bash is no excuse for using four-letter variable names :P \$\endgroup\$ Feb 18, 2011 at 22:25
  • \$\begingroup\$ yes sir, I got your point. \$\endgroup\$ Feb 18, 2011 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\$
2
  • \$\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\$ Aug 28, 2014 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\$ Aug 29, 2014 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 agree to our terms of service and acknowledge you have read our privacy policy.

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