8
\$\begingroup\$

The Task

Given the positive value of unix timestamp output any of two valid ISO 8601 time formats:

  • YYYY-MM-DDTHH:mm:SS or, alternatively,
  • YYYY-MM-DD HH:mm:SS

You may ignore any leap seconds, if you wish, as well as the timezone part of the date.

Also, ignore the year 2038 problem.

Rules

  • You must not use any built-in date functions,
  • You may not use any string formatting functions (like printf), however sorting, reversing, changing case etc. is allowed,
  • Your program may not contain any numbers (ie. characters 0, 1, 2, ... 9) and letters which mean hexadecimal number (A, a, B, b, ... f), if used in context of hexadecimal number,
  • The only exception are line numbers (or labels and goto statements), if your language requires them,
  • You may use any function or method that returns a number, but only once (each) in your program:

    For example, you can use something like zero = length(''); to have number 0, but then you can't use one = length('a'); because you would use the length() function twice, so you can consider using one = cos(zero);

  • The same applies to built-in constants or variables

    You may use built-in constants like __LINE__ etc., but only once for each value (for example, __FILE__ is "really" constant in a file, so you can use it only once in a file, but __LINE__ varies for each line, so you can use it once per line).

  • If a function or a constant is used in a loop that has a chance to be executed more than once, it is considered to be used more than once and this is forbidden,
  • All loopholes are forbidden (especially, loading the result from another site or executing an external program to do the calculations).
  • Any errors, compiler warnings are allowed, however the ISO 8601 string must be valid (no error messages within the output string)

Testing

Your code must return a valid value which will be tested with the site http://www.unixtimestamp.com/.

Notes

This question is a popularity contest, so the highest scored answer wins (not earlier than at Midnight on Monday, September 7th UTC). The main idea is to show your skills when limited to not use numbers. When voting, please consider how numbers are represented by the author and what tricks they use. In my opinion such constructions like

 eighty_six_thousand_four_hundred = one;
 eighty_six_thousand_four_hundred++;
 eighty_six_thousand_four_hundred++;
 //... 86399 times

or

// 86399 empty lines
...
$eighty_six_thousand_four_hundred = __LINE__;

should be avoided.

I also kindly ask that you provide an online version of your program, so everyone can test it (the program must not be limited to your environment).

\$\endgroup\$
13
  • \$\begingroup\$ I feel like the entire difficult part of this question is the no function reuse part, so it should be made more prominent. \$\endgroup\$
    – isaacg
    Commented Sep 1, 2015 at 9:36
  • 1
    \$\begingroup\$ You've made it optional to ignore leap seconds, but imposed compatibility with a website where I don't see any leap-second-related options. How is the testing going to work? \$\endgroup\$ Commented Sep 1, 2015 at 10:26
  • \$\begingroup\$ @PeterTaylor I will check a list of leap seconds since 1970 to the timestamp given and if the difference between your output and true value will not be greater than number of leap seconds added, I will consider the result valid. For example, up to now there were 26 leap seconds, so you may give result within 26 seconds accuracy \$\endgroup\$
    – Voitcus
    Commented Sep 1, 2015 at 10:32
  • \$\begingroup\$ If the same function name is overloaded to have different behaviour in an overloaded version, are we allowed to use each overloaded variant once? For instance, in J, monadic | computes the magnitude (like abs() in C) whereas dyadic | computes the residue (like % in C). Are we allowed to use both? Does this clause extend to functions that can potentially return a number? What about high-order functions? \$\endgroup\$
    – FUZxxl
    Commented Sep 1, 2015 at 11:17
  • 1
    \$\begingroup\$ hundered=d; one=hundered/hundered; two=one+one eight=two<<two ten=two+eight;, and from then on I'm just turning the handle. \$\endgroup\$
    – Jasen
    Commented Sep 2, 2015 at 0:27

2 Answers 2

5
\$\begingroup\$

Inform 7

"ISO Eight Six Zero One" by "Dannii Willis"

There is a room.
Sixty is a number variable.
Hundred is a number variable.
Days per year is a number variable.
Days per leap year is a number variable.
Leap year period is a number variable.
Days per leap year period is a number variable.
Epoch is a number variable.
Seconds per day is a number variable.
Months is a list of numbers variable.

To initialise number variables:
    let thirty be ten times three;
    let thirty one be thirty plus one;
    now sixty is five times twelve;
    now hundred is ten times ten;
    now days per year is sixty times six plus five;
    now days per leap year is days per year plus one;
    now leap year period is hundred times four;
    now days per leap year period is leap year period times days per year plus hundred minus three;
    now epoch is leap year period times five minus thirty;
    now seconds per day is sixty times sixty times twelve times two;
    truncate months to zero entries;
    let X be zero; add X to months;
    increase X by thirty one; add X to months;
    increase X by thirty minus two; add X to months;
    increase X by thirty one; add X to months;
    increase X by thirty; add X to months;
    increase X by thirty one; add X to months;
    increase X by thirty; add X to months;
    increase X by thirty one; add X to months;
    increase X by thirty one; add X to months;
    increase X by thirty; add X to months;
    increase X by thirty one; add X to months;
    increase X by thirty; add X to months;

