Exercise: text as Unicode clock faces

[2017-03-15] dev, javascript, unicode
(Ad, please don’t block)

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