Created
April 2, 2022 19:30
-
-
Save tylerneylon/0ef4293e1ba7bba4becdad24ff365951 to your computer and use it in GitHub Desktop.
A short Python module to convert to and from 7date strings. See http://tylerneylon.com/a/7date_spec/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
""" | |
7date.py | |
A small module to convert a datetime into a 7date string, or to convert a | |
7date string into a datetime. | |
The purpose of this file is to make it easier for you to integrate 7date | |
support into a program that already uses Gregorian dates. | |
Sample usage: | |
$ python3 7date.py | |
160.2022 | |
$ python3 7date.py 0.2040 | |
2040-01-01 00:00:00 | |
$ python 7date.py 10.2040 # One week later. | |
2040-01-08 00:00:00 | |
See the 7date specification here: | |
http://tylerneylon.com/a/7date_spec/ | |
""" | |
# ___________________________________________________________________________ | |
# Imports | |
import sys | |
from datetime import datetime | |
from datetime import timedelta | |
# ___________________________________________________________________________ | |
# Functions | |
# This expects n and b to be integers with n >= 0 and b > 1. | |
# This returns the number n written in base b, as a string. | |
def tobase(n, b): | |
assert n >= 0 and b > 1 | |
digits = [] | |
while n > 0: | |
digits.append(n % b) | |
n //= b | |
digits = digits if digits else [0] | |
return ''.join(map(str, reversed(digits))) | |
# This converts string s, which is expected to have digits 0 through (b-1), | |
# into a Python number, which is returned. | |
# This assumes 1 < b <= 10, and that s represents a nonnegative integer. | |
def frombase(s, b): | |
assert 1 < b <= 10 | |
n = 0 | |
for char in s: | |
n *= b | |
d = ord(char) - ord('0') | |
if not 0 <= d < b: | |
raise ValueError(f'Invalid digit in base-{b} number "{s}"') | |
n += d | |
return n | |
# This expects a valid 7date string (either standard or digital format); it | |
# returns a datetime object for the beginning of the given day. | |
# If there is a parsing error, this throws a ValueError. | |
def to_datetime(sevendate_str): | |
sevendate_str = sevendate_str.strip() # Ignore surrounding whitespace. | |
dot = sevendate_str.find('.') | |
if dot == -1: | |
# Assume it's in digital format, which is YYYY-DDDD. | |
year_str = sevendate_str[0:4] | |
day_str = sevendate_str[5:] | |
else: | |
# Assume it's in standard format, which is D+.YYYY. | |
day_str = sevendate_str[:dot] | |
year_str = sevendate_str[dot + 1:] | |
day_num = frombase(day_str, 7) | |
year_num = int(year_str) | |
time = datetime(year_num, 1, 1) + timedelta(days=day_num) | |
return time | |
# This expects either None or a datetime string. If you pass in None (or no | |
# arguments), it provides the 7date string. | |
def to_string(time=None, do_use_digital_format=False): | |
if time is None: | |
time = datetime.now() | |
year = str(time.year) | |
day = tobase(time.timetuple().tm_yday - 1, 7) | |
if do_use_digital_format: | |
# The format specifier "0>4s" means "right-align (>) the string (s) in | |
# `day`, and pad with '0' chars to achieve minimum length 4." | |
# https://peps.python.org/pep-3101/#standard-format-specifiers | |
return f'{year}-{day:0>4s}' | |
else: | |
return f'{day}.{year}' | |
# ___________________________________________________________________________ | |
# Main | |
if __name__ == '__main__': | |
# Check to see if perhaps a 7date string was provided to us. | |
if len(sys.argv) > 1 and sys.argv[1] != '-d': | |
sevendate_str = sys.argv[1] | |
print(str(to_datetime(sevendate_str))) | |
else: # Otherwise, provide the 7date string for today. | |
do_use_digital_format = ('-d' in sys.argv) | |
print(to_string(do_use_digital_format=do_use_digital_format)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment