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.
The following times are available as clock faces in Unicode:
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.
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' ]
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.