How old is it roughly?

29

3

Write a short program which takes in a positive number of seconds representing an age, and outputs an estimate of that time in English.

Your program must output the least precise amount of time which has passed, among the following metrics and their lengths in seconds:

second = 1
minute = 60
hour   = 60 * 60
day    = 60 * 60 * 24
week   = 60 * 60 * 24 * 7
month  = 60 * 60 * 24 * 31
year   = 60 * 60 * 24 * 365

Examples

input      : output
1          : 1 second
59         : 59 seconds
60         : 1 minute
119        : 1 minute
120        : 2 minutes
43200      : 12 hours
86401      : 1 day
1815603    : 3 weeks
1426636800 : 45 years

As you can see above, after the time of say, 1 day (60 * 60 * 24 = 86400 seconds), we no longer output minute(s) or hour(s), but only days until we surpass the time of one week, and so on.

Consider the given length of time to be an age. For example, after 119 seconds, 1 minute has passed, not 2.

Rules

  • No specification for 0 or negative inputs.
  • Follow proper pluralization. Every measure greater than 1 must include an s following the word.
  • You may not use a pre-existing library which serves the function of the entire program.
  • This is a code golf, shortest program wins the internet points.
  • Have fun!

bitconfused

Posted 2018-03-18T02:26:49.250

Reputation: 529

3I don't understand how we choose a unit or amount. Do we round? – xnor – 2018-03-18T02:35:00.207

1@xnor we integer divide and use the smallest non-zero value along with its unit (possibly pluralised). Hence 59 -> "59 seconds" and 86401 -> "1 day". – Jonathan Allan – 2018-03-18T03:21:09.000

5

Welcome to PPCG! Nice first challenge. For future reference there is a sandbox which is useful for getting feedback before posting to main.

– Jonathan Allan – 2018-03-18T03:24:10.000

It's too bad that weeks are included 'cause if they weren't there would be a 33 byte Mathematica solution: Floor[DateObject@#-DateObject@0]&. – DanTheMan – 2018-03-18T03:49:27.847

@DanTheMan That's neat! Just tried it out and it doesn't appear to count for no pluralization though. – bitconfused – 2018-03-18T03:54:48.160

4

Note that Do X without Y is discouraged, as well as Non-observable program requirement.

– user202729 – 2018-03-18T14:02:53.730

1How should we round the numbers? Should 119 seconds be 1 minute or 2 minutes? What about 90? – user202729 – 2018-03-18T14:19:27.287

@user202729 Is that all in regard to my rule against using a library which "serves the function of the entire program"? I see that on quite a few other codegolfs, it seemed sensible. Also, as Johnathan Allan said, always round down to the nearest increment of the unit. 119 and 90 both round down to 60 = 1 minute. – bitconfused – 2018-03-18T18:24:42.553

1Postgres has a near built-in for this (justify_interval). – Denis de Bernardy – 2018-03-18T21:07:03.563

1

Rules should be put in the challenge, not comment. See Changing the challenge in the comments.

– user202729 – 2018-03-19T01:13:49.123

@user202729 what change? Could you be more specific? – bitconfused – 2018-03-19T05:10:50.790

The "integer-divide" (round down) part. – user202729 – 2018-03-19T05:11:36.133

Whether one uses "integer division" is implementation specific. Since Jonathan's comment and your question I've made clarifications within the challenge. Thanks! – bitconfused – 2018-03-19T05:16:26.917

But then, you should specify that the time should be rounded down. – user202729 – 2018-03-19T05:17:33.150

1My initial assumption was that people would understand that after 90 seconds, 1 minute has passed but 2 minutes haven't. It's one minute old, not two. Similar to how someone's age counts only how many years have passed since their birth. I have now edited the challenge, thanks again. – bitconfused – 2018-03-19T05:27:35.613

Answers

8

Jelly, 62 bytes

TṀị
“¢<<ð¢‘×\×€0¦7,31,365F⁸:µç“ɲþḣ⁹ḢṡṾDU¤µQƝṁ⁼ẹ»Ḳ¤ṭÇK;⁸Ç>1¤¡”s

A full program printing the result.
(As a monadic link it returns a list of an integer followed by characters)

Try it online!

How?

TṀị - Link 1: list of integers, K; list, V  e.g. [86401,1440,24,1,0,0,0], ["second","minute","hour","day","week","month","year"]
T   - truthy indexes of K                        [1,2,3,4]
 Ṁ  - maximum                                    4
  ị - index into V                               "day"

