Are we then yet?

I'm a time traveler, and I'm obsessed with the passage of time. I particularly love the moments when the clock hands pass 12, or when I get to flip to the next page of my calendar, or when everyone yells "Happy New Year!"

Please write for me a program to show me how far I am from the last such moment to the next, in the form of a progress bar. For example, if I tell it the time is 09:12, it should print this:

09:00 ####---------------- 10:00


If I tell it the month is May, 1982, it should print this:

1982-01 #######------------- 1983-01


Did I mention I'm a time traveler? I travel to anywhere from the first millisecond of 0 A.D. to the last millisecond of 9999 A.D., so the program needs to handle any date and time in that range.

Input

• Input will be in one of the following formats:

• YYYY-MM-DDThh:mm:ss.sss
• YYYY-MM-DDThh:mm:ss
• YYYY-MM-DDThh:mm
• YYYY-MM-DDThh
• YYYY-MM-DD
• YYYY-MM

These are the only formats that need be handled. Each part will have exactly the number of digits shown, which means fractional seconds may have trailing zeroes (e.g. .120, never .12). The T is a literal letter "T" delimiting the date from the time. Hours are on a 24-hour clock.

• Months and days are 1-based (more on this below).

• Invalid and out-of-range inputs need not be handled.

• At the programmer's discretion, input may have a single trailing newline.

Progress bar math

The program is concerned with the least- and second-least-significant units in the given input. For example, if the input has day-level precision (e.g. 2016-12-14), the progress bar will indicate what proportion of the days in the input month have elapsed versus what remains.

The progress bar will have 20 units (characters) and the proportion represented will be rounded to the nearest increment of 120. For example, given 2016-12-14T12:28, the progress bar will show Round(2860 × 20) = 9 of 20 units "filled."

1-based months and days

Although the day of December 1 (for example) is 01 in 2016-12-01, for the purpose of calculation it is the 0th day of the month, because the truncated units imply the 0th millisecond of the 0th minute of the 0th hour of the day. In other words, 2016-12-01 is 031 of the way through December and 2016-12-02 is 131, and so on.

Likewise, 2016-01 is the 0th millisecond of the 0th day of January, so in calculations it is 012, which means 2016-12 is 1112.

Yes, that means months and days will never completely fill the progress bar.

Differing month durations and leap years

Different months have different numbers of days and the output must reflect this—including leap years. The progress bar for February 6, 2017 will be different from the progress bar for February 6, 2016 (or January 6 of both years).

Miscellaneous

• Time travelers use the proleptic Gregorian calendar. TL;DR: No special cases like missing days in 1752. Input will include dates in the year 0 A.D.
• Time travelers ignore daylight savings.
• The program is not required to account for leap seconds, but it may.

Output

The program (or function) must print (or return as a string) a horizontally-oriented 20-character progress bar that is "filled in" for time that has elapsed and "open" for time that remains. It must "fill in" from left to right.

The progress bar must have a label to its left showing the beginning of the period being counted and another to its right showing the beginning of the next period, in the same format as the input (but showing only two units of precision). For our example 2016-12-14 valid output would be:

12-01 #########----------- 01-01


Here are the valid label formats for each of the possible periods:

• Months: YYYY-MM
• Days: MM-DD
• Hours: DDThh
• Minutes: hh:mm
• Seconds: mm:ss
• Milliseconds: ss.sss

No additional units may be included in the labels, and none may be omitted.

Output notes

• The progress bar's "filled in" units will be represented by a # character. "Open" units will be represented by -.
• There must be exactly one space between the progress bar and each label.
• Leading or trailing spaces and/or a single trailing newline are allowed.

Winning

This is . Shortest code in bytes wins. Standard rules apply. Standard loopholes forbidden.

Examples

Input                      Output
-----------------------    -------------------------------------
2016-12-12T12:17           12:00 ######-------------- 13:00
2016-12-12                 12-01 #######------------- 01-01
0000-01-01T00:00:00.000    00.000 -------------------- 01.000
0000-01-01T00:00           00:00 -------------------- 01:00
1899-12-31T23              31T00 ###################- 01T00
1899-12-31                 12-01 ###################- 01-01
1899-12                    1899-01 ##################-- 1900-01
1982-05-15T17:15           17:00 #####--------------- 18:00
1982-05-15T17              15T00 ##############------ 16T00
1982-05                    1982-01 #######------------- 1983-01
9999-12-31T23:59:59.999    59.000 #################### 00.000
9999-12                    9999-01 ##################-- 10000-01
2000-01-06                 01-01 ###----------------- 02-01
2000-02-06                 02-01 ###----------------- 03-01
2001-02-06                 02-01 ####---------------- 03-01
1742-09-10                 09-01 ######-------------- 10-01

• Do we have to worry about leap seconds? Dec 14 '16 at 14:21
• @Riley Good question. No. Dec 14 '16 at 14:38
• I assume Daylight Savings is ignored, since there's no one standardized form? Dec 14 '16 at 15:02
• @CAD97 Good question. You are correct. DST really complicates things for time travelers so we ignore it. Dec 14 '16 at 15:10
• "1899-12-31T23" example is not valid according to the provided list of input formats, the same for "1982-05-15T17". Please verify your test data. Dec 14 '16 at 19:17

JavaScript, 282 bytes

