Python: learning to work with dates using the zodiac signs

Python: learning to work with dates using the zodiac signs

In this article we will see a practical example of operations on dates in Python.

In this article we will see a practical example of operations on dates in Python.

We will create a simple command-line application that will take the user's date of birth as input and return the name of his zodiac sign.

There are twelve zodiac signs and each of them covers a specific period of time during the year. To represent them we will use a JSON file containing an array of objects structured as follows:

[
    {
        "name": "Sign Name",
        "date": {
            "from": [day, month],
            "to": [day, month]
        }
    }
]

day and month are integers. A feature of the constructor of the Python date class is precisely that of accepting only integers for data such as the year, month and day. from represents the start date of the period of a sign and to its end. The year is irrelevant.

We must only accept dates in ISO format, i.e. YYYY-mm-dd and verify that the year, month and day are valid. To do this, we first define a validation function.

import json
import re
import sys
from datetime import date, datetime


def is_valid_date_str(date_str):
    reg = re.compile(r'^\d{4}-\d{2}-\d{2}$')
    match = reg.search(date_str)
    if not match:
        return False
    try:
        dt = date.fromisoformat(date_str)
        year = dt.year
        now = datetime.now()
        if year >= now.year:
            return False
        month = dt.month
        if month > 12 or month < 1:
            return False
        day = dt.day
        if day > 31 or day < 1:
            return False
    except ValueError:
        return False
    return True

The first step is to check the format of the date using a simple regular expression. We use search() to perform a match on the entire input string. At this point, if the format is valid, we corroborate our routine by obtaining an object of type date starting from the ISO format provided using the method fromisoformat(). We also proceed to verify that the year, month and day fall within a valid range.

Now we can define a function to get the list of dictionaries from the JSON file.

def get_signs_list(filename=None):
    signs = []
    if filename is None:
        return signs
    with open(filename, 'r') as f:
        signs = json.load(f)
    return signs

We know that the relevant data are the day and month of the user's date of birth and that these data must be compared with the values contained in the from and to lists of each dictionary . We therefore have to compare three date objects starting from the transformation of the string in the format YYYY-mm-dd of the date of birth into a Python date object. This operation can be performed using the datetime.strptime() method which accepts the date string as the first argument and the expected format of the input date as the second argument.

def find_sign_by_birth_date(birthdate=None):
    if birthdate is None:
        return None
    if not is_valid_date_str(birthdate):
        return None
    signs_list = get_signs_list('./zodiac-signs.json')
    format_date = '%Y-%m-%d'
    dt = datetime.strptime(birthdate, format_date)
    now = datetime.now()
    day = dt.day
    month = dt.month
    reference_date = date(now.year, month, day)
    sign_name = ''
    for sign in signs_list:
        from_day, from_month = sign['date']['from']
        to_day, to_month = sign['date']['to']
        from_date = date(now.year, from_month, from_day)
        to_date = date(now.year, to_month, to_day)

        if reference_date >= from_date and reference_date <= to_date:
            sign_name = sign['name']
            break

    return sign_name

If the date of birth falls within the established range, i.e. if it is greater than or equal to the starting date of the period and if at the same time it is less than or equal to the end date of the period, we return the name of the user's zodiac sign. We remind again that the year is irrelevant but it is necessary to specify it to create a Python date object.

We can use our code as follows:

def main():
    birth_date = input('Insert your birth date (YYYY-mm-dd)')
    sign_found = find_sign_by_birth_date(birth_date)
    if sign_found is None:
        print('Invalid birth date')
        sys.exit(1)
    print(f'Your sign is: {sign_found}')
    sys.exit(0)


if __name__ == '__main__':
    main()