9
\$\begingroup\$

Your mission is to write a program that accepts a date in the formats 2014-02-27 (calendar date) and 2014-W09-4 (week date) from standard input and outputs the date in the format it wasn't entered in (to standard output with a trailing newline). The dates must conform to the standard ISO 8601, i.e. with weeks that starts on Mondays and where the first week in a (week) year is the one that contains the first Thursday in the calendar year. You are not allowed to use any libraries, built-in functions or operators that relates to the handling of dates and time.

For example:

>./aprogramnamethatmostprobablyrelatestobutterfliesandbiscuits
2014-02-27
2014-W09-4
>./aprogramnamethatmostprobablyrelatestobutterfliesandbiscuits
2014-W09-4
2014-02-27
>./aprogramnamethatmostprobablyrelatestobutterfliesandbiscuits
2008-12-30
2009-W01-2
>./aprogramnamethatmostprobablyrelatestobutterfliesandbiscuits
2009-W53-7
2010-01-03
>

Years prior to 1583 and after 9999 need not yield the correct answer.

The shortest answer (in characters, not bytes) will be declared the winner and therefore accepted in 19½ days.

A nice resource: ISO week date

\$\endgroup\$
8
  • \$\begingroup\$ Prior to 1582 or prior to 1583? \$\endgroup\$ Feb 27, 2014 at 0:01
  • 1
    \$\begingroup\$ What about operators? MySQL has the interval <x> <unit> (eg: 2008-12-30 + interval 53 weeks). \$\endgroup\$ Feb 27, 2014 at 0:04
  • \$\begingroup\$ I do indeed mean 1583, sorry for my immensely confused range of years. I will immediately correct it. Date and time related operators gives an immense advantage to a language, so they are henceforth disallowed. \$\endgroup\$
    – Fors
    Feb 27, 2014 at 0:21
  • 2
    \$\begingroup\$ See en.wikipedia.org/wiki/Zeller%27s_congruence for the answer. I implemented this as a statement function in Fortran IV about 45 years ago but have lost the punch cards containing it. \$\endgroup\$ Feb 27, 2014 at 0:24
  • \$\begingroup\$ Can we get some external informations, like which day of the week the years starts and how many days the month have? They aren't directly manipulation the data, but gathering precious information. \$\endgroup\$ Feb 27, 2014 at 0:32

2 Answers 2

9
\$\begingroup\$

Javascript: 422 characters

Originally this had 825 characters, and was still a bit comprehensible and legible, beside the fact of being golfed. I was sure that it could be reduced in something between 10% to 20%. This answer passed by tons of edits from me and got suggestions from @Ismael Miguel and specially from @DocMax resulting in an extremely-golf-crushed source down to 422 characters, with the side-effect of making it heavily obfuscated, a reduction of almost 49%. Really thanks for both, I wish that you two could get a badge for this. I did not thought that it could be reduced by so much.

Here is the code, 422 characters:

x=prompt(E=eval);W=x[f=5]=='W';A=x.match(/\d+/g);y=+A[a=0];for(t="'012011223445569'[z]-30+30*z";q=a%4<1&(a%100>0|a%400<1),a++<y;f=(f+1+q)%7)v=f;z=w=+A[1]-W;r=+A[2];if(W){w+=f>3;h=7*w+r-f;for(z=1;h-q*(z>2)>=E(t);++z);w=1+(10+z--)%12;d=h-q*(z<3)-E(t);y+=z?z>12:-1}else{g=r+E(t)-(w>1&&!q+w-2?2-q:2);d=(f+g)%7;w=0|g/7+(f<4);w=w>11&r-d>26?(++y,1):w<1?(--y,53^v>3):w}alert(y+(W?'-':'-W')+(w>9?'':0)+w+'-'+(!W|++d>9?'':0)+d+'\n')

Pretty complex. Here a (somewhat long) partially-ungolfed (but still badly obfuscated) and commented version:

x = prompt(E = eval); // Receives input, but has a side-effect of having a strange message in the prompt box.

// Year 0 of the Gregorian calendar (if such thing existed) would start on a 5=Friday. (0=Monday, 6=Sunday)
W = x[f = 5] == 'W'; // Checks if there is a W in the sixth char.
A = x.match(/\d+/g); // Divide the input in 3 numbers.

y = +A[a = 0]; // Parse the year.