“¢<<ð¢‘×\×€0¦7,31,365F⁸:µç“...»Ḳ¤ṭÇK;⁸Ç>1¤¡”s - Main link: integer, N  e.g. 3599
“¢<<𢑠                                      - list of code-page indices = [1,60,60,24,1]
        \                                     - cumulative reduce with:
       ×                                      -  multiplication = [1,60,3600,86400,86400]
             7,31,365                         - list of integers = [7,31,365]
            ¦                                 - sparse application...
           0                                  - ...to index: 0 (rightmost)
         ×€                                   - ...of: multiplication for €ach = [1,60,3600,86400,[604800,2678400,31536000]]
                     F                        - flatten = [1,60,3600,86400,604800,2678400,31536000]
                      ⁸                       - chain's left argument, N    3599
                       :                      - integer divide         [3599,59,0,0,0,0,0]
                        µ                     - start a new monadic chain, call that X
                                ¤             - nilad followed by links as a nilad:
                          “...»               -   compression of "second minute hour day week month year"
                               Ḳ              -   split at spaces = ["second","minute","hour","day","week","month","year"]
                         ç                    - call the last link (1) as a dyad - i.e. f(X,["second","minute","hour","day","week","month","year"])
                                              -                             "minute"
                                  Ç           - call the last link (1) as a monad - i.e. f(X,X)
                                              -                             59
                                 ṭ            - tack                        [59,['m','i','n','u','t','e']]
                                   K          - join with spaces            [59,' ','m','i','n','u','t','e']
                                           ”s - literal character '
                                          ¡   - repeat...
                                         ¤    - ...number of times: nilad followed by link(s) as a nilad:
                                     ⁸        -   chain's left argument, X  [3599,59,0,0,0,0,0]
                                      Ç       -   call the last link (1) as a monad - i.e. f(X,X)
                                              -                             59
                                       >1     -   greater than 1?           1
                                    ;         - concatenate                 [59,' ','m','i','n','u','t','e','s']
                                              - implicit print - smashes to print  "59 minutes"

Jonathan Allan

Posted 2018-03-18T02:26:49.250

Reputation: 67 804

8

C, 194 180 144 128 characters

Thanks to @gastropher for the code reductions. I forgot that C allows for implicit parameters using K&R-style functions! Also thanks to @gmatht for the idea of putting literals inside instead of arrays. I extended that to the characters by abusing taking advantage of wide character/char16_t strings! The compiler doesn't seem to like \1 in its ☺ form though.

f(t,c,d){for(c=7;!(d=t/L"\1<ฐ\1•▼ŭ"[--c]/(c>2?86400:1)););printf("%d %.6s%s\n",d,c*6+(char*)u"敳潣摮業畮整潨牵 慤y†敷步 潭瑮h敹牡",(d<2)+"s");}

Try it online!

Original solution

I split up the arrays into separate lines to make it easier to see the rest of the solution.

char *n[]={"second","minute","hour","day","week","month","year"};
int o[]={1,60,3600,86400,604800,2678400,31536000};
f(int t){int c=7,d;while(!(d=t/o[--c]));printf("%d %s%s\n",d,n[c],d>1?"s":"");}

Try it online!

Running the divisors in order from largest to smallest, we get the coarsest unit of time. The program misbehaves if you give it 0 seconds, but as the specification explicitly excludes this value, I deem that to be acceptable.

ErikF

Posted 2018-03-18T02:26:49.250

Reputation: 2 149

Some tricks can be used to get it down to 183 bytes: Try it online!

– gastropner – 2018-03-18T06:46:27.793

1

Sorry, that one introduced a bug. Proper one at 180 bytes: Try it online!

– gastropner – 2018-03-18T06:53:22.740

@gastropner I think the last one has a bug too. '(d<1)' should be '(d<2)'... or '(d<=1)', but lets not go crazy. – gmatht – 2018-03-18T18:05:54.840

@gmatht You are quite right! – gastropner – 2018-03-18T18:11:53.987

OK, last one, I promise. 164 bytes.

– gastropner – 2018-03-18T18:32:29.050

OK, but your promises don't bind me. 160 bytes: Try it online!

– gmatht – 2018-03-18T18:59:38.773

both o and n are only used once. Can you not just use them as literals in the source? (e.g. n[c] becomes {"second","minute","hour","day","week","month","year"}[c]) – LambdaBeta – 2018-03-19T13:27:04.077

@LambdaBeta is that legal syntax in C? – NieDzejkob – 2018-03-19T17:09:50.873

