Portrait Dr. Axel Rauschmayer
Dr. Axel Rauschmayer
Homepage | Twitter
Cover of book “Exploring ES6”
Book, exercises, quizzes
(free to read online)
Logo of newsletter “ES.next news”
Newsletter (free)
Cover of book “JavaScript for impatient programmers”
Book (free online)

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