// The t variable is used to create a table with cumulative months length starting from December of the previous year, this table is acessed by using the eval() function.
// Calculate the weekday of 01/01 of the year in the f variable. Do that by counting Gregorian calendar years from 0 until now.
// Count 2 weekdays for leap years and 1 for non-leap years.
// The variable q defines if the year is a leap year.
// The v variable stores the weekday of 01/01 of the prevoius year.
// The % 7 ensures that the weekday are kept in the interval 0-6, where 0 is Monday and 6 is Sunday.
for (t = "'012011223445569'[z] - 30 + 30 * z"; q = a % 4 < 1 & (a % 100 > 0 | a % 400 < 1), a++ < y; f = (f + 1 + q) % 7)
  v = f;

z = w = +A[1] - W; // Parse the second number, might be the week number or the month, accordingly to the W.
r = +A[2]; // Parse the third number, might be the week day or the day in the month, accordingly to the W.

// Converts week date to calendar date.
if (W) {

  // The expression "7 * (f > 3) - f" maps the offset between the calendar date and the week date in the following manner:
  // {0Mon:1, 1Tue:0, 2Wed:-1, 3Thu:-2, 4Fri:4, 5Sat:3, 6Sun:2}
  // Obtain the day within the year and store it in h.
  w += f > 3;
  h = 7 * w + r - f;

  // Finds the day offset in the table to the corresponding month.
  for (z = 1; h - q * (z > 2) >= E(t); ++z);

  // This calculates the day in the month and the month.
  w = 1 + (10 + z--) % 12;
  d = h - q * (z < 3) - E(t);

  // Fix-up the year, if needed.
  y += z ? z > 12 : -1

// Converts calendar date to week date.
} else {

  // Calculate how many days passed since 01/01
  g = r + E(t) - (w > 1 && !q + w - 2 ? 2 - q : 2);

  // Knowing the week day in which the year started and how many days passed since 01/01, calculate the week day of the given date.
  d = (f + g) % 7;

  // Calculates how many weeks passed since 01/01 and add one week if the year started on Monday, Tuesday, Wednesday or Thursday.
  w = 0 | g / 7 + (f < 4);

  // If we are in the last 3 days of the year, and 01/01 will fall on Monday, Tuesday, Wednesday or Thursday,
  // then we advance to the first week of the next year.
  // Do this by checking r + 3 - (d - 2) > 31, where:
  //   r is the date;
  //   (d - 2) is the weekday (d=1 for monday, 7 for sunday);
  //   (3 - (d - 2)) is the number of days until the week's Thursday and;
  //   > 31 means January of the next year.
  // r + 3 - (d - 2) > 31 is simplified to r - d > 26.
  w = w > 11 & r - d > 26 ? (++y, 1)

  // If we are at the 0th week of the year, go back to the last week of the previous year.
  // If the last year started on Friday, Saturday or Sunday this would be the 52nd week. It is the 53rd otherwise.
    : w < 1 ? (--y, 53 ^ v > 3)

  // Neither of the previous two cases.
    : w
}

// Output it.
alert(y + (W ? '-' : '-W') + (w > 9 ? '' : 0) + w + '-' + (!W | ++d > 9 ? '' : 0) + d + '\n')

The prompt box comes with the message with the serialization of the eval function. Ignore this message, it is just a side-effect of golfing some chars.

It passes for all provided test cases. If given invalid input, the behaviour is undefined.

Here are the test cases, had some bugs in previous versions (specially with the last two lines):

2014-02-27  <-->  2014-W09-4
2008-12-30  <-->  2009-W01-2
2010-01-03  <-->  2009-W53-7
2011-04-23  <-->  2011-W16-6
2012-01-01  <-->  2011-W52-7

In case you are curious, here it is the original, 825 characters code:

