Thornton 2 Library of Scraps
1K34E

Rickdate in a Shellscript

Rickdate was invented in December 1985 by Rick Wong as a highly compressed expression of an ordinary civil date for use in source code comments on small screens. Rick explains Rickdate here.

The essence of a Rickdate is the base 36 numbering system. Base 36 numbers are like ordinary everyday base 10 numbers, except there are 36 possible digits instead of 10; Rickdate uses the Arabic numerals 0-9 to represent the first 10 digits and the uppercase letters of the English alphabet A-Z to represent the remaining 26 digits. In base 10, the digit places are the ones place, the tens place, the hundreds place, and so on, each a power of ten larger.

In base 36, the digit places are powers of 36 instead, so the digit places are the ones place, the 36s place, the 1296s place, and so on. Adding "1" to "9" in base 36 gives the next digit, "A"; adding "1" to "A" gives "B"; and so on, and only when adding "1" to "Z" is another digit needed: "10" in base 36.

A Rickdate is three base 36 numbers combined into a single code. The final digit is the day of the month (1-V), the penultimate digit is the month number (1-C for January-December), and the remaining leading digits are the year number. For example, the Rickdate code "1K29L" encodes the year "1K2", the month "9", and the day "L". The base 36 number "1K2" is "2018" in base 10, the base 36 number "9" is by coincidence also "9" in base 10, and the base 36 number "L" is "21" in base 10. So the Rickdate code "1K29L" encodes "2018-09-21" or "September 21, 2018" (the first day of Autumn in 2018).

A Rickdate can also be expressed in an abbreviated form for ultimate compactness. For example, "1K29L" could be expressed as "29L". Much like expressing a decimal year as, for example, "'69" (which could represent 1969 or 2069) some context is lost as a result. Using only the last year digit in a Rickdate means only a 36-year window of time can be represented: "29L" could mean "September 21, 2018" ("1K29L"), "September 21, 1982" ("1J29L"), or "September 21, 2054" ("1L29L"). For many uses, this compromise may be acceptable.

Whether full or abbreviated, Rickdates are also extremely convenient for alphanumeric sorting in situations like filenames where the ISO-8601 "YYYY-MM-DD" format simply uses way too many characters.


Supercar Service number 11

(Image credit: Flickr user fotorus, cc-by-nd-20.)

With the aid of only the Unix utilities bc(1) and date(1), a shellscript can be written to convert a conventional date into a Rickdate.

This is mine, rickdate.sh:

#!/bin/sh

#
# rickdate.sh v.1K34E
#
# Convert a date string to the Rickdate expression of that date.
# An explanation of Rickdate: http://yak.net/kablooey/RickDate.html
#
# Usage:
#
#     rickdate.sh "<date-string>" ["<format-string>"]
#
# Both strings should be quoted.
#
# GNU date understands most date strings without the aid of a format
# string, but BSD date does not.
#
# On BSD, a format string must be provided.  The default for this
# program is "%A, %B %e, %Y" (accepting a string like "Tuesday, January
# 1, 2019").  If you need to convert a date string in a different
# format, provide its format string as the second parameter.  See the
# strftime(3) manpage for format details.
#
# If you need a 3- or 4-character Rickdate string, pipe the output
# through cut(1) like so:
#
#     rickdate.sh "<date-string>" | cut -c 3-5  # 3-character string
#     rickdate.sh "<date-string>" | cut -c 2-5  # 4-character string
#
# Between the years 1296 and 46,655, a full Rickdate string is always
# five characters long.
#

#
# Begin License (FreeBSD 2-Clause/Simplified BSD License).
# 
# Copyright 2019 Ariel Millennium Thornton.  All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
# 
# 1.  Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 
# 2.  Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
# 
# THIS SOFTWARE IS PROVIDED BY ARIEL MILLENNIUM THORNTON ''AS-IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ARIEL MILLENNIUM THORNTON
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# 
# The views and conclusions contained in the software and documentation
# are those of the authors and should not be interpreted as representing
# official policies, either expressed or implied, of Ariel Millennium
# Thornton.
# 
# End License (FreeBSD 2-Clause/Simplified BSD License).
#


to36() {
  # Provided a base-10 expression of a base 36 digit, return a single
  # digit with the uppercase letters A-Z filling in positions 10-35.
  case $1 in
    00) printf "0" ;; 12) printf "C" ;; 24) printf "O" ;;
    01) printf "1" ;; 13) printf "D" ;; 25) printf "P" ;;
    02) printf "2" ;; 14) printf "E" ;; 26) printf "Q" ;;
    03) printf "3" ;; 15) printf "F" ;; 27) printf "R" ;;
    04) printf "4" ;; 16) printf "G" ;; 28) printf "S" ;;
    05) printf "5" ;; 17) printf "H" ;; 29) printf "T" ;;
    06) printf "6" ;; 18) printf "I" ;; 30) printf "U" ;;
    07) printf "7" ;; 19) printf "J" ;; 31) printf "V" ;;
    08) printf "8" ;; 20) printf "K" ;; 32) printf "W" ;;
    09) printf "9" ;; 21) printf "L" ;; 33) printf "X" ;;
    10) printf "A" ;; 22) printf "M" ;; 34) printf "Y" ;;
    11) printf "B" ;; 23) printf "N" ;; 35) printf "Z" ;;
  esac
}

from10to36() {
  # Provided an arbitrary base 10 number, convert it to a base 36
  # number.
  printf "%s\n" "obase=36; $1" | \
  bc | \
  tr ' ' '\n' | \
  while read digit
    do
      to36 "${digit}"
    done
  #
}

# Grab date string (and format string if given) from command line
datestr="$1"
datefmt="$2"
if [ -z "${datestr}" ]
  then
    echo "Error: no date string was given." >&2
    echo "Usage: $0 \"<date string>\" [\"<date format string>\"]" >&2
    exit 1
  fi

# Set IFS: we need a space field separator
IFS=$' \t\n'

# Detect if date is GNU Date or BSD Date, and turn date string into
# YYYY-MM-DD format
ver=`date --version 2>/dev/null`
if [ -z "${ver}" ]
  then
    # BSD Date
    if [ -z "${datefmt}" ]
      then
        datefmt="%A, %B %e, %Y"
        fi
      #
    ymd=`date -j -f "${datefmt}" "${datestr}" "+%F" 2>/dev/null`
  else
    # GNU Date
    ymd=`date --date="${datestr}" --iso-8601 2>/dev/null`
  fi

if [ -z "${ymd}" ]
  then
    echo "Error: date string could not be turned into Y-M-D format." >&2
    echo "Usage: $0 \"<date string>\" [\"<date format string>\"]" >&2
    exit 1
  fi

# Turn YYYY-MM-DD string into Rickdate format
yr=${ymd%%-*}   # match {[YYYY]-MM-DD}
md=${ymd#*-}    # match {YYYY-[MM-DD]}
mo=${md%-*}     # match YYYY-{[MM]-DD}
da=${md#*-}     # match YYYY-{MM-[DD]}
# Translate each decimal number to a base 36 number
yrr=`from10to36 ${yr}`
mor=`from10to36 ${mo}`
dar=`from10to36 ${da}`
# A Rickdate string is the three base-36 numbers run together
rickdate="${yrr}${mor}${dar}"

# Print the Rickdate string
if [ -z "${rickdate}" ]
  then
    echo "Error: unable to turn date string into rickdate." >&2
    exit 1
  else
    echo "${rickdate}"
  fi

exit 0