Unfortunately only somewhat. Example:printf((char*[]){"alpha","bravo","charlie"}[1]); does print bravo. Here it can save 1 byte at least on the char array (removes the 'n' twice, removes the ';', adds '(' and ')') – LambdaBeta – 2018-03-19T17:15:22.020

you could remove \n from printf – GPS – 2018-03-22T15:19:35.380

Suggest 1/d instead of (d<2) – ceilingcat – 2018-08-01T17:33:25.573

7

Perl 5, 110 bytes

year31536000month2678400week604800day86400hour3600minute60second1=~s:\D+:say"$% $&",$_=$%>1&&"s"if$%=$_/$':reg

Try it online!

Ton Hospel

Posted 2018-03-18T02:26:49.250

Reputation: 14 114

5

Stax, 54 bytes

▀♂♂┼╕Qá◙à*ä∙Φò►⌠╨Ns↔║►πîÇ∙cI≡ªb?δ♪9gΓ╕┬≥‼⌡Öå01:♪EoE╘≡ë

Run and debug it

Here's the unpacked, ungolfed, ascii representation of the same program.

                            stack starts with total seconds
c60/                        push total minutes to stack
c60/                        ... hours 
c24/                        ... days
Yc7/                        ... weeks
y31/                        ... months
y365/                       ... years
L                           make a list out of all the calculated time units
`)sQP(dr'pk,oV4?_HIFD?x`j   compressed literal for singular forms of unit names
\                           zip totals with names
rF                          foreach pair of total and name (in reverse orer)
  h!C                       skip if the current total is falsey (0)
  _J                        join the total and unit name with a space
  's_1=T+                   concat 's' unless the total is one

Following execution, since there's no other output, the top of the stack is printed implicitly.

Run this one

recursive

Posted 2018-03-18T02:26:49.250

Reputation: 8 616

5

JavaScript (ES6), 131 bytes

n=>[60,60,24,7,31/7,365/31,0].map((v,i)=>s=n<1?s:(k=n|0)+' '+'second,minute,hour,day,week,month,year'.split`,`[n/=v,i])|k>1?s+'s':s

Try it online!

Arnauld

Posted 2018-03-18T02:26:49.250

Reputation: 111 334

I wasn't aware of the syntax that you used (split,). I learned something new. Great solution. – Makotosan – 2018-03-18T14:18:50.507

1@Makotosan Note that what is actually passed to split is the array [',']. Therefore, this only works with functions that force coercion to a string. – Arnauld – 2018-03-18T14:25:13.790

3

Java 8, 197 195 157 bytes

n->(n<60?n+" second":(n/=60)<60?n+" minute":(n/=60)<24?n+" hour":(n/=24)<7?n+" day":n<31?(n/=7)+" week":n<365?(n/=31)+" month":(n/=365)+" year")+(n>1?"s":"")

-38 bytes thanks to @OlivierGrégoire.

Explanation:

Try it online.

n->               // Method with long parameter and String return-type
  (n<60?          //  If `n` is below 60:
    n             //   Output `n`
    +" second"    //   + " second"
   :(n/=60)<60?   //  Else-if `n` is below 60*60
    n             //   Integer-divide `n` by 60, and output it
    +" minute"    //   + " minute"
   :(n/=60)<24?   //  Else-if `n` is below 60*60*24:
    n             //   Integer-divide `n` by 60*60, and output it
    +" hour"      //   + " hour"
   :(n/=24)<7?    //  Else-if `n` is below 60*60*24*7:
    n             //   Integer-divide `n` by 60*60*24, and output it
    +" day"       //   + " day"
   :n<31?         //  Else-if `n` is below 60*60*24*31:
    (n/=7)        //   Integer-divide `n` by 60*60*24*7, and output it
    +" week"      //   + " week"
   :n<365?        //  Else-if `n` is below 60*60*24*365:
    (n/=31)       //   Integer-divide `n` by 60*60*24*31, and output it
    +" month"     //   + " month"
   :              //  Else:
    (n/=365)      //   Integer-divide `n` by 60*60*24*365, and output it
    +" year")     //   + " year"
   +(n>1?"s":"")  //  And add a trailing (plural) "s" if (the new) `n` is larger than 1

Kevin Cruijssen

Posted 2018-03-18T02:26:49.250

Reputation: 67 575

1157 bytes. I just golfed your numbers to shorter ones and moved /= around where needed. – Olivier Grégoire – 2018-03-19T13:14:09.463

