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')}
interval <x> <unit>
(eg:2008-12-30 + interval 53 weeks
). \$\endgroup\$