To decide whether (year - a number) is a leap year:
    if the remainder after dividing year by four is zero:
        if the remainder after dividing year by hundred is not zero:
            yes;
        otherwise if the remainder after dividing year by leap year period is zero:
            yes;
    no;

To say zero padded (X - a number):
    if X is less than ten:
        say zero;
    say X;

[ This general algorithim is taken from the PHP source code ]
To say (X - a number) as an ISO Eight Six Zero One date:
    initialise number variables;
    [ Split the timestamp into days and seconds ]
    let days be X divided by seconds per day;
    let seconds be the remainder after dividing X by seconds per day;
    [ Calculate the date ]
    let year be epoch;
    let month be twelve;
    let day be zero;
    increase days by one;
    [ Year ]
    if days is at least days per leap year period:
        let leap year period count be days divided by days per leap year period;
        increase year by leap year period times leap year period count;
        decrease days by days per leap year period times leap year period count;
    while days is at least days per leap year:
        increase year by one;
        if year is a leap year:
            decrease days by days per leap year;
        otherwise:
            decrease days by days per year;
    [ Month ]
    if year is a leap year:
        decrease entry one of months by one;
        decrease entry two of months by one;
    while month is greater than one:
        if days is greater than entry month of months:
            break;
        decrease month by one;
    [ Day ]
    now day is days minus entry month of months;
    [ Calculate the time ]
    let hours be seconds divided by (sixty times sixty);
    let minutes be the remainder after dividing (seconds divided by sixty) by sixty;
    let secs be the remainder after dividing seconds by sixty;
    say "[year]-[zero padded month]-[zero padded day] [zero padded hours]:[zero padded minutes]:[zero padded secs]";

Inform 7 allows the numbers one to twelve to be written in words - from these we calculate the other numbers which are needed. With that it was just a matter of transferring over the simplest algorithm I could find, which happened to be PHP's. I haven't accounted for negative timestamps. Maybe another day.

Here are some tests:

When play begins:
    say "0: [0 as an ISO Eight Six Zero One date][line break]";
    say "1000000000: [1000000000 as an ISO Eight Six Zero One date][line break]";
    say "1234567890: [1234567890 as an ISO Eight Six Zero One date][line break]";

Which produce the following correct output:

0: 1970-01-01 00:00:00
1000000000: 2001-09-09 01:46:40
1234567890: 2009-02-13 23:31:30
\$\endgroup\$
1
  • 3
    \$\begingroup\$ Thanks, I love this. Negative timestamps are not necessary to be handled. \$\endgroup\$
    – Voitcus
    Commented Sep 2, 2015 at 11:27
3
\$\begingroup\$

JavaScript (ES6)

So I'm not 100% sure I'm adhering to the rules totally, so please let me know if not and I'll remove.

var T = +prompt('Enter a timestamp'),
[one, two, three, four, five, six, seven, eight, nine, zero] = btoa('\xd7\x6d\xf8\xe7\xae\xfc\xf7\x40').split(''),
minute = six + zero,
hour = minute * minute,
day = hour * (two + four),
Y = one + nine + seven + zero,
M = one,
D = one,
H = zero,
I = zero,
S = zero;

while (T) {
    var monthLookup = [
        [],
        (three + one) * day,
        (+(two + eight) + !(Y%four)) * day,
        (three + one) * day,
        (three + zero) * day,
        (three + one) * day,
        (three + zero) * day,
        (three + one) * day,
        (three + one) * day,
        (three + zero) * day,
        (three + one) * day,
        (three + zero) * day,
        (three + one) * day
    ];

    if (T >= monthLookup[M]) {
        T -= monthLookup[M++];
        if (M == one + three) {
            M = one;
            Y++;
        }
    }
    else if (T >= day) {
        T -= day;
        D++;
    }
    else if (T >= hour) {
        T -= hour;
        H++;
    }
    else if (T >= minute) {
        T -= minute;
        I++;
    }
    else {
        S = T;
        break;
    }
}

// prepend leading zeroes
M = M < one + zero ? zero + M : M;
D = D < one + zero ? zero + D : D;
H = H < one + zero ? zero + H : H;
I = I < one + zero ? zero + I : I;
S = S < one + zero ? zero + S : S;

alert(Y+'-'+M+'-'+D+' '+H+':'+I+':'+S);

Basically I get all the numbers by encoding the string (I've used \x?? notation for ease of testing, but these are just unprintable ASCII chars and so don't include numbers) and then use concatenation to get the numbers I need, to use a pretty basic decrementor approach to populate the various fields.

\$\endgroup\$
1
  • \$\begingroup\$ It's ok - I don't consider them as numbers. It's tricky to use one function for all digits \$\endgroup\$
    – Voitcus
    Commented Sep 2, 2015 at 19:08

Not the answer you're looking for? Browse other questions tagged or ask your own question.