├── .github └── workflows │ └── lint.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── click_datetime └── __init__.py ├── examples └── datetime_with_custom_format.py ├── poetry.lock ├── pyproject.toml ├── requirements.txt ├── setup.cfg └── setup.py /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Python Poetry and Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: [3.8, 3.9, 3.10, 3.11, 3.12] 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | 21 | - name: Install Poetry 22 | run: | 23 | curl -sSL https://install.python-poetry.org | python3 - 24 | 25 | - name: Install dependencies 26 | run: | 27 | poetry install 28 | 29 | - name: Lint with Black 30 | run: | 31 | poetry run black --check click_datetime/ 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment 2 | .env 3 | .envrc 4 | 5 | # Compiles and caches 6 | __pycache__ 7 | *.pyc 8 | 9 | # Build & Packaging 10 | dist/ 11 | *.egg-info 12 | 13 | # Virtual envs 14 | .venv/ 15 | 16 | # Misc 17 | .DS_Store 18 | .aider* 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Dawson Reid & contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.md 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Click Datetime 2 | 3 | Click support for Python's `datetime` types to allow developers to easy parse 4 | date strings as parameters to Python click CLIs. 5 | 6 | ## Example 7 | 8 | You can accept a datetime as a parameter to your click CLI 9 | 10 | ```python 11 | from datetime import datetime 12 | import click 13 | from click_datetime import Datetime 14 | 15 | 16 | @click.option( 17 | "--date", 18 | type=Datetime(format="%Y-%m-%d"), 19 | default=datetime.now(), 20 | help="An example parsing and printing a datetime.", 21 | ) 22 | @click.command() 23 | def cli(date: datetime): 24 | click.echo("The date : {0}".format(date)) 25 | 26 | 27 | if __name__ == "__main__": 28 | cli() # type: ignore 29 | ``` 30 | 31 | ```bash 32 | $ python main.py --date=2016-01-01 33 | ``` 34 | 35 | ## Installation 36 | 37 | ```bash 38 | pip install click-datetime 39 | ``` 40 | 41 | ## Development 42 | 43 | ### Building and packaging 44 | 45 | ```bash 46 | poetry build 47 | ``` 48 | 49 | ### Testing the compiled wheel 50 | 51 | ```bash 52 | # Create a virtual environment for testing 53 | python -m .venv/test 54 | source .venv/test/bin/activate 55 | 56 | # Confirm importing and exporting is correct 57 | python -c 'import click_datetime as cd; print(dir(cd))' 58 | ``` 59 | 60 | ## Authors 61 | 62 | - Dawson Reid (@ddaws) 63 | -------------------------------------------------------------------------------- /click_datetime/__init__.py: -------------------------------------------------------------------------------- 1 | import click 2 | from datetime import datetime 3 | 4 | 5 | class Datetime(click.ParamType): 6 | """ 7 | A datetime object parsed via datetime.strptime. 8 | 9 | Format specifiers can be found here : 10 | https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior 11 | """ 12 | 13 | name = "date" 14 | 15 | def __init__(self, format): 16 | self.format = format 17 | 18 | def convert(self, value, param, ctx): 19 | if value is None: 20 | return value 21 | 22 | if isinstance(value, datetime): 23 | return value 24 | 25 | try: 26 | datetime_value = datetime.strptime(value, self.format) 27 | return datetime_value 28 | except ValueError as ex: 29 | self.fail( 30 | 'Could not parse datetime string "{datetime_str}" formatted as {format} ({ex})'.format( 31 | datetime_str=value, 32 | format=self.format, 33 | ex=ex, 34 | ), 35 | param, 36 | ctx, 37 | ) 38 | -------------------------------------------------------------------------------- /examples/datetime_with_custom_format.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | import click 4 | 5 | from click_datetime import Datetime 6 | 7 | 8 | @click.option( 9 | "--date", 10 | type=Datetime(format="%Y-%m-%d"), 11 | default=datetime.now(), 12 | help="An example parsing and printing a datetime.", 13 | ) 14 | @click.command() 15 | def cli(date: datetime): 16 | click.echo("The date : {0}".format(date)) 17 | 18 | 19 | if __name__ == "__main__": 20 | cli() # type: ignore 21 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "black" 5 | version = "24.8.0" 6 | description = "The uncompromising code formatter." 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, 11 | {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, 12 | {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, 13 | {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, 14 | {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, 15 | {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, 16 | {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, 17 | {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, 18 | {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, 19 | {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, 20 | {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, 21 | {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, 22 | {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, 23 | {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, 24 | {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, 25 | {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, 26 | {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, 27 | {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, 28 | {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, 29 | {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, 30 | {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, 31 | {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, 32 | ] 33 | 34 | [package.dependencies] 35 | click = ">=8.0.0" 36 | mypy-extensions = ">=0.4.3" 37 | packaging = ">=22.0" 38 | pathspec = ">=0.9.0" 39 | platformdirs = ">=2" 40 | 41 | [package.extras] 42 | colorama = ["colorama (>=0.4.3)"] 43 | d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] 44 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 45 | uvloop = ["uvloop (>=0.15.2)"] 46 | 47 | [[package]] 48 | name = "click" 49 | version = "8.1.7" 50 | description = "Composable command line interface toolkit" 51 | optional = false 52 | python-versions = ">=3.7" 53 | files = [ 54 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 55 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 56 | ] 57 | 58 | [package.dependencies] 59 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 60 | 61 | [[package]] 62 | name = "colorama" 63 | version = "0.4.6" 64 | description = "Cross-platform colored terminal text." 65 | optional = false 66 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 67 | files = [ 68 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 69 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 70 | ] 71 | 72 | [[package]] 73 | name = "mypy-extensions" 74 | version = "1.0.0" 75 | description = "Type system extensions for programs checked with the mypy type checker." 76 | optional = false 77 | python-versions = ">=3.5" 78 | files = [ 79 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 80 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 81 | ] 82 | 83 | [[package]] 84 | name = "packaging" 85 | version = "24.1" 86 | description = "Core utilities for Python packages" 87 | optional = false 88 | python-versions = ">=3.8" 89 | files = [ 90 | {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, 91 | {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, 92 | ] 93 | 94 | [[package]] 95 | name = "pathspec" 96 | version = "0.12.1" 97 | description = "Utility library for gitignore style pattern matching of file paths." 98 | optional = false 99 | python-versions = ">=3.8" 100 | files = [ 101 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 102 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 103 | ] 104 | 105 | [[package]] 106 | name = "platformdirs" 107 | version = "4.3.2" 108 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 109 | optional = false 110 | python-versions = ">=3.8" 111 | files = [ 112 | {file = "platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617"}, 113 | {file = "platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c"}, 114 | ] 115 | 116 | [package.extras] 117 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 118 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 119 | type = ["mypy (>=1.11.2)"] 120 | 121 | [[package]] 122 | name = "wheel" 123 | version = "0.44.0" 124 | description = "A built-package format for Python" 125 | optional = false 126 | python-versions = ">=3.8" 127 | files = [ 128 | {file = "wheel-0.44.0-py3-none-any.whl", hash = "sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f"}, 129 | {file = "wheel-0.44.0.tar.gz", hash = "sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49"}, 130 | ] 131 | 132 | [package.extras] 133 | test = ["pytest (>=6.0.0)", "setuptools (>=65)"] 134 | 135 | [metadata] 136 | lock-version = "2.0" 137 | python-versions = "^3.12" 138 | content-hash = "a438220ad99af90620cae87534b8957490136857bfeb5323e45c2dc5cb9f8f7c" 139 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "click-datetime" 3 | version = "0.4.0" 4 | description = "Datetime type support for click." 5 | authors = ["Dawson R <105720-ddaws@users.noreply.gitlab.com>"] 6 | license = "MIT" 7 | readme = "README.md" 8 | 9 | [build-system] 10 | requires = ["poetry-core"] 11 | build-backend = "poetry.core.masonry.api" 12 | 13 | [tool.poetry.dependencies] 14 | python = "^3.12" 15 | click = ">=8.0.0,<9.0.0" 16 | wheel = "^0.44.0" 17 | 18 | [tool.poetry.group.dev.dependencies] 19 | black = "^24.8.0" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.6 2 | wheel==0.38.1 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [metadata] 5 | description-file = README.md 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup 4 | 5 | with open('README.md') as f: 6 | readme = f.read() 7 | 8 | setup( 9 | name='click-datetime', 10 | version='0.2', 11 | description='Datetime type support for click.', 12 | long_description=readme, 13 | author='Dawson Reid', 14 | author_email='dreid93@gmail.com', 15 | url='https://github.com/ddaws/click-datetime', 16 | license='MIT', 17 | packages=['click_datetime'], 18 | package_data={'click-datetime': ['README.md']}, 19 | include_package_data=True, 20 | install_requires=[ 21 | 'click', 22 | ], 23 | extras_require={ 24 | 'dev': [ 25 | 'wheel', 26 | ] 27 | } 28 | ) 29 | --------------------------------------------------------------------------------