11
\$\begingroup\$

Another date-manipulating problem :P

Task

Write a program or a function that calculates the difference between two dates given by a user.

Input & Output

Similar to the previous one, inputs are two YYYYMMDDs, separated by a space , a comma ,, or a minus sign -.

Example of input values:

20100101-20010911
20110620-20121223
19000101 20101010
33330101,19960229
00010101 99991231

Output is an integer, which is difference between two dates, in days.

For example, input 20110101-20100101 yields 365, and 33320229 17000101 yields 596124.

You may test results at here at here. (See rintaun's comments below.) If two dates are same, the program should returns 0, if the date is valid (see Score).

Restriction

Of course, you must not use any kinds of function/class/... which are related to timestamp or date, and you should use Gregorian calender.

Score

If your code doesn't keep the restriction, then score = -∞.

Default bonus is 1.

  • If your code works regardless of order of inputs (for example, 20100101,20110101 returns 365 or -365), bonus+=1.
  • If your code can handle year 0, bonus+=0.5.
  • If your code recognizes invalid month(between 1~12)/date(between 1~31), like 20109901 or 34720132, and prints E (& terminates the program or returns something like 0), bonus+=1.
  • Regardless of above rule, if your code recognizes invalid dates, like 20100230, 20100229, or 20111131, and prints E (& terminates the program or returns something like 0), bonus+=1.
  • Regardless of above two rules, if your code recognizes invalid input string, like 20100101|20100202 or 2010010120100202, and prints E (& terminates the program or returns something like 0), bonus+=1.

score = floor(-4.2*code.length/bonus). Code with highest score wins. If two top codes have same score, then codes with highest bonus wins. If two top codes have both same score and bonus, then codes with highest votes wins.

(Due: When there's more than 5 codes which has more than (or equal) +1 votes.)

\$\endgroup\$
  • \$\begingroup\$ Is 20030229 considered an invalid date by the third bonus? \$\endgroup\$ – rintaun Jun 20 '11 at 7:51
  • \$\begingroup\$ @rintaun Yes. It's invalid, unlike 20040229. :P \$\endgroup\$ – JiminP Jun 20 '11 at 9:05
  • 1
    \$\begingroup\$ Does WolframAlpha actually return the correct result? I'm getting conflicting answers from it and timeanddate.com. My program, which I believe is working correctly (at least in that instance :P), agrees with the latter. \$\endgroup\$ – rintaun Jun 20 '11 at 9:15
  • \$\begingroup\$ @rintaun I think Wolfram|Alpha was wrong, since 365*4 + 2 + 2 = 1464. Thanks for the information! \$\endgroup\$ – JiminP Jun 20 '11 at 9:52
  • 1
    \$\begingroup\$ It should be noted that even with timeanddate.com, there are some problems: it only accepts years 1-3999, and it auto-adjusts for the 11-day discrepancy between the Julian and Gregorian calendars for dates prior to Sept. 3, 1752 (so 17520903 through 17520914 are not valid dates). Keep this in mind when testing results. \$\endgroup\$ – rintaun Jun 20 '11 at 13:18
3
\$\begingroup\$

Perl 5.14, score = -162

-163 -181 -196 -214 -167 -213 -234
  • code.length = 211: 208 source characters + 3 for running perl with the -p option
  • bonus = 5.5: default, order, year 0, never-valid month/day, invalid date, wholly-invalid input

Code

$_=eval(join'-',map{($y,$m,$d)=/(....)(..)(..)/;die"E\n"if!($m*$d)||$m>12||$d>30+($m&1^$m>7)-($m==2)*(2-!($y=~s/00$//r%4));$y-=($m<3)-400;$d+int(($m+9)%12*30.6+.4)+int(365.2425*$y)}/^(\d{8})[ ,-](\d{8})$/)//E

Calculates a modified Julian day number for each date (ignoring the epoch-related adjustments to save code length) and subtracts the two. (ref. "Julian Day" at Wikipedia).

  • requires perl 5.14+ for the /r option on the substitutions
  • month-length calculation to get the invalid date bonus: the 30+($m&1^$m>7) part gives the length of any month but February; the rest adjusts for February in an ordinary or leap year

Assumptions

  • "use Gregorian calendar" means the proleptic Gregorian calendar for dates before whichever Julian to Gregorian transition we're using. That is, don't subtract 11 days for intervals that cross, for example, the 3 Sep 1752 - 14 Sep 1752 British transition.
  • "handle year 0" means, for example, 00000101-00010101 should give 366, as 0 is an integral multiple of 400, and so year 0 is a leap year.
\$\endgroup\$
  • \$\begingroup\$ With the changes you made, it seems like your program now accepts invalid months and days, like 20111300-20119999 returns 2717. \$\endgroup\$ – migimaru Aug 27 '11 at 13:48
  • \$\begingroup\$ @migimaru: I have indeed optimized out the correctness. Darn. I'll edit and maybe come back to it. \$\endgroup\$ – DCharness Aug 27 '11 at 15:44
2
\$\begingroup\$

PHP, Score: -539.1

  • 706 characters
  • All bonus items; bonus = 5.5

Code

<?php $a='(\d{4})(0[0-9]|1[0-2])([0-2][0-9]|3[01])';@$p=preg_match;if(!$p('/^(\d{8})[- ,](\d{8})$/',fgets(STDIN),$z))@die(E);unset($z[0]);sort($z);foreach($z AS$x){if(!$p('/(\d{4})(0[0-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])/',$x,$w))die(E);$n[]=$w;}$m=array(31,28,31,30,31,30,31,31,30,31,30,31);$r=0;$b=$n[0][1];$c=$n[0][2];$d=$n[0][3];$e=$n[1][1];$f=$n[1][2];$g=$n[1][3];@$t=str_pad;if((($b.$e==229)&&(!(!($b%4)+!($b%100)-!($b%400))))||($c>12))die(E);for($z=$b.$c.$d;;$s=$d,$r++){if($z==$e.$f.$g)break;if($z>$e.$f.$g)@die(E);if(@$s==$d)$d++;if((($c!=2)&&($d>$m[$c-1]))||(($c==2)&&($d>($m[$c-1]+!($b%4)-!($b%100)+!($b%400))))){$c++;$d=1;}if($c>12){$b++;$c=1;}$z=$b.$t($c,2,0,0).$t($d,2,0,0);}echo($r>0)?--$r:0;

Ungolfed

<?php
$a='(\d{4})(0[0-9]|1[0-2])([0-2][0-9]|3[01])';
@$p=preg_match;
if(!$p('/^(\d{8})[- ,](\d{8})$/',fgets(STDIN),$z)) @die(E);
unset($z[0]);
sort($z);
foreach($z AS $x)
{
        if (!$p('/(\d{4})(0[0-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])/',$x,$w)) die(E);
        $n[]=$w;
}
$m=array(31,28,31,30,31,30,31,31,30,31,30,31);
$r=0;
$b=$n[0][1];
$c=$n[0][2];
$d=$n[0][3];
$e=$n[1][1];
$f=$n[1][2];
$g=$n[1][3];
@$t=str_pad;
if ((($b.$e==229)&&(!(!($b%4)+!($b%100)-!($b%400))))||($c>12)) die(E);
for ($z=$b.$c.$d;;$s=$d,$r++)
{
        if ($z==$e.$f.$g)break;
        if ($z>$e.$f.$g)@die(E);
        if (@$s==$d)$d++;
        if ((($c!=2)&&($d>$m[$c-1]))||(($c==2)&&($d>($m[$c-1]+!($b%4)-!($b%100)+!($b%400)))))
        {
                $c++;
                $d=1;
        }
        if ($c>12)
        {
                $b++;
                $c=1;
        }
        $z=$b.$t($c,2,0,0).$t($d,2,0,0);
}
echo($r>0)?--$r:0;

Note

Calculates the number of days by iterating through each valid date between the two provided. It's pretty slow on larger ranges. I'm sure this isn't the best way to solve this, but I got impatient, and this is what I ended up with. :)

Also, I know the "ungolfed" code still isn't very readable, but rewriting it completely would require too much effort.

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

Ruby 1.9, Score: -175 -186 -191 -199

  • Code length: 229 243 250 260 characters
  • Bonus: 5.5 (default, order, year 0, invalid month/day, invalid date, invalid input)

The code accepts input through stdin.

h=->n{n/4-n/100+n/400+1}
u,v=gets.split(/[ ,-]/).map{|s|s=~/^\d{8}$/?(d,e,f=[s[0,4],s[4,2],s[6,2]].map &:to_i;x=[0,y=31,28+h[d]-z=h[d-1]]+[y,30,y,30,y]*2
(!x[e]||e*f<1||f>x[e])?0:d*365+z+eval(x[0,e]*?+)+f):0}
puts (v*u>0)?u-v :?E

Notes:

  • h returns the number of leap years up until that year (including year 0 for the bonus).
  • The regex handles the invalid input bonus.
  • The (!x[e]||e*f<1||f>x[e]) condition handles the invalid month/day/date bonuses.
  • The result is displayed as the first date minus the second date, so if the second date is later it will output as a negative number.
  • Does not adjust for the change between Julian and Gregorian calendars, so 33320229 17000101 results in 596134.
\$\endgroup\$
  • \$\begingroup\$ Thanks for error-checking my solution and for pushing me to keep improving. I particularly like your February-length calculation here. \$\endgroup\$ – DCharness Aug 28 '11 at 8:32
  • \$\begingroup\$ @DCharness Thanks for pushing me as well. I realized there was plenty of room for improvement in my original submission. \$\endgroup\$ – migimaru Aug 28 '11 at 9:44
1
\$\begingroup\$

Python, Score: -478

  • characters: 455
  • bonus: reverse dates, invalid day/month, invalid date

solution:

import re
a=re.split('[-, ]',raw_input())
def c(x):return x[0]
def f(x,y=3):return(1if x%400==0 or x%100!=0and x%4==0 else 0)if y>2 else 0
t=[31,28,31,30,31,30,31,31,30,31,30,31]
[q,w,e],[i,o,p]=sorted([map(int,[a[x][:4],a[x][4:6],a[x][6:]])for x in[0,1]],key=c)
print sum(map(f,range(q,i)))+(i-q)*365+p+sum(t[:o-1])-e-sum(t[:w-1])+f(i,o)-f(q,w)if 0<w<13and 0<e<32and 0<o<13and 0<p<32and(e<=t[w-1]or(f(q)and e==29))and(p<=t[o-1]or(f(i)and p==29))else 'E'

I don't have "ungolfed" version as this is how I wrote it. I didn't test it properly so if you find a bug - please comment.

edit: hopefully fixed a bug pointed out in a comments and added unpacking in form of [a,b],[c,d]=[[1,2],[3,4]

\$\endgroup\$
  • \$\begingroup\$ Sorry, but when I tested with Python 2.7 Shell, invalid inputs like '20000001,20010101' doesn't print E. (FYI, 0>-1>12, 0>6>12, 0>13>12 returns False.) \$\endgroup\$ – JiminP Jun 22 '11 at 0:41
  • \$\begingroup\$ Thanks. I'm quite new to python. Writing this script I learned that python does this x<y<z comparison or there is a x if y else z. Tried to fix it. \$\endgroup\$ – rplnt Jun 22 '11 at 10:42
  • \$\begingroup\$ @rpInt: for golfing, there is also the [x,z][y] which is shorter than x if y else z, though it doesn't always work since unlike the if-expression it's not lazy. \$\endgroup\$ – Lie Ryan Sep 1 '11 at 15:51
1
\$\begingroup\$

PHP, score: -516

chars : 685 676

bonus : 5.5

<? $z='/((\d{1,4})(\d\d)(\d\d))[- ,]((\d{1,4})(\d\d)(\d\d))/';if(!preg_match($z,$argv[1],$m))die('E');$s=1;if($m[1]>$m[5]){if(!preg_match($z,"$m[5] $m[1]",$m))die('E');$s=-1;}$b=array(31,28,31,30,31,30,31,31,30,31,30,31);list($x,$v,$c,$d,$e,$w,$f,$g,$h)=$m;if($d>12||1>$d||$g>12||1>$g||1>$e||1>$h||($e>$b[$d-1]&&!($d==2&&$e<30&&$c%4==0))||($h>$b[$g-1]&&!($g==2&&$h<30&&$f%4==0)))die('E');$z='array_slice';$y='array_sum';$x=$d!=$g||$e>$h;$r=$x?$b[$d-1]+$h-$e:$h-$e;$d+=$x;if($d>12){$c++;$d=1;}$r+=$d>$g?$y($z($b,$d-1,13-$d))+$y($z($b,0,$g-1)):($d!=$g?$y($z($b,$d-1,$g-$d)):0);$r+=($f-$c-($d>$g))*365;for($i=$c;$i<=$f;$i++)if($i%4==0&&$i.'0229'>$v&&$i.'0229'<$w)$r++;echo $s*$r;
\$\endgroup\$
  • \$\begingroup\$ PHP code needs the <? at the beginning to run, otherwise it just prints out the code. \$\endgroup\$ – Gareth Aug 24 '11 at 16:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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