Personal favourite: n->{for(int t=60,d[]={1,t,t*=60,t*=24,t*7,t*31,t*365},x=7;;)if(n>=d[--x])return(n/=d[x])+" "+"second,minute,hour,day,week,month,year".split(",")[x]+(n>1?"s":"");} (162 bytes), probably a good base for golfing. – Olivier Grégoire – 2018-03-19T13:32:49.137

Save 9 bytes using n/7+ instead of (n/=7)+ etc. – Neil – 2018-03-21T12:37:10.820

@Neil I'm afraid that won't work. For example, if the input is 2678400, the output should be 1 month instead of 1 months (singular instead of plural). – Kevin Cruijssen – 2018-03-22T08:48:56.247

Oh, subtle, thanks for letting me know. – Neil – 2018-03-22T08:54:31.170

2

Kotlin, 205 203 196 bytes

x->val d=86400
with(listOf(1 to "second",60 to "minute",3600 to "hour",d to "day",d*7 to "week",d*31 to "month",d*365 to "year").last{x>=it.first}){val c=x/first
"$c ${second+if(c>1)"s" else ""}"}

Try it online!

Makotosan

Posted 2018-03-18T02:26:49.250

Reputation: 503

2

APL+WIN, 88 119 bytes

Original version missed out weeks and months as pointed out by Phil H;(

Prompts screen input of number of seconds

a←⌽<\⌽1≤b←⎕÷×\1 60 60 24 7,(31÷7),365÷31⋄b,(-(b←⌊a/b)=1)↓∊a/'seconds' 'minutes' 'hours' 'days' 'weeks' 'months' 'years'

Explanation

b←⎕÷×\1 60 60 24 7,(31÷7),365÷31 prompts for input and converts to years, days, hours, minutes, seconds

a←⌽<\⌽1≤b identify largest unit of time and assign it to a

a/'years' 'days' 'hours' 'minutes' 'seconds' select time unit

(-(b←⌊a/b)=1)↓∊ determine if singular if so drop final s in time unit

b, concatenate number of units to time unit from previous steps

Graham

Posted 2018-03-18T02:26:49.250

Reputation: 3 184

Did someone eat the weeks and months? – Phil H – 2018-03-21T17:19:43.860

@PhilH Cookie monster ? ;) Thanks. Answer edited accordingly. – Graham – 2018-03-21T20:07:21.290

It looked too neat, even for APL! Also, how are you counting the bytes? I count 119 characters rather than bytes... – Phil H – 2018-03-22T12:55:45.337

@PhilH I do not understand your comment first we agree on 119 bytes which I changed on editing the answer and above you do not say how many bytes you are questioning – Graham – 2018-03-22T13:11:07.527

2

T-SQL, 306 bytes (281 bytes without I/O)

DECLARE @n INT=1
DECLARE @r VARCHAR(30)=TRIM(COALESCE(STR(NULLIF(@n/31536000,0))+' year',STR(NULLIF(@n/2678400,0))+' month',STR(NULLIF(@n/604800,0))+' week',STR(NULLIF(@n/86400,0))+' day',STR(NULLIF(@n/3600,0))+' hour',STR(NULLIF(@n/60,0))+' minute',STR(@n)+' second'))IF LEFT(@r,2)>1 SET @r+='s'
PRINT @r

Razvan Socol

Posted 2018-03-18T02:26:49.250

Reputation: 341

Two small typos: TRIM is not defined, this possibly should be LTRIM. Between week and day, you have a +, should possibly be a , – Stephan Bauer – 2018-03-19T10:23:23.763

Indeed, instead of + it should be a , and I corrected that now. However, the TRIM function is defined since SQL Server 2017. Thanks. – Razvan Socol – 2018-03-19T11:37:58.157

2

R, 157 bytes

function(n,x=cumprod(c(1,60,60,24,7,31/7,365/31)),i=cut(n,x),o=n%/%x[i])cat(o," ",c("second","minute","hour","day","week","year")[i],"if"(o>1,"s",""),sep="")

Try it online!

cut is handy, since it splits ranges into factors, which are stored internally as integers, meaning we can use them as array indices as well. We can probably do something a bit more clever with the time period names, but I can't figure it out just yet.

Giuseppe

Posted 2018-03-18T02:26:49.250

Reputation: 21 077

1

JavaScript (Node.js), 177 bytes

x=>{return d=86400,v=[[d*365,'year'],[d*31,'month'],[d*7,'week'],[d,'day'],[3600,'hour'],[60,'minute'],[1,'second']].find(i=>x>=i[0]),c=parseInt(x/v[0]),c+' '+v[1]+(c>1?'s':'')}