p=parseInt;x=prompt();y=p(x.substr(0,4));f=6;j="a%4<1&(a%100>0|a%400<1)";for(a=1583;a<y;a++)f+=eval(j)?2:1;f%=7;q=eval(j);if(x.contains('W')){w=p(x.substr(6,2))-1;r=p(x.substr(9))-1;u=f<1?3:f<5?1-f:f-4;h=w*7+r+u+1;e=q?1:0;d=h<1?(m=12,y--,h+31):h<32?(m=1,h):h<60+e?(m=2,h-31):h<91+e?(m=3,h-59-e):h<121+e?(m=4,h-90-e):h<152+e?(m=5,h-120-e):h<182+e?(m=6,h-151-e):h<213+e?(m=7,h-181-e):h<244+e?(m=8,h-212-e):h<274+e?(m=9,h-243-e):h<305+e?(m=10,h-273-e):h<335+e?(m=11,h-304-e):h<366+e?(m=12,h-334-e):(m=1,y++,h-365-e);alert(y+'-'+(m<10?'0':'')+m+'-'+(d<10?'0':'')+d+'\n')}else{m=p(x.substr(5,2));d=p(x.substr(8));g=d-1;for(b=1;b<m;b++)g+=[31,q?29:28,31,30,31,30,31,31,30,31,30,31][b-1];v=(f+g)%7;w=~~(g/7)+(f>0&f<5?1:0);if(v<1)w--,v=7;if(m>11&d>28&32-d+f>3)w=1,y++;if(w<1)w=53,y--;alert(y+'-W'+(w<10?'0':'')+w+'-'+(v<1?7:v)+'\n')}
\$\endgroup\$
26
  • \$\begingroup\$ You can reduce your code a few bytes by creating u='substr' and instead of using x.substr(), you can use x[u](). And instead of x.contains('W'), try /w/i.test(x). And that ` w = ~~(g / 7) + (f > 0 & f < 5 ? 1 : 0)` can be replaced with w=~~(g/7)+!!f&5. Just for the performance sake, try replacing all i++ with ++i (i is just an example var). \$\endgroup\$ Feb 27, 2014 at 13:56
  • \$\begingroup\$ @IsmaelMiguel Thanks. I did all your changes except the !!f&5. I used the longer ~~(f>0&f<5) instead. The reason is that !!f&5 gave me 1 for 5 and 6, meaning that it would mess-up years starting on Fridays or Saturdays. \$\endgroup\$ Feb 27, 2014 at 15:31
  • \$\begingroup\$ Then instead of !!f&5 you still can do (f>0&f<5) (without the ?1:0), which has the same effect \$\endgroup\$ Feb 27, 2014 at 16:11
  • \$\begingroup\$ @IsmaelMiguel Thanks, reduced further 2 chars. \$\endgroup\$ Feb 27, 2014 at 16:51
  • \$\begingroup\$ Sorry bothering you again, but you can change this f += eval(j) ? 2 : 1; for f += 2-!eval(j); or f += 1+!eval(j);. And where you have s = q ? h - 1 : h; you can replace by s = h-!!q;, reducing more 3 chars. And where you have ([dm] < 10 ? '0' : ''), you can replace with ([dm]>9?'':0) saving more 2 chars for each substitution. On your array, replace q ? 29 : 28 with 29-!q. I believe that there are other tiny details, but I think you can cut down almost 12 chars with these tips. \$\endgroup\$ Feb 27, 2014 at 17:23
4
\$\begingroup\$

C: 286 characters

In a quite unorthodox fashion I will give an entry to my own challenge (but I will not accept this answer even if it is the shortest when the time comes), just to show another (much less elegant than the JS-solution) way to attack the problem.

I[9],C[9],W[9],Y,M=5,D,y,w,d;main(){for(gets(I);sprintf(C,"%d-%02d-%02d",Y,M,D),sprintf(W,"%d-W%02d-%d",y,w,d),(strcmp(I,C)||puts(W)<0)&&(strcmp(I,W)||puts(C)<0);++D>28+(M^2?M+(M>7)&1^2:!(Y&3)&&(Y%25||!(Y&15)))&&(D=1,M=M%12+1)<2&&Y++,(d=d%7+1)<2&&(M>11&&D>28||M<2&&D<5?(w=1,y++):w++));}

I simply simulate the calendars day by day, increasing the month, week, year and week-year as needed. When either the week date or calendar date is equal to what was inputted I output the date in the format it wasn't inputted in (so if the input is invalid it will simply loop forever).

In a slightly more readable form:

I[9],C[9],W[9],Y,M=5,D,y,w,d;
main(){
        for(
                gets(I);

                sprintf(C,"%d-%02d-%02d",Y,M,D),
                sprintf(W,"%d-W%02d-%d",y,w,d),

                (strcmp(I,C)||puts(W)<0)&&
                (strcmp(I,W)||puts(C)<0);

                ++D>28+(M^2?M+(M>7)&1^2:!(Y&3)&&(Y%25||!(Y&15)))&&
                (D=1,M=M%12+1)<2&&Y++,

                (d=d%7+1)<2&&
                (M>11&&D>28||M<2&&D<5?(w=1,y++):w++)
        );
}
\$\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.