4
\$\begingroup\$

Given a positive integer input n seconds, output the time in human readable format.

The time will be considered from 00:00:00, 1st January, 1 A.D.

Leap years must be taken into account.

Assume that the gregorian calendar was followed since the beginning.

(Gregorian Calendar: Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400. For example, the years 1700, 1800, and 1900 were not leap years, but the years 1600 and 2000 were)

Output must be a string of the format:

x Millennia/Millennium, x Centuries/Century, x Decades/Decade, x Years/Year, x Months/Month, x Days/Day, x Hours/Hour, x Minutes/Minute and x Seconds/Second

The 0 values must not be shown.

Program must handle at least up to 999 Millennia(inclusive).

Plural/Singular must be kept in mind.

(For millennium, both millennia and millenniums are valid)

,,and and spaces must also be kept in mind.

Case may be anything.

Examples:

1:1 second
2:2 seconds
60:1 minute
62:1 minute and 2 seconds
3600:1 hour
3661:1 hour, 1 minute and 1 second
31536001:1 year and 1 second
317310301:1 decade, 20 days, 13 hours, 45 minutes and 1 second
3499454714:1 century, 1 decade, 10 months, 22 days, 22 hours, 45 minutes and 14 seconds
73019321114:2 millennia, 3 centuries, 1 decade, 3 years, 10 months, 22 days, 45 minutes and 14 seconds

Reference: date-difference to duration converter

This is code-golf so shortest code wins

\$\endgroup\$
1
  • \$\begingroup\$ @JoKing, leap seconds are a bit unpredictable aren't they?. It is added when the difference between atomic and astronomical clocks get greater than 0.9 seconds right? Does this happen after fixed intervals of time? If yes, then you can use them. \$\endgroup\$ Dec 12, 2018 at 9:45

5 Answers 5

4
\$\begingroup\$

Perl 6, 273 245 233 230 bytes

-28 bytes thanks to nwellnhof!

{S/rys/ries/}o{S:th(*)/\,/ and/}o{join ', ',map {$_~'s'x(.[0]>1)},grep *[0],(|[R,](.[0].polymod(10 xx 3)),|.skip Z <millennium century decade year month day hour minute second>)}o{~DateTime.new($_-62135596800)~~m:g/\d+/Z-(^6 X<3)}

Try it online!

I think there might be a trick or two I can use with EVAL to make this shorter. This solution takes into account leap seconds, so the last test case is a bit off. Both no longer applicable.

(Old) Explanation:

{                                      } # First code block
 DateTime.new($_-62135596800)  # Initialise a date time object with input amount of seconds
{  ...  }o    # Pass value to the next code block
 fmt(.year-1: "%04d").comb Z   # Zip each digit of the year with
                             <millennium century decade year>  # The appropriate word
 <month day hour minute second>.map:{  ...  }   # For each of these words
                                ".$^a".EVAL     # EVAL the method on the DateTime
                                           -(2>$++)     # -1 if month or day
                                                   ,$a  # Followed by the word itself
  grep +*[0],  # From these, filter out the ones with value 0
  map {$_~'s'x(.[0]>1)},    # Add plurals if greater than 1
  join ', ',                # Join with commas
 {  ...  }o         # Pass string to next code block
  S:th(*)           # Substitute the last instance of
         /\,/       # A comma
            / and/  # With ' and'
 {  ...  }o         # Pass to next code block
  S/rys/ries/       # Fix plural of century
\$\endgroup\$
2
  • 1
    \$\begingroup\$ As of commenting, only this one and the C(gcc) by nwellnhof work for 999 millennium (31525395004800 seconds). \$\endgroup\$
    – KGlasier
    Dec 12, 2018 at 17:19
  • 1
    \$\begingroup\$ @KGlasier Thanks for pointing that out; apparently JavaScript's date library maxes out at 1e8 days, so I've had to adjust my answer to compensate. \$\endgroup\$
    – Neil
    Dec 19, 2018 at 23:53
2
\$\begingroup\$

Python 2, 304 297 bytes

