Plot contrivance

You wake up to find that something has gone horribly wrong! Your time machine has malfunctioned and you are lost sometime between June 2022 and October 1991.

You check the computer that you keep on the desk to see what time it is, but alas the positron interference from your quantum undetangler has fried the system clock, the internet and several other sensible ways to use a computer to check the time!

You wrack your brain, for some way to figure out the time. It's too dangerous to go outside... you haven't changed your decor in decades .. Aha! You've got it! You know for sure that over the last 31 years you've always kept a perfectly update version of your favorite programming language on this very computer! You're still a bit loopy from the decompactification so you can't remember some things, but you do retain your perfect knowledge of the history of the Unicode standard.


Your task is to write a computer program which when run will output the approximate date or date range based on the implementation of unicode available.


For example since (Ogham Letter Eadhadh) was added to Unicode in version 3.0 the following Haskell program determines if we are before the release of 3.0 in September 1999 or after.

import Data.Char
main =
  putStrLn $ 
      generalCategory 'ᚓ' == OtherLetter
      "September 1999-present"
      "October 1991-August 1999"

Try it online!


You may assume either

  1. Your programming language has an idealized version of its unicode handling library that has always implemented the current standard. (Even for dates before the invention of your language)
  2. Your programming language has the actual up to date version of its unicode library that would be available at the particular date, bugs and all.

To keep things neat and tidy do not mix and match. If you exploit a bug you're on track two, if you use anachronistic language features you're on track one.

You should not use any language features other than the library to determine the date. For example you can't just pull the language version string or check if a certain function exists. You must use calls to the unicode handling library.


This is a so answers will be scored by their vote counts.

I encourage voters to consider the following criteria when judging programs:

  1. Granularity. Vote up programs that can distinguish the date to high levels of detail.
  2. Conciseness. Not exactly code-golf, but I encourage you to vote up programs that determine the date in fewer checks.
  3. Cleverness. No matter what I say people are going to vote up cleverer answers, but it's worth mentioning. I encourage you to vote up answers that use obscure unicode trivia, exploit strange edge-cases etc.

3 Answers 3


Java 4+, 20 date-ranges

Language limitations: https://en.wikipedia.org/wiki/Java_(programming_language)#Versions
- String#replaceAll / java.util.regex.Pattern were added in Java 1.4 (February 6th, 2002)
- Alternatives Character.isDefined(int) / Character.getType(int) were added in Java 5 (September 30th, 2004)

