12
\$\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\$
13
  • \$\begingroup\$ Is 20030229 considered an invalid date by the third bonus? \$\endgroup\$
    – rintaun
    Jun 20, 2011 at 7:51
  • \$\begingroup\$ @rintaun Yes. It's invalid, unlike 20040229. :P \$\endgroup\$
    – JiminP
    Jun 20, 2011 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, 2011 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, 2011 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, 2011 at 13:18

6 Answers 6

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\$
2
  • \$\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, 2011 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, 2011 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\$

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\$
3
  • \$\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, 2011 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, 2011 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, 2011 at 15:51
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\$
2
  • \$\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, 2011 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, 2011 at 9:44
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\$
1
  • \$\begingroup\$ PHP code needs the <? at the beginning to run, otherwise it just prints out the code. \$\endgroup\$
    – Gareth
    Aug 24, 2011 at 16:55
1
+300
\$\begingroup\$

PowerShell, 294 bytes, ~-353 score, +3.5 bonus

function J($s){if($s.length-ne8){"E";exit};iex ('$Y_0,4));$M_4,2));$D_6,2))'-replace'_','=[int]($s.substring(');$B=($M-14)/12;if($m-notin1..12){'E';exit};$A=$Y+4800;($J=1461*($A+$B)/4+367*($M-2-12*$B)/12-(3*(($A+$B+100)/100))/4+$D-32075)};[math]::truncate((J("$($args[0])"))-(J("$($args[1])")))

Try it online!

It uses Julian Date to calculate difference between to YYYYMMDD dates.

Explanation

  • At first, to avoid redundant code, it's better to use a function. So we declare a function named J which calculates the julian date of a given date in YYYYMMDD format, and takes the parameter as $s.

  • Next, we need to check for invalid string. If the given string length is not 8 (YYYYMMDD length is 8), then output E and exit.

  • Parse the string. We used substrings for that. First 4 characters are the year ($Y), next 2 are the month ($M), and next 2 are the day ($D). To get a substring, we use .NET .substring() method for that, and cast it to type [int]. But its a huge part of code, and to golf it we replace =[int]($s.substring( with _ and build a string $Y_0,4));$M_4,2));$D_6,2)) and while in the code replace _ with =[int]($s.substring( again and evaluate the expression using iex (Which is an alias of Invoke-Expression).

  • Another check for month validity. 1..12 is the array of numbers between 1 to 12, if $M (month number) is not in the 1..12 array, we output E and exit.

  • Then the main logic goes. We calculate the Julian date. I found the algorithm here

  • End the function.

  • We compute date difference after that. $args is an automatic array in Powershell which contains the parameters provided to the script (Not the function). We cast 0 and 1 index to string ($() operator is sub-expression operator, which evaluates a string as code inside a double-quoted string, and then surround double quotes to cast to string), pass each to J function and subtract the returned integer Julian dates.

  • At last, truncate the result using .NET [math]::truncate() method.

Bonus

  • Initial bonus, so bonus +1

  • The code will work if the first date is smaller than second one (i.e. 20100101 20110101), so bonus +1

  • Cannot handle year 0, so bonus +0

  • Can handle invalid month, but not invalid Day, so bonus +0.5

  • Cannot determine invalid dates, so bonus +0

  • Can handle invalid strings in parameters, so bonus +1

Total: 1+1+0+0.5+0+1=3.5

Total score

  • Code length: 294 bytes
  • Total bonus: +3.5

$$ floor(-4.2*294/3.5) = -353 $$

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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