Bootstrap
Committed d9a8ae
index 0000000..4711d41
--- /dev/null
+poetry.lock
+.coverage
+.test_coverage.xml
+.test_results.xml
index 0000000..b99e7b8
--- /dev/null
+"""
+Tools for NewBase60, NewBase64 and NewDateTime numbering systems.
+
+NewBase60
+---------
+
+NewBase60 is a base 60 (sexagesimal) numbering system that uses only
+ASCII numbers and letters that are print, prose and code safe. Lowercase
+l and uppercase I are aliased to the number 1. Capital O is aliased to
+the number 0.
+
+ 0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz
+
+NewBase60 encoded numbers sort properly in the typical case.
+
+NewBase64
+---------
+
+NewBase64 is a base 64 numbering system that extends NewBase60 for use
+in shortly encoding square grids. The four characters added are -, +, *
+and $.
+
+ 0 1 2 3 4 5 6 7
+ ----------------
+ 0| 0 1 2 3 4 5 6 7
+ 8| 8 9 A B C D E F
+ 16| G H J K L M N P
+ 24| Q R S T U V W X
+ 32| Y Z _ a b c d e
+ 40| f g h i j k m n
+ 48| o p q r s t u v
+ 56| w x y z - + * $
+
+latitude: -90 to 90 (pole to pole)
+longitude: -180 to 180 (meridian back to meridian)
+
+Earth is split into 32 quadrants:
+
+ 0 1 2 3 4 5 6 7
+ 8 9 A B C D E F
+ G H J K L M N P
+ Q R S T U V W X
+
+Each quadrant can be further scoped by 1/64ths. e.g. the 0th quadrant:
+
+ 0 1 2 3 4 5 6 7
+ 8 9 A B C D E F
+ G H J K L M N P
+ Q R S T U V W X
+ Y Z _ a b c d e
+ f g h i j k m n
+ o p q r s t u v
+ w x y z - + * $
+
+The initial quadrant is divided into 64 subquadrants with dimensions
+5.625 degrees square. Second and third subquadrants are 0.703125 and
+0.087890625 degrees square respectively.
+
+1 degree latitude = between 110.567 (at equator) and 111.699 km (at poles)
+1 degree longitude = between 111.321 (at equator) and 0 (at poles)
+
+ precision scope
+ 1 4.950 Mm
+
+ 2 618.75 km
+ 3 77.34375 km
+ 4 9.66796875 km
+ 5 1.20849609375 km
+
+ 6 151.06201171 m
+ 7 18.8827514648 m
+ 8 2.36034393311 m
+
+ 9 295.04299163 mm
+ 10 36.88037395 mm
+ 11 4.61004674 mm
+
+ 12 576.25584 μm
+ 13 72.0319803804 μm
+ 14 9.00399754755 μm
+ 15 1.12549969344 μm
+
+NewDateTime
+-----------
+
+Four sundays (0th day of the week) mark solar equinoxes and solstices.
+Monday through Saturday are days 1-6. Friday and Saturday are weekends
+resulting in a "4 day work week".
+
+5 days * 52 weeks = 260 work days in existing calendar
+5 days * 50 weeks = 250 work days accounting for two weeks vacation
+4 days * 60 weeks = 240 work days in NewDateTime
+
+Four 90 day, 15 week quarters. Each quarter is split into three months.
+
+52 days * 4 quarters = 184 work days accounting for mid-quarter vacation
+
+ Apr May Jun Jul Aug Sep Oct Nov Dec Jan Feb Mar
+
+ 0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz
+ Su @--------------@--------------@--------------@-------------- 0
+ Mo X X X X 1
+ Tu X X X X 2
+ We X X X X X X X X X X X X 3
+ Th X X X X X X X X X X X X 4
+ Fr XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 5
+ Sa XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 6
+
+ @ - sunday
+ X - non-work
+
+ 0 year 1
+
+ 000 new epoch
+
+ 000 Sunday
+
+ 000 northern tilt - new first sunday (old Mar 20, equinox)
+ 0F0 northern sun - new second sunday (old Jun 21, northern solstice)
+ 0W0 southern tilt - new third sunday (old Sep 23, equinox)
+ 0k0 southern sun - new fourth sunday (old Dec 21, southern solstice)
+
+ 001 Monday
+ 002 Tuesday
+ 003 Wednesday
+ 004 Thursday
+ 005 Friday
+ 006 Saturday
+
+ 0z6 last day of first year
+
+ 100 second new new year
+
+ 1 year 2
+ 1R 26th week of the 2nd year
+ 1R0 Monday of the 26th week of the 2nd year
+
+Based upon @[Tantek Çelik][1]'s [NewBase60][2], [NewBase64][3] and
+[NewCalendar][4] specifications.
+
+[1]: https://tantek.com
+[2]: https://tantek.pbworks.com/NewBase60
+[3]: https://tantek.pbworks.com/NewBase64
+[4]: https://tantek.pbworks.com/NewCalendar
+
+"""
+
+import pendulum
+from Crypto.Random import random
+
+nb60 = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz"
+nb60_re = "[0-9A-HJ-NP-Z_a-km-z]"
+_nb60map = dict(zip(nb60, range(len(nb60))))
+_nb60map["l"] = 1 # typo l to 1
+_nb60map["I"] = 1 # typo I to 1
+_nb60map["O"] = 0 # typo O to 0
+
+
+def nbencode(number, min_length=1):
+ """
+ return the NewBase60 string equivalent of decimal integer `i`
+
+ `min_length`: final size of string, zero padding on the left if needed
+
+ >>> nbencode(2011)
+ 'ZX'
+ >>> nbencode(2011, min_length=5)
+ '000ZX'
+ >>> nbencode(-2011)
+ '-ZX'
+
+ """
+ string = ""
+ try:
+ number = int(number)
+ except ValueError:
+ raise ValueError("NewBase60 encoding requires a decimal integer")
+ sign = ""
+ if number < 0:
+ sign = "-"
+ number = abs(number)
+ while number > 0:
+ number, i = divmod(number, 60)
+ string = nb60[i] + string
+ length = len(string)
+ min_length = int(min_length)
+ if length < min_length:
+ string = "0" * (min_length - length) + string
+ return sign + string
+
+
+def nbdecode(string):
+ """
+ return the decimal integer equivalent of NewBase60 string `s`
+
+ >>> nbdecode('ZX')
+ 2011
+ >>> nbdecode('000ZX')
+ 2011
+ >>> nbdecode('-ZX')
+ -2011
+
+ """
+ string = str(string)
+ sign = 1
+ if string.startswith("-"):
+ sign = -1
+ string = string[1:]
+ number = 0
+ for character in string:
+ number = number * 60 + _nb60map.get(character, 0)
+ return number * sign
+
+
+def nbrandom(length):
+ """
+ return a random NewBase60 string `length` digits long
+
+ >>> nbrandom(3) # doctest: +SKIP
+ 'Do_'
+ >>> nbrandom(6) # doctest: +SKIP
+ 'j9cQ4m'
+
+ """
+ start = nbdecode("1" + "0" * (length - 1))
+ end = nbdecode("z" * length)
+ return nbencode(random.randint(start, end))
+
+
+def nbrange(start, stop=None, step=1):
+ """
+ return a generator that yields NewBase60 strings like the builtin `range`
+
+ Arguments will be coerced to their NewBase60 string representations if
+ not provided as such.
+
+ >>> list(nbrange(5))
+ ['0', '1', '2', '3', '4']
+ >>> list(nbrange('ex', 'f3'))
+ ['ex', 'ey', 'ez', 'f0', 'f1', 'f2']
+
+ """
+ if stop is None:
+ stop = start
+ start = "0"
+ for i in range(nbdecode(start), nbdecode(stop), step):
+ yield nbencode(i)
+
+
+def ncencode(dt):
+ """ """
+ ddd = int(dt.format("DDD"))
+ bimnumber = int((ddd - 1) / 61) + 1
+ bim = hex(bimnumber + 9)[2].upper()
+ dayinbim = int((ddd - 1) % 61) + 1
+ seconds = round((dt.hour * 3600) + (dt.minute * 60) + dt.second) / 24
+ return nbencode(dt.year), bim, nbencode(dayinbim), nbencode(seconds)
+
+
+def ncdecode(year, bim, dayinbim, seconds):
+ """ """
+ bimnumber = int("0x" + bim, 16) - 9
+ ddd = 61 * (bimnumber - 1) + dayinbim
+ return pendulum.parse("{}-{:03}".format(year, ddd))
index 0000000..9fcaa2e
--- /dev/null
+[tool.poetry]
+name = "newmath"
+version = "0.2.2"
+description = "utilities for using NewBase60"
+authors = ["Angelo Gladding <angelo@ragt.ag>"]
+license = "BSD-2-Clause"
+
+[tool.poetry.dependencies]
+python = ">=3.10,<3.11"
+pycryptodome = "^3.16.0"
+
+[tool.poetry.group.dev.dependencies]
+gmpg = {path="../gmpg", develop=true}
+
+# [[tool.poetry.source]]
+# name = "main"
+# url = "https://ragt.ag/code/pypi"
+
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"