Try it online!

Makotosan

Posted 2018-03-18T02:26:49.250

Reputation: 503

1

Python 2, 146 144 bytes

lambda n,d=86400:[`n/x`+' '+y+'s'*(n/x>1)for x,y in zip([365*d,31*d,7*d,d,3600,60,1],'year month week day hour minute second'.split())if n/x][0]

Try it online!

2 bytes saved thanks to Jonathan Allan

Chas Brown

Posted 2018-03-18T02:26:49.250

Reputation: 8 959

1if n/x saves a byte. – Jonathan Allan – 2018-03-18T12:55:13.390

1Reversing the order and indexing with 0 saves another. – Jonathan Allan – 2018-03-18T13:00:43.273

1

Batch, 185 bytes

@for %%t in (1.second 60.minute 3600.hour 43200.day 302400.week, 1339200.month, 15768000.year)do @if %1 geq %%~nt set/an=%1/%%~nt&set u=%%~xt
@if %n% gtr 1 set u=%u%s
@echo %n%%u:.= %

Neil

Posted 2018-03-18T02:26:49.250

Reputation: 95 035

1

PHP, 183 bytes

<?$a=[second=>$l=1,minute=>60,hour=>60,day=>24,week=>7,month=>31/7,year=>365/31];foreach($a as$p=>$n){$q=$n*$l;if($q<=$s=$argv[1])$r=($m=floor($s/$q))." $p".($m>1?s:"");$l=$q;}echo$r;

Try it online!

Jo.

Posted 2018-03-18T02:26:49.250

Reputation: 974

1

Julia 0.6, 161 bytes

f(n,d=cumprod([1,60,60,24,7,31/7,365/31]),r=div.(n,d),i=findlast(r.>=1),l=Int(r[i]))="$l $(split("second minute hour day week month year",' ')[i])$("s"^(l>1*1))"

Try it online!

niczky12

Posted 2018-03-18T02:26:49.250

Reputation: 301

0

Ruby, 129 bytes

->n{x=[365*d=24*k=3600,d*31,d*7,d,k,60,1].index{|j|0<d=n/k=j};"#{d} #{%w{year month week day hour minute second}[x]}#{d>1??s:p}"}

Try it online!

Asone Tuhid

Posted 2018-03-18T02:26:49.250

Reputation: 1 944

0

Perl 6/Rakudo 138 bytes

I'm sure there's further to go, but for now

{my @d=(365/31,31/7,7,24,60,60);$_/=@d.pop while @d&&$_>@d[*-1];$_.Int~" "~ <year month week day hour minute second>[+@d]~($_>1??"s"!!"")}

Explicate:

{ # bare code block, implicit $_ input
    my @d=(365/31,31/7,7,24,60,60); # ratios between units
    $_ /= @d.pop while @d && $_ > @d[*-1]; # pop ratios off @d until dwarfed
    $_.Int~   # implicitly output: rounded count
        " "~  # space
        <year month week day hour minute second>[+@d]~ # unit given @d
        ($_>1??"s"!!"")  # plural
}

Phil H

Posted 2018-03-18T02:26:49.250

Reputation: 1 376

0

R, 336

Working in progress

function(x){
a=cumprod(c(1,60,60,24,7,31/7,365/31))
t=c("second","minute","hour","day","week","month")
e=as.data.frame(cbind(table(cut(x,a,t)),a,t))
y=x%/%as.integer(as.character(e$a[e$V1==1]))
ifelse(x>=a[7],paste(x%/%a[7],ifelse(x%/%a[7]==1,"year","years")),
ifelse(y>1,paste(y,paste0(e$t[e$V1==1],"s")),paste(y,e$t[e$V1==1])))}

Riccardo Camon

Posted 2018-03-18T02:26:49.250

Reputation: 41

0

R, 246 bytes

f=function(x,r=as.integer(strsplit(strftime(as.POSIXlt(x,"","1970-01-01"),"%Y %m %V %d %H %M %S")," ")[[1]])-c(1970,1,1,1,1,0,0),i=which.max(r>0)){cat(r[i],paste0(c("year","month","week","day","hour","minute","second")[i],ifelse(r[i]>1,"s","")))}

Try it online!

This is using time formating instead of arithemtics, just for the hell of it. Maybe others could make this smaller?

niczky12

Posted 2018-03-18T02:26:49.250

Reputation: 301