(x,v=x.split(/\D/g),l=v.length-2,[a,b,c,d]=("10e5,01,-,12,01,-,"+new Date(v[0],v[1],0).getDate()+",00,T,24,00,:,60,00,:,60,000,.,1000").split,.slice(l*3,l*3+4),t=(v[l+1]-b)/d*20+.5|0,n=v[l],o=((n|0)+1)%a,r=l?('0'+o).slice(-2):o)=>n+c+b+' '+'#'.repeat(t)+'-'.repeat(20-t)+' '+r+c+b


Passes all the tests

(
x,
v=x.split(/\D/g),
l=v.length-2,
[a,b,c,d]=("10e5,01,-,12,01,-,"+new Date(v[0],v[1],0).getDate()+",00,T,24,00,:,60,00,:,60,000,.,1000").split,.slice(l*3,l*3+4),
t=(v[l+1]-b)/d*20+.5|0,
n=v[l],
o=((n|0)+1)%a,
r=l?('0'+o).slice(-2):o
) =>n+c+b+' '+'#'.repeat(t)+'-'.repeat(20-t)+' '+r+c+b


Test function prints nothing for pass, values for fail.

function test(value,expected){
if (f(value)!=expected)
{
console.log(value);
console.log(f(value));
console.log(expected);
}
}


The test cases:

test('2016-12-12T12:17','12:00 ######-------------- 13:00')                 ;
test('2016-12-12','12-01 #######------------- 01-01')                       ;
test('0000-01-01T00:00:00.000','00.000 -------------------- 01.000')        ;
test('0000-01-01T00:00','00:00 -------------------- 01:00')                 ;
test('1899-12-31T23','31T00 ###################- 01T00')                    ;
test('1899-12-31','12-01 ###################- 01-01')                       ;
test('1899-12','1899-01 ##################-- 1900-01')                      ;
test('1982-05-15T17:15','17:00 #####--------------- 18:00')                 ;
test('1982-05-15T17','15T00 ##############------ 16T00')                    ;
test('1982-05','1982-01 #######------------- 1983-01')                      ;
test('9999-12-31T23:59:59.999','59.000 #################### 00.000')        ;
test('9999-12','9999-01 ##################-- 10000-01')                     ;
test('2000-01-06','01-01 ###----------------- 02-01')                       ;
test('2000-02-06','02-01 ###----------------- 03-01')                       ;
test('2001-02-06','02-01 ####---------------- 03-01')                       ;
test('1742-09-10','09-01 ######-------------- 10-01')                       ;

• I had to learn a new language to top this version... it is very concise! Dec 18 '16 at 4:49

Pyth, 213 bytes

My first code in pyth! Behold:

+%h=N:[d"%.4d"\-=Z"%.2d"\-Z\TZ\:Z\:Z\."%.3d")-*2lKr:w"[-T:.]"d7 3*2lKJ@K_2+@N1+%eN=b<lK4+d+*\#=Gs+*20c-eKb@=H[0^9T12?q2=k@K1+28+q0%=YhK4-q0%Y400q0%Y100+30%+k/k8 2 24 60 60 999)lK.5+*\--20G+d+%hN%+J1@H-lK1+@N1%eNb


My pyth code is closely based off of my previous python answer. Here is the ungolfed version with comments:

"K is the input, as a list of numbers"
Kr:w"[-T:.]"d7
"Y=year"
=YhK
"k=month"
=k@K1
"H = a list of denominators"
=H[0 ^9T 12 ?q2k+28+q0%Y4-q0%Y400q0%Y100 +30%+k/k8 2 24 60 60 999)
"J is the second-to-last number of the input"
J@K_2
"b is the +1 starting point for months and days"
=b<lK4
"G is the number of hashtags in the statusbar"
=Gs+*20c-eKb@HlK.5
"N is the formatted string"
=N:[d"%.4d"\-=Z"%.2d"\-Z\TZ\:Z\:Z\."%.3d")-*2lK3 *2lK
+%hNJ+@N1+%eNb+d+*\#G+*\--20G+d+%hN%+J1@H-lK1+@N1%eNb


Testing multiple values is easily accomplished by making the code loop, and adding a newline print to the end:

Wp+%h=N:[d"%.4d"\-=Z"%.2d"\-Z\TZ\:Z\:Z\."%.3d")-*2lKr:w"[-T:.]"d7 3*2lKJ@K_2+@N1+%eN=b<lK4+d+*\#=Gs+*20c-eKb@=H[0^9T12?q2=k@K1+28+q0%=YhK4-q0%Y400q0%Y100+30%+k/k8 2 24 60 60 999)lK.5+*\--20G+d+%hN%+J1@H-lK1+@N1+%eNb"\n"


Then I ran cat testinput | pyth code.pyth > output and diff output testoutput Or try it online.

Python 2, 371 bytes

This challenge was surprisingly difficult! It seemed like I was going to be just under 300 until I worked out the output string formatting.

The kind of cool part is that my answer does not use any date package:

import re
s=raw_input()
S=[int(i)for i in re.sub('[-T:.]',' ',s).split()]
l=len(S)
y,m=S[:2]
d=[0,20<<9,12,28+(y%4==0!=y%100)+(y%400==0)if m==2else 30+(m+m/8)%2,24,60,60,999]
a,n=S[-2:]
b=1-(1if l>3else 0)
h=int(20.*(n-b)/d[l]+.5)
x,y,z='- %.4d - %.2d - %.2d T %.2d : %.2d : %.2d . %.3d'.split()[l*2-3:l*2]
print x%a+y+z%b+' '+'#'*h+'-'*(20-h)+' '+x%((a+1)%d[l-1])+y+z%b