- Version changelogs with links: https://en.wikipedia.org/wiki/Unicode#Versions
- More accurate release dates: https://unicode.org/history/publicationdates.html
class Main{
  public static void main(String[]a){
    String test = "A\u4E00\u1100\uBB5E\u20AC\u13A0\u10400\u1740\u10800\u1A00\u1B00\u102A0\u10B00\u1BC0\u11100\u20BA\u061C\u16AD0\u11700\u1E900\u11A00\u11800\u10FE0\u32FF\u10FB0\u1E290";
    int length = test.replaceAll("\\p{Cn}","").length();
      // 'A': Added as default
      case 1: // no-op
        System.out.print("v1.0.0:\tOctober 1991 (up to and including May 1992)");
      // '\u4E00': https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)
      case 2: // no-op
        System.out.print("v1.0.1:\tJune 1992 (up to and including May 1993)");
      // '\u1100': https://en.wikipedia.org/wiki/Hangul#Unicode
      case 3: // no-op
        System.out.print("v1.1:\tJune 1993 (up to and including June 1995)");
      // '\uBB5E': https://unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/KSC/HANGUL.TXT
      case 4: // no-op
        System.out.print("v2.0:\tJuly 1996 (up to and including April 1998)");
      // '\u20AC': https://www.unicode.org/versions/Unicode2.1.0/ & https://www.unicode.org/Public/2.1-Update4/ReadMe-2.1.9.txt (search for "version 2.1.2")
      case 5: // no-op
        System.out.print("v2.1.2:\tMay 1998 (up to and including August 1999)");
      // '\u13A0': https://en.wikipedia.org/wiki/Cherokee_(Unicode_block)
      case 6: // no-op
        System.out.print("v3.0:\tSeptember 1999 (up to and including February 2001)");
      // '\u10400': https://en.wikipedia.org/wiki/Deseret_(Unicode_block)
      case 8: // first case
        //System.out.print("v3.1:\tMarch 2001 (up to and including February 2002)");
        System.out.print("v3.1:\tMarch 2001 (February 6th, 2002 up to and including February 28th, 2002)");
      // '\u1740': https://en.wikipedia.org/wiki/Buhid_(Unicode_block)
      case 9:
        System.out.print("v3.2:\tMarch 2002 (up to and including March 2003)");
      // '\u10800': https://en.wikipedia.org/wiki/Cypriot_Syllabary_(Unicode_block)
      case 11:
        System.out.print("v4.0:\tApril 2003 (up to and including March 30th, 2005)");
      // '\u1A00': https://en.wikipedia.org/wiki/Buginese_(Unicode_block)
      case 12:
        System.out.print("v4.1:\tMarch 31st, 2005 (up to and including July 13th, 2006)");
      // '\u1B00': https://en.wikipedia.org/wiki/Balinese_(Unicode_block)
      case 13:
        System.out.print("v5.0:\tJuly 14th, 2006 (up to and including April 3rd, 2008)");
      // '\u102A0': https://en.wikipedia.org/wiki/Carian_(Unicode_block)
      case 15:
        System.out.print("v5.1:\tApril 4th, 2008 (up to and including September 30th, 2009)");
      // '\u10B00': https://en.wikipedia.org/wiki/Avestan_(Unicode_block)
      case 17:
        System.out.print("v5.2:\tOctober 1st, 2009 (up to and including October 10th, 2010)");
      // '\u1BC0': https://en.wikipedia.org/wiki/Batak_(Unicode_block)
      case 18:
        System.out.print("v6.0:\tOctober 11th, 2010 (up to and including January 30th, 2012)");
      // '\u11100': https://en.wikipedia.org/wiki/Chakma_(Unicode_block)
      case 20:
        System.out.print("v6.1:\tJanuary 31st, 2012 (up to and including September 25th, 2012)");
      // '\u20BA': https://en.wikipedia.org/wiki/Turkish_lira#Currency_sign
      case 21:
        System.out.print("v6.2:\tSeptember 26th, 2012 (up to and including September 29th, 2012)");
      // '\u061C': Diff https://www.unicode.org/Public/6.2.0/ucd/UnicodeData.txt and https://www.unicode.org/Public/6.3.0/ucd/UnicodeData.txt
      case 22:
        System.out.print("v6.3:\tSeptember 30th, 2012 (up to and including June 15th, 2014)");
      // '\u16AD0': https://en.wikipedia.org/wiki/Bassa_Vah_(Unicode_block)
      case 24:
        System.out.print("v7.0:\tJune 16th, 2014 (up to and including June 16th, 2015)");
      // '\u11700': https://en.wikipedia.org/wiki/Ahom_(Unicode_block)
      case 26:
        System.out.print("v8.0:\tJune 17th, 2015 (up to and including June 20th, 2016)");
      // '\u1E900': https://en.wikipedia.org/wiki/Adlam_(Unicode_block)
      case 28:
        System.out.print("v9.0:\tJune 21st, 2016 (up to and including June 19th, 2017)");
      // '\u11A00': https://en.wikipedia.org/wiki/Zanabazar_Square_(Unicode_block)
      case 30:
        System.out.print("v10.0:\tJune 20th, 2017 (up to and including June 4th, 2018)");
      // '\u11800': https://en.wikipedia.org/wiki/Dogra_(Unicode_block)
      case 32:
        System.out.print("v11.0:\tJune 5th, 2018 (up to and including March 4th, 2019)");
      // '\u10FE0': https://en.wikipedia.org/wiki/Elymaic_(Unicode_block)
      case 34:
        System.out.print("v12.0:\tMarch 5th, 2019 (up to and including May 6th, 2019)");
      // '\u32FF': https://en.wikipedia.org/wiki/Reiwa#Technology
      case 35:
        System.out.print("v12.1:\tMay 7th, 2019 (up to and including March 9th, 2020)");
      // '\u10FB0': https://en.wikipedia.org/wiki/Chorasmian_(Unicode_block)
      case 37:
        System.out.print("v13,9:\tMarch 10th, 2020 (up to and including September 13th, 2021)");
      // '\u1E290': https://en.wikipedia.org/wiki/Toto_(Unicode_block)
      case 39:
        System.out.print("v14.0:\tSeptember 14th, 2021 (up to and including June 15th, 2022)");
        System.err.printf("Unknown unicode version and date! (%d)%n", length);

(Don't) try it online.


Uses \p{Cn} regex-replacement to remove unassigned unicode characters from a string, before evaluating its length.

This uses rule option #2. Since String#replaceAll and java.util.regex.Pattern were added in Java 4, released on February 6th, 2002 (and alternatives Character#isDefined(int) and Character#getType(int) with Character.UNASSIGNED since Java 5 of September 30th, 2004), it only works for beyond that date. I've added the previous Unicode versions and date-ranges to the code for completeness, but since Java 4 wasn't released yet at the time, they can be seen as no-ops.

This is just an initial version which looks at added characters in the Unicode versions, mentioned on Wikipedia. I'll see if I can further define inner Unicode versions later on, since there are versions where the Unicode standard has been updated, but no characters were added/removed (e.g. added casing support for lower-/uppercase letters and things like that). But this already was quite time-consuming, so this will do for now.


Erlang, 4 date ranges

#!/usr/bin/env escript

main(_) ->
    Versions = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                "v9.0:\tJune 21st, 2016 (up to and including May 6th, 2019)",
                "v14.0:\tSeptember 14th, 2021 (up to and including June 15th, 2022)",
                "v13.0:\tMarch 10th, 2020 (up to and including September 13th, 2021)",
                "v12.1:\tMay 7th, 2019 (up to and including March 9th, 2020)"},
    Index =
        %% Unicode 14.0.0
        tryit(16#a7f2) +
        %% Unicode 13.0.0
        tryit(16#ab69) +
        %% Unicode 12.1.0
    io:format("~s~n", [element(Index, Versions)]).

tryit(C) ->
    NFKD = unicode:characters_to_nfkd_list([C]),
    Hex = [io_lib:format("~.16B", [X]) || X <- NFKD],
    io:format("~6.16B -> ~s~n", [C, lists:join(", ", Hex)]),


This uses changes in normalization rules between Unicode versions. It gets the NFKD canonical form of three code points, adds the number of hex digits in the result, and uses that as an index into a tuple with date ranges shamelessly stolen from Kevin Cruijssen's answer.

As of Unicode 9.0, the three characters all decompose to themselves, giving a starting length of 12. The characters are:

  • ㋿, 32FF, SQUARE ERA NAME REIWA, which decomposes into 令和 (4EE4 548C) as of Unicode 12.1. The length goes up by 4, to 16. (This character was introduced when Naruhito became emperor of Japan and adopted the era name Reiwa, succeeding his father Akihito and ending the Heisei era.)
  • ꭩ, AB69, MODIFIER LETTER SMALL TURNED W, which becomes ʍ (28D) as of Unicode 13.0. The length goes down by 1, to 15.
  • ꟲ, A7F2, MODIFIER LETTER CAPITAL C, which becomes C (43) as of Unicode 14.0. The length goes down by 2, to 13.

The unicode module was added to Erlang in release 20.0, supporting Unicode 9.0. It was upgraded to Unicode 10.0 in release 21.0, to 11.0 in release 22.0, to 12.1 in release 23.0, to 13.0 in release 24.0, and to 14.0 in release 25.0. I couldn't find any suitable characters to distinguish versions 9 to 11.

Using a bunch of Erlang installations I had lying around, the output is:
  A7F2 -> A7F2
  AB69 -> AB69
  32FF -> 32FF
v9.0:   June 21st, 2016 (up to and including May 6th, 2019)

  A7F2 -> A7F2
  AB69 -> AB69
  32FF -> 4EE4, 548C
v12.1:  May 7th, 2019 (up to and including March 9th, 2020)
  A7F2 -> A7F2
  AB69 -> 28D
  32FF -> 4EE4, 548C
v13.0:  March 10th, 2020 (up to and including September 13th, 2021)

  A7F2 -> 43
  AB69 -> 28D
  32FF -> 4EE4, 548C
v14.0:  September 14th, 2021 (up to and including June 15th, 2022)

Python, 22 date ranges (21 Unicode versions)

codepoints_dates = {
    b"\\u03F4": "2001 February 28",      #3.1 
    b"\\u0220": "2002 March 22",         #3.2 
    b"\\u0221": "2003 April 18",         #4.0 
    b"\\u0240": "2005 March 30",         #4.1 
    b"\\u0242": "2006 July 14",          #5.0 
    b"\\u0370": "2008 April 4",          #5.1 
    b"\\u0524": "2009 October 1",        #5.2 
    b"\\u0526": "2010 October 11",       #6.0 
    b"\\u058F": "2012 January 31",       #6.1 
    b"\\u20BA": "2012 September 26",     #6.2 
    b"\\u06A3": "2013 September 30",     #6.3 
    b"\\u037F": "2014 June 16",          #7.0 
    b"\\u08B3": "2015 June 17",          #8.0 
    b"\\u1C80": "2016 June 21",          #9.0 
    b"\\u0860": "2017 June 20",          #10.0
    b"\\u0560": "2018 June 5",           #11.0
    b"\\u0C77": "2019 March 5",          #12.0
    b"\\u32FF": "2019 May 7",            #12.1
    b"\\u08C0": "2020 March 10",         #13.0
    b"\\u061D": "2021 September 14",     #14.0
    b"\\u0CF3": "2022 September NN",     #15.0

prev_date = ""
for code, date in codepoints_dates.items():
    prev_date = date
    date = ""

print(prev_date, "-", date)

Each codepoint has been introduced in the corresponding Unicode version. The information is taken from the Delta Code Charts (here is the link to the Chart of version 14.0 as an example).

  • \$\begingroup\$ Why do you start with v3.1 instead of going all the way back to v1.0? \$\endgroup\$ Jun 16 at 15:07
  • \$\begingroup\$ @DominicvanEssen Probably because the unicode strings \u were added in Python 2.0, which was released on October 15th, 2000. \$\endgroup\$ Jun 16 at 15:54
  • 1
    \$\begingroup\$ @Dominic van Essen TBH it's just because I did not find the Delta Charts for versions lower than 3.1 and I'm lazy \$\endgroup\$
    – Matteo C.
    Jun 16 at 18:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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