Python: Click - Handling Date Parameter
Date: 2019-07-29 11:08:00
Learn how to process date parameters with the Python Click Library.

I've been building a little CLI application using the https://click.palletsprojects.com/en/7.x/[Python Click Library^], and I wanted to pass in a Date as a parameter.
There's more than one way to do this.

Let's first install the Click library:

[source,bash]
----
pip install click
----

And now we'll import our required libraries:

[source, python]
----
from datetime import date
import click
----

Now we'll create a sub command that takes two parameters: `date-start` and `date-end`.
These parameters have the type `DateTime`, and we can pass a string in the format `yyyy-mm-dd` from the command line:

[source,python]
----
@click.group()
def cli():
    pass


@click.command()
@click.option('--date-start', type=click.DateTime(formats=["%Y-%m-%d"]),
              default=str(date.today()))
@click.option('--date-end', type=click.DateTime(formats=["%Y-%m-%d"]),
              default=str(date.today()))
def dummy(date_start, date_end):
    click.echo(f"Start: {date_start}, End: {date_end} ")


cli.add_command(dummy)

if __name__ == '__main__':
    cli()
----

We can execute this command by running the following:

[source, bash]
----
$ python blog.py dummy --date-start 2018-01-01
Start: 2018-01-01 00:00:00, End: 2019-07-29 00:00:00
----

We only passed in `date-start`, which means `date-end` has defaulted to today's date.
The dates are correct, but we still have time information that we don't want.
We can get rid of that by calling the `date` function on those `DateTime` objects:

[source,python]
----
@click.command()
@click.option('--date-start', type=click.DateTime(formats=["%Y-%m-%d"]),
              default=str(date.today()))
@click.option('--date-end', type=click.DateTime(formats=["%Y-%m-%d"]),
              default=str(date.today()))
def dummy(date_start, date_end):
    date_start = date_start.date()
    date_end = date_end.date()
    click.echo(f"Start: {date_start}, End: {date_end} ")
----

Now let's run the script again:

[source, bash]
----
$ python blog.py dummy --date-start 2018-01-01
Start: 2018-01-01, End: 2019-07-29
----

Alternatively we can create our own Date type based on the DateTime type:

[source, python]
----
from datetime import datetime

class Date(click.ParamType):
    name = 'date'

    def __init__(self, formats=None):
        self.formats = formats or [
            '%Y-%m-%d',
            '%Y-%m-%dT%H:%M:%S',
            '%Y-%m-%d %H:%M:%S'
        ]

    def get_metavar(self, param):
        return '[{}]'.format('|'.join(self.formats))

    def _try_to_convert_date(self, value, format):
        try:
            return datetime.strptime(value, format).date()
        except ValueError:
            return None

    def convert(self, value, param, ctx):
        for format in self.formats:
            date = self._try_to_convert_date(value, format)
            if date:
                return date

        self.fail(
            'invalid date format: {}. (choose from {})'.format(
                value, ', '.join(self.formats)))

    def __repr__(self):
        return 'Date'
----

And then we'll add a new command that uses this parameter type:

[source, python]
----
@click.command()
@click.option('--date-start', type=Date(formats=["%Y-%m-%d"]), default=str(date.today()))
@click.option('--date-end', type=Date(formats=["%Y-%m-%d"]), default=str(date.today()))
def dummy2(date_start, date_end):
    click.echo(f"Start: {date_start}, End: {date_end} ")

cli.add_command(dummy2)
----


And now let's call this sub command:

[source, python]
----
$ python blog.py dummy2 --date-start 2018-01-02 --date-end 2019-04-05
Start: 2018-01-02, End: 2019-04-05
----