lambda s:' and '.join(', '.join([`n`+' '+d+'s'*(n>1)for n,d in zip([int(str(datetime(1,1,1)+timedelta(0,s))[i:i-~(i>4)])-(2<i<9)for i in[0,1,2,3,5,8,11,14,17]],'millennium century decade year month day hour minute second'.split())if n]).replace('rys','ries').rsplit(', ', 1))
from datetime import*

Try it online!

Centuries are annoying...

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

Wolfram Language 255 251 bytes

If an Oxford comma is acceptable, this works:

Row[#,"  "]&/@(Cases[Partition[StringSplit@ToString@UnitConvert[#~Quantity~"Seconds",MixedUnit@{"Millenium","Century", "Decades","Years","Months","Weeks","Days","Hours","Minutes","Seconds"}],2],Except@{"0",_}]/.{a__,{b__}}:> {a,{"and",b}})&

UnitConvert is used to convert the number of seconds into mixed unit representation.

Cases displays only those measures in which the magnitude is not zero.

The remaining functions, Row, Partition, ToString and StringSplit merely format the output as required.

/.{a__,{b_, c_}}:> {a,{"and",b, c} inserts "and" before the final measure in those answers containing more than one unit of measure.

\$\endgroup\$
1
\$\begingroup\$

C (gcc) / glibc / Linux / 64 bits, 337 bytes

int*gmtime();f(long s){s-=62135596800;int*t=gmtime(&s),y=t[5]+1899,i=10,j=0,c=0,v[]={1,*t,t[1],t[2],t[3]-1,t[4],y%i,y/i%i,y/100%i,y/1e3},l[]={0,"second","minute","hour","day","month","year","decade","centur","millenium"};for(;i--;)v[i]&&(j&&printf("%s%d %s%s%s",c++?i?", ":" and ":"",v[j],l[j],j-8?"":v[j]<2?"y":"ie","s"+(v[j]<2)),j=i);}

Try it online!

Explanation

int*gmtime();  // Declare gmtime
f(long s){     // Function taking seconds as long
  s-=62135596800;   // Subtract constant for 0001-01-01
  int
    *t=gmtime(&s),  // Break down time
    y=t[5]+1899,    // Years since 1 AD
    i=10,
    j=0,
    c=0,
    v[]={  // Values
      1,        // Sentinel
      *t,       // Seconds
      t[1],     // Minutes
      t[2],     // Hours
      t[3]-1,   // Month days
      t[4],     // Months
      y%i,      // Years
      y/i%i,    // Decades
      y/100%i,  // Centuries
      y/1e3     // Milleniums
    },
    // Labels
    l[]={0,"second","minute","hour","day","month","year","decade","centur","millenium"};
  for(;i--;)  // Loop from i=9 to 0
    v[i]&&(     // If current value not 0 (or sentinel reached)
      j&&printf("%s%d %s%s%s",  // Print if previous index not 0
                c++?i?", ":" and ":"",  // Separator
                v[j],  // Value
                l[j],  // Label
                j-8?"":v[j]<2?"y":"ie",  // Handle century/centuries
                "s"+(v[j]<2)),  // Plural
      j=i  // Remember index
    );
}
\$\endgroup\$
1
  • \$\begingroup\$ As of commenting, only this one and the perl 6 by Jo King work for 999 millennium (31525395004800 seconds). \$\endgroup\$
    – KGlasier
    Dec 12, 2018 at 17:18
1
\$\begingroup\$

JavaScript (ES6), 384 bytes

f=
(n,r=[],d=new Date(n%31556952e4*1e3-621355968e5),y=d.getUTCFullYear()+(n/31556952e4|0)*1e4-1,g=(u,n=d[`getUTC${u}s`](),p=`s`,s=``)=>(n|=0)&&r.push(n+` `+u+(n>1?p:s)))=>(g(`Millenni`,y/1000,`a`,`um`),g(`Centur`,y/100%10,`ies`,`y`),g(`Decade`,y/10%10),g(`Year`,y%10),g(`Month`,d.getUTCMonth()),g(`Day`,d.getUTCDate()-1),g`Hour`,g`Minute`,g`Second`,g=r.pop(),r[0]?r.join`, `+` and `+g:g)
<input oninput=o.textContent=f(this.value)><pre id=o>

\$\endgroup\$
0

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.