2017-03-15

Exercise: text as Unicode clock faces

In this blog post, we explore how arbitrary ASCII text can be encoded as Unicode clock faces:

    > clocksToPlain('πŸ•”πŸ•˜πŸ•–πŸ••πŸ•–πŸ•œπŸ•–πŸ•œπŸ•–πŸ•ŸπŸ•’πŸ•‘')
    'Hello!'

I’m explaining ideas by Maggie Pint and @FakeUnicode.

Unicode clock faces

The following times are available as clock faces in Unicode:

  • Full hours:
    • CLOCK FACE ONE OCLOCK (U+1F550): πŸ•
    • CLOCK FACE TWO OCLOCK (U+1F551): πŸ•‘
    • ···
    • CLOCK FACE TWELVE OCLOCK (U+1F55B): πŸ•›
  • Half-hours:
    • CLOCK FACE ONE-THIRTY (U+1F55C): πŸ•œ
    • CLOCK FACE TWO-THIRTY (U+1F55D): πŸ•
    • ···
    • CLOCK FACE TWELVE-THIRTY (U+1F567): πŸ•§

Interpreting clock faces as Unicode characters

The idea is as follows: the clock faces give you hex digits from 0 to F (you get the range 0–7 twice). Therefore, two clocks encode an 8-bit hex number, which can be interpreted as a Unicode character.

If you want to, you can stop reading here and implement clocksToPlain() yourself. The next subsection gives you a little help. The subsection after that gives you solutions.

Encoding and decoding via escape() and unescape()

For decoding clock-encoded text, we can get help from unescape():

    > escape('πŸ•”πŸ•˜')
    '%uD83D%uDD54%uD83D%uDD58'

You can see that each 21-bit code points is encoded as two 16-bit code units. For example, the code point U+1F554 is encoded as '%uD83D%uDD54':

    > '\u{D83D}\u{DD54}'
    'πŸ•”'
    > '\u{D83D}\u{DD54}' === '\u{1F554}'
    true

The pair of clocks gives you the two hex digits 4 and 8. Once you have them, you can use escape():

    > unescape('%48')
    'H'

clocksToPlain()

Thus, a compact way of decoding clock faces is:

    function clocksToPlain(clocks) {
        return unescape(
            escape(clocks).replace(/u.{9}(.).{11}/g, '$1'));
    }
    
    console.log(clocksToPlain('πŸ•”πŸ•˜πŸ•–πŸ••πŸ•–πŸ•œπŸ•–πŸ•œπŸ•–πŸ•ŸπŸ•’πŸ•‘'));
        // Hello!

A more self-descriptive version looks like this:

    function clocksToPlain(clocks) {
        const digits = [...clocks].map(ch => {
            const codePointHex = ch.codePointAt(0).toString(16);
            return codePointHex[codePointHex.length-1];
        });
        return mapTuple(digits, 2, (digitPair) => {
            const codePoint = Number.parseInt(digitPair.join(''), 16);
            return String.fromCodePoint(codePoint);
        }).join('');
    }
    
    function mapTuple(arr, tupleSize, func) {
        const result = [];
        let start = 0;
        while (start < arr.length) {
            const end = Math.min(arr.length, start + tupleSize);
            result.push(func(arr.slice(start, end), start, arr));
            start = end;
        }
        return result;
    }
    
    console.log(clocksToPlain('πŸ•”πŸ•˜πŸ•–πŸ••πŸ•–πŸ•œπŸ•–πŸ•œπŸ•–πŸ•ŸπŸ•’πŸ•‘'));
        // Hello!

Alternatively, you can use String.prototype.match() to group characters, but I liked the more universal mapTuple().

    > 'abcde'.match(/../g)
    [ 'ab', 'cd' ]

Encoding text as clock faces

If you want to produce clock text, you can use the following function.

    function plainToClocks(plain) {
        return [...plain].map(ch => {
            const MAX_DIGITS = 6;
            const hexCode = ch.codePointAt(0).toString(16)
                .padStart(MAX_DIGITS, '0');
            // We are assuming that ch.codePointAt(0) < 256
            return digitToClock(hexCode[4])+digitToClock(hexCode[5]);
        }).join('');
    }
    
    function digitToClock(hexDigit) {
        const codePoint = Number.parseInt('1F55'+hexDigit, 16);
        return String.fromCodePoint(codePoint);
    }
    
    console.log(plainToClocks('Hello!'));
        // πŸ•”πŸ•˜πŸ•–πŸ••πŸ•–πŸ•œπŸ•–πŸ•œπŸ•–πŸ•ŸπŸ•’πŸ•‘

I’m using the spread operator (...) to split the string plain into code units.

Further reading