././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4669907 pyrfc3339-2.1.0/0000755000175100001660000000000015052365545012574 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/.gitignore0000644000175100001660000000512415052365540014561 0ustar00runnerdocker# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml .idea/**/dictionaries .idea/**/shelf # Sensitive or high-churn files .idea/**/dataSources/ .idea/**/dataSources.ids .idea/**/dataSources.local.xml .idea/**/sqlDataSources.xml .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml # Gradle .idea/**/gradle.xml .idea/**/libraries # CMake cmake-build-debug/ cmake-build-release/ # Mongo Explorer plugin .idea/**/mongoSettings.xml # File-based project format *.iws # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Cursive Clojure plugin .idea/replstate.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editor-based Rest Client .idea/httpRequests././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/CHANGES.rst0000644000175100001660000000277715052365540014406 0ustar00runnerdockerChangelog ========= 2.1.0 (2025-08-23) ------------------ (The Git branch used for development was renamed from ``master`` to ``main``) - Greatly simplify timestamp parsing and generation code, leveraging improvements in native Python capabilities in the past decade-and-a-half. See :commit:`53c2d15` for further details. - Simplify GitHub Actions workflow. Add `zizmor `_ to audit workflows. - Modernize packaging and documentation configuration. 2.0.1 (2024-11-03) ------------------ - Fix GitHub Actions badge in README.rst. 2.0 (2024-11-03) ---------------- (not released to PyPI) - Migrate tests from ``nose`` to ``unittest`` and ``pytest`` (:issue:`16`) - Replace :mod:`pytz` dependency with :attr:`datetime.timezone.utc` and :mod:`zoneinfo` (:issue:`15`) - Reformat codebase with ``black`` and ``isort`` - Configure GitHub Actions; remove Travis CI configuration file 1.1 (2018-06-10) ---------------- - Drop support for EOL Python releases, add (explicit) support for Python 3.5 and 3.6 (:issue:`7,10,11,12`) - Add :meth:`.utils.FixedOffset.__deepcopy__()` method, to prevent crash on deepcopy (:issue:`8`) 1.0 (2015-11-09) ---------------- - First formally-tagged release - Fix :func:`.utils.timedelta_seconds()` to use :meth:`datetime.timedelta.total_seconds()` when the native method is available (:issue:`6`) - Documentation and packaging cleanup (:issue:`4,5`) 0.2 (2014-02-09) ---------------- - Python 3 compatibility (:issue:`2`) 0.1 (2011-01-26) ---------------- - Initial release ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/LICENSE.txt0000644000175100001660000000207115052365540014412 0ustar00runnerdockerCopyright (c) 2025 Contributors to the pyRFC3339 project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/MANIFEST.in0000644000175100001660000000011115052365540014316 0ustar00runnerdockerprune pyrfc3339/tests prune .idea prune .github exclude .readthedocs.yml ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4659908 pyrfc3339-2.1.0/PKG-INFO0000644000175100001660000000530615052365545013675 0ustar00runnerdockerMetadata-Version: 2.4 Name: pyRFC3339 Version: 2.1.0 Summary: Generate and parse RFC 3339 timestamps Author-email: Kurt Raschke License-Expression: MIT Project-URL: Homepage, https://github.com/kurtraschke/pyrfc3339 Project-URL: Documentation, https://pyrfc3339.readthedocs.io/ Project-URL: Repository, https://github.com/kurtraschke/pyRFC3339.git Project-URL: Bug Tracker, https://github.com/kurtraschke/pyRFC3339/issues Project-URL: Changelog, https://github.com/kurtraschke/pyRFC3339/blob/main/CHANGES.rst Keywords: rfc-3339,timestamp,iso-8601,datetime Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Internet Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE.txt Dynamic: license-file Description =========== .. image:: https://github.com/kurtraschke/pyRFC3339/actions/workflows/python.yml/badge.svg :target: https://github.com/kurtraschke/pyRFC3339/actions/workflows/python.yml :alt: Build Status .. image:: https://readthedocs.org/projects/pyrfc3339/badge/?version=latest :target: https://pyrfc3339.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status pyRFC3339 parses and generates :RFC:`3339`-compliant timestamps using `Python `_ `datetime.datetime `_ objects. >>> from pyrfc3339 import generate, parse >>> from datetime import datetime, timezone >>> generate(datetime.now(timezone.utc)) #doctest:+ELLIPSIS '...T...Z' >>> parse('2009-01-01T10:01:02Z') datetime.datetime(2009, 1, 1, 10, 1, 2, tzinfo=datetime.timezone.utc) >>> parse('2009-01-01T14:01:02-04:00') datetime.datetime(2009, 1, 1, 14, 1, 2, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000))) Installation ============ To install the latest version from `PyPI `_: ``$ pip install pyRFC3339`` To install the latest development version: ``$ pip install https://github.com/kurtraschke/pyRFC3339/tarball/main#egg=pyRFC3339-dev`` Tests as well as enforcement of code style, formatting, and type safety are run with `tox `_: ``$ tox`` To build the documentation with Sphinx: ``$ tox -e docs`` The documentation is also available online at: https://pyrfc3339.readthedocs.io/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/README.rst0000644000175100001660000000300315052365540014252 0ustar00runnerdockerDescription =========== .. image:: https://github.com/kurtraschke/pyRFC3339/actions/workflows/python.yml/badge.svg :target: https://github.com/kurtraschke/pyRFC3339/actions/workflows/python.yml :alt: Build Status .. image:: https://readthedocs.org/projects/pyrfc3339/badge/?version=latest :target: https://pyrfc3339.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status pyRFC3339 parses and generates :RFC:`3339`-compliant timestamps using `Python `_ `datetime.datetime `_ objects. >>> from pyrfc3339 import generate, parse >>> from datetime import datetime, timezone >>> generate(datetime.now(timezone.utc)) #doctest:+ELLIPSIS '...T...Z' >>> parse('2009-01-01T10:01:02Z') datetime.datetime(2009, 1, 1, 10, 1, 2, tzinfo=datetime.timezone.utc) >>> parse('2009-01-01T14:01:02-04:00') datetime.datetime(2009, 1, 1, 14, 1, 2, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000))) Installation ============ To install the latest version from `PyPI `_: ``$ pip install pyRFC3339`` To install the latest development version: ``$ pip install https://github.com/kurtraschke/pyRFC3339/tarball/main#egg=pyRFC3339-dev`` Tests as well as enforcement of code style, formatting, and type safety are run with `tox `_: ``$ tox`` To build the documentation with Sphinx: ``$ tox -e docs`` The documentation is also available online at: https://pyrfc3339.readthedocs.io/ ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4619906 pyrfc3339-2.1.0/docs/0000755000175100001660000000000015052365545013524 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4619906 pyrfc3339-2.1.0/docs/build/0000755000175100001660000000000015052365545014623 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/build/.gitignore0000644000175100001660000000010615052365540016603 0ustar00runnerdocker# Ignore everything in this directory * # Except this file !.gitignore././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/requirements.txt0000644000175100001660000000007215052365540017002 0ustar00runnerdockerSphinx==8.2.3 sphinx-issues==5.0.1 sphinx-rtd-theme==3.0.2././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4639907 pyrfc3339-2.1.0/docs/source/0000755000175100001660000000000015052365545015024 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4639907 pyrfc3339-2.1.0/docs/source/_static/0000755000175100001660000000000015052365545016452 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/_static/.gitignore0000644000175100001660000000000015052365540020423 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4639907 pyrfc3339-2.1.0/docs/source/_templates/0000755000175100001660000000000015052365545017161 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/_templates/.gitignore0000644000175100001660000000000015052365540021132 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/changes.rst0000644000175100001660000000003615052365540017160 0ustar00runnerdocker.. include:: ../../CHANGES.rst././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/conf.py0000644000175100001660000001511515052365540016321 0ustar00runnerdocker# -*- coding: utf-8 -*- # # pyRFC3339 documentation build configuration file, created by # sphinx-quickstart on Mon Jul 27 19:32:42 2009. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os from importlib.metadata import version as get_version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx_issues'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = {'.rst': 'restructuredtext'} # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' # General information about the project. project = u'pyRFC3339' copyright = u'2025 Contributors to the pyRFC3339 project' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # release: str = get_version("pyrfc3339") version: str = ".".join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'pyRFC3339doc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pyRFC3339.tex', u'pyRFC3339 Documentation', u'Kurt Raschke', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} issues_github_path = 'kurtraschke/pyrfc3339' rst_prolog = """ .. role:: python(code) :language: python :class: highlight """././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/doc.rst0000644000175100001660000000014615052365540016317 0ustar00runnerdockerModules ======= .. toctree:: :maxdepth: 2 :glob: pyrfc3339 generator parser utils ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/generator.rst0000644000175100001660000000027315052365540017541 0ustar00runnerdocker:mod:`pyrfc3339.generator` -- Generate :RFC:`3339` timestamps ============================================================= .. automodule:: pyrfc3339.generator :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/index.rst0000644000175100001660000000046415052365540016664 0ustar00runnerdocker.. pyRFC3339 documentation master file, created by sphinx-quickstart on Mon Jul 27 19:32:42 2009. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. pyRFC3339 ========= .. toctree:: :maxdepth: 3 intro changes doc license././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/intro.rst0000644000175100001660000000003615052365540016703 0ustar00runnerdocker.. include:: ../../README.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/license.rst0000644000175100001660000000005715052365540017175 0ustar00runnerdockerLicense ======= .. include:: ../../LICENSE.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/parser.rst0000644000175100001660000000026215052365540017045 0ustar00runnerdocker:mod:`pyrfc3339.parser` -- Parse :RFC:`3339` timestamps ============================================================= .. automodule:: pyrfc3339.parser :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/pyrfc3339.rst0000644000175100001660000000012615052365540017215 0ustar00runnerdocker:mod:`pyrfc3339` -- Imports ============================= .. automodule:: pyrfc3339 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/docs/source/utils.rst0000644000175100001660000000027415052365540016714 0ustar00runnerdocker:mod:`pyrfc3339.utils` -- Utilities for working with timestamps =============================================================== .. automodule:: pyrfc3339.utils :members: ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4659908 pyrfc3339-2.1.0/pyRFC3339.egg-info/0000755000175100001660000000000015052365545015573 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966309.0 pyrfc3339-2.1.0/pyRFC3339.egg-info/PKG-INFO0000644000175100001660000000530615052365545016674 0ustar00runnerdockerMetadata-Version: 2.4 Name: pyRFC3339 Version: 2.1.0 Summary: Generate and parse RFC 3339 timestamps Author-email: Kurt Raschke License-Expression: MIT Project-URL: Homepage, https://github.com/kurtraschke/pyrfc3339 Project-URL: Documentation, https://pyrfc3339.readthedocs.io/ Project-URL: Repository, https://github.com/kurtraschke/pyRFC3339.git Project-URL: Bug Tracker, https://github.com/kurtraschke/pyRFC3339/issues Project-URL: Changelog, https://github.com/kurtraschke/pyRFC3339/blob/main/CHANGES.rst Keywords: rfc-3339,timestamp,iso-8601,datetime Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Internet Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE.txt Dynamic: license-file Description =========== .. image:: https://github.com/kurtraschke/pyRFC3339/actions/workflows/python.yml/badge.svg :target: https://github.com/kurtraschke/pyRFC3339/actions/workflows/python.yml :alt: Build Status .. image:: https://readthedocs.org/projects/pyrfc3339/badge/?version=latest :target: https://pyrfc3339.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status pyRFC3339 parses and generates :RFC:`3339`-compliant timestamps using `Python `_ `datetime.datetime `_ objects. >>> from pyrfc3339 import generate, parse >>> from datetime import datetime, timezone >>> generate(datetime.now(timezone.utc)) #doctest:+ELLIPSIS '...T...Z' >>> parse('2009-01-01T10:01:02Z') datetime.datetime(2009, 1, 1, 10, 1, 2, tzinfo=datetime.timezone.utc) >>> parse('2009-01-01T14:01:02-04:00') datetime.datetime(2009, 1, 1, 14, 1, 2, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000))) Installation ============ To install the latest version from `PyPI `_: ``$ pip install pyRFC3339`` To install the latest development version: ``$ pip install https://github.com/kurtraschke/pyRFC3339/tarball/main#egg=pyRFC3339-dev`` Tests as well as enforcement of code style, formatting, and type safety are run with `tox `_: ``$ tox`` To build the documentation with Sphinx: ``$ tox -e docs`` The documentation is also available online at: https://pyrfc3339.readthedocs.io/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966309.0 pyrfc3339-2.1.0/pyRFC3339.egg-info/SOURCES.txt0000644000175100001660000000120515052365545017455 0ustar00runnerdocker.gitignore CHANGES.rst LICENSE.txt MANIFEST.in README.rst pyproject.toml docs/requirements.txt docs/build/.gitignore docs/source/changes.rst docs/source/conf.py docs/source/doc.rst docs/source/generator.rst docs/source/index.rst docs/source/intro.rst docs/source/license.rst docs/source/parser.rst docs/source/pyrfc3339.rst docs/source/utils.rst docs/source/_static/.gitignore docs/source/_templates/.gitignore pyRFC3339.egg-info/PKG-INFO pyRFC3339.egg-info/SOURCES.txt pyRFC3339.egg-info/dependency_links.txt pyRFC3339.egg-info/top_level.txt pyrfc3339/__init__.py pyrfc3339/generator.py pyrfc3339/parser.py pyrfc3339/py.typed pyrfc3339/utils.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966309.0 pyrfc3339-2.1.0/pyRFC3339.egg-info/dependency_links.txt0000644000175100001660000000000115052365545021641 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966309.0 pyrfc3339-2.1.0/pyRFC3339.egg-info/top_level.txt0000644000175100001660000000001215052365545020316 0ustar00runnerdockerpyrfc3339 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/pyproject.toml0000644000175100001660000000520515052365540015505 0ustar00runnerdocker[build-system] requires = ["setuptools>=64", "setuptools_scm>=8"] build-backend = "setuptools.build_meta" [project] name = "pyRFC3339" dynamic = ["version"] requires-python = ">= 3.9" authors = [ { name = "Kurt Raschke", email = "kurt@kurtraschke.com" } ] description = "Generate and parse RFC 3339 timestamps" readme = { file = "README.rst", content-type = "text/x-rst" } keywords = ["rfc-3339", "timestamp", "iso-8601", "datetime"] license = "MIT" license-files = ["LICENSE.txt"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet" ] [project.urls] Homepage = "https://github.com/kurtraschke/pyrfc3339" Documentation = "https://pyrfc3339.readthedocs.io/" Repository = "https://github.com/kurtraschke/pyRFC3339.git" "Bug Tracker" = "https://github.com/kurtraschke/pyRFC3339/issues" Changelog = "https://github.com/kurtraschke/pyRFC3339/blob/main/CHANGES.rst" [tool.setuptools] packages = ["pyrfc3339"] [tool.setuptools_scm] [tool.mypy] strict = true [tool.flake8] max-line-length = 132 [tool.isort] profile = "black" [tool.coverage.run] omit = ["pyrfc3339/tests/*"] [tool.tox] requires = ["tox>=4"] env_list = ["sort", "format", "style", "type", "3.9", "3.10", "3.11", "3.12", "3.13"] skip_missing_interpreters = true [tool.tox.gh.python] "3.13" = ["sort", "format", "style", "type", "3.13"] "3.12" = ["3.12"] "3.11" = ["3.11"] "3.10" = ["3.10"] "3.9" = ["3.9"] [tool.tox.env_run_base] deps = ["pytest", "pytest-subtests", "pytest-cov", "tzdata;platform_system==\"Windows\""] commands = [["pytest", "--doctest-glob=docs/source/*.rst", "--doctest-glob=README.rst", "--doctest-modules", "--doctest-continue-on-failure", "--cov=pyrfc3339", "."]] [tool.tox.env.type] skip_install = true deps = ["mypy"] commands = [["mypy", "pyrfc3339"]] [tool.tox.env.sort] skip_install = true deps = ["isort"] commands = [["isort", "--check", "--diff", "pyrfc3339"]] [tool.tox.env.format] skip_install = true deps = ["black"] commands = [["black", "--check", "--diff", "pyrfc3339"]] [tool.tox.env.style] skip_install = true deps = ["flake8", "flake8-pyproject"] commands = [["flake8", "pyrfc3339"]] [tool.tox.env.docs] deps = ["-r docs/requirements.txt"] commands = [["sphinx-build", "-M", "html", "docs/source", "docs/build"]] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4659908 pyrfc3339-2.1.0/pyrfc3339/0000755000175100001660000000000015052365545014241 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/pyrfc3339/__init__.py0000644000175100001660000000140215052365540016342 0ustar00runnerdocker""" pyRFC3339 parses and generates :RFC:`3339`-compliant timestamps using Python :class:`datetime.datetime` objects. >>> from pyrfc3339 import generate, parse >>> from datetime import datetime, timezone >>> generate(datetime.now(timezone.utc)) #doctest:+ELLIPSIS '...T...Z' >>> parse('2009-01-01T10:01:02Z') datetime.datetime(2009, 1, 1, 10, 1, 2, tzinfo=datetime.timezone.utc) >>> parse('2009-01-01T14:01:02-04:00') datetime.datetime(2009, 1, 1, 14, 1, 2, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000))) """ from importlib.metadata import PackageNotFoundError, version from .generator import generate from .parser import parse try: __version__ = version("pyrfc3339") except PackageNotFoundError: pass __all__ = ["generate", "parse"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/pyrfc3339/generator.py0000644000175100001660000000641615052365540016603 0ustar00runnerdockerimport re from datetime import datetime, timezone def generate( dt: datetime, utc: bool = True, accept_naive: bool = False, microseconds: bool = False, ) -> str: """ Generate an :RFC:`3339`-formatted timestamp from a :class:`datetime.datetime`. >>> from datetime import datetime, timezone >>> from zoneinfo import ZoneInfo >>> generate(datetime(2009, 1, 1, 12, 59, 59, 0, timezone.utc)) '2009-01-01T12:59:59Z' The timestamp be normalized to UTC unless :python:`utc=False` is specified, in which case it will use the timezone from the :class:`~datetime.datetime`'s :attr:`~datetime.datetime.tzinfo` attribute. >>> eastern = ZoneInfo('US/Eastern') >>> dt = datetime(2009, 1, 1, 12, 59, 59, tzinfo=eastern) >>> generate(dt) '2009-01-01T17:59:59Z' >>> generate(dt, utc=False) '2009-01-01T12:59:59-05:00' Unless :python:`accept_naive=True` is specified, the :class:`~datetime.datetime` must not be naive. >>> generate(datetime(2009, 1, 1, 12, 59, 59, 0)) Traceback (most recent call last): ... ValueError: naive datetime and accept_naive is False >>> generate(datetime(2009, 1, 1, 12, 59, 59, 0), accept_naive=True) '2009-01-01T12:59:59Z' If, however, :python:`accept_naive=True` is specified, the :class:`~datetime.datetime` is assumed to represent a UTC time. Attempting to generate a local timestamp from a naive datetime will result in an error. >>> generate(datetime(2009, 1, 1, 12, 59, 59, 0), accept_naive=True, utc=False) Traceback (most recent call last): ... ValueError: cannot generate a local timestamp from a naive datetime :param datetime.datetime dt: the :class:`~datetime.datetime` for which to generate an :RFC:`3339` timestamp. :param bool utc: :const:`True` to normalize the supplied :class:`datetime.datetime` to UTC; :const:`False` otherwise. Defaults to :const:`True`. :param bool accept_naive: :const:`True` if :func:`generate()` should accept a 'naive' datetime (that is, one without timezone information) and treat it as a UTC timestamp; :const:`False` otherwise. Defaults to :const:`False`. :param bool microseconds: :const:`True` to generate a timestamp which includes fractional seconds, if present; :const:`False` otherwise. Defaults to :const:`False`. Note that fractional seconds are *truncated*, not rounded when :obj:`microseconds` is :const:`False`. :return: the supplied :class:`~datetime.datetime` instance represented as an :RFC:`3339` timestamp :rtype: str """ if dt.tzinfo is None: if accept_naive: if utc: dt = dt.replace(tzinfo=timezone.utc) else: raise ValueError( "cannot generate a local timestamp from a naive datetime" ) else: raise ValueError("naive datetime and accept_naive is False") if utc: dt = dt.astimezone(timezone.utc) timestamp = dt.isoformat(timespec="microseconds" if microseconds else "seconds") if dt.tzinfo == timezone.utc: timestamp = re.sub(r"\+00:00$", "Z", timestamp) return timestamp ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/pyrfc3339/parser.py0000644000175100001660000000650715052365540016112 0ustar00runnerdockerimport re import sys from datetime import datetime, timezone from .utils import datetime_utcoffset def parse(timestamp: str, utc: bool = False, produce_naive: bool = False) -> datetime: """ Parse an :RFC:`3339`-formatted timestamp and return a :class:`datetime.datetime`. If the timestamp is presented in UTC, then the :attr:`~datetime.datetime.tzinfo` attribute of the returned `datetime` will be set to :attr:`datetime.timezone.utc`. >>> parse('2009-01-01T10:01:02Z') datetime.datetime(2009, 1, 1, 10, 1, 2, tzinfo=datetime.timezone.utc) Otherwise, a :class:`datetime.timezone` instance is created with the appropriate offset, and the :attr:`~datetime.datetime.tzinfo` attribute of the returned :class:`~datetime.datetime` is set to that value. >>> parse('2009-01-01T14:01:02-04:00') datetime.datetime(2009, 1, 1, 14, 1, 2, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000))) However, if :meth:`parse()` is called with :python:`utc=True`, then the returned :class:`~datetime.datetime` will be normalized to UTC (and its :attr:`~datetime.datetime.tzinfo` attribute set to :attr:`~datetime.timezone.utc`), regardless of the input timezone. >>> parse('2009-01-01T06:01:02-04:00', utc=True) datetime.datetime(2009, 1, 1, 10, 1, 2, tzinfo=datetime.timezone.utc) As parsing is delegated to :meth:`datetime.datetime.fromisoformat()`, certain timestamps which do not strictly adhere to :RFC:`3339` are nonetheless accepted. >>> parse('2009-01-01T06:01:02') datetime.datetime(2009, 1, 1, 6, 1, 2) Exceptions will, however, be thrown for blatantly invalid input: >>> parse('2009-01-01T25:01:02Z') Traceback (most recent call last): ... ValueError: hour must be in 0..23 :param str timestamp: the :RFC:`3339` timestamp to be parsed :param bool utc: :const:`True` to normalize the timestamp to UTC; :const:`False` otherwise. Defaults to :const:`False`. :param bool produce_naive: :const:`True` if the produced :class:`~datetime.datetime` instance should not have a timezone attached (that is, be 'naive'); :const:`False` otherwise. Defaults to :const:`False`. :return: the parsed timestamp :rtype: datetime.datetime """ # Python does not recognize "Z" as an alias for "+00:00", so we perform the # substitution here. timestamp = re.sub("Z$", "+00:00", timestamp, flags=re.IGNORECASE) # Python releases prior to 3.11 only support three or six digits of fractional # seconds. RFC 3339 is more lenient, so pad to six digits and truncate any # excessive digits. # This can be removed in October 2026, once Python 3.10 and earlier # have been retired. # noinspection PyUnreachableCode if sys.version_info < (3, 11): timestamp = re.sub( r"(\.)([0-9]+)(?=[+\-][0-9]{2}:[0-9]{2}$)", lambda match: match.group(1) + match.group(2).ljust(6, "0")[:6], timestamp, ) dt_out = datetime.fromisoformat(timestamp) if utc: dt_out = dt_out.astimezone(timezone.utc) if produce_naive: if datetime_utcoffset(dt_out) == 0: dt_out = dt_out.replace(tzinfo=None) else: raise ValueError("cannot produce a naive datetime from a local timestamp") return dt_out ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/pyrfc3339/py.typed0000644000175100001660000000000015052365540015721 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755966304.0 pyrfc3339-2.1.0/pyrfc3339/utils.py0000644000175100001660000000160715052365540015752 0ustar00runnerdockerfrom datetime import datetime def datetime_utcoffset(dt: datetime) -> float: """ Return the UTC offset for an aware :class:`datetime.datetime` in seconds. >>> from datetime import datetime >>> from zoneinfo import ZoneInfo >>> z = ZoneInfo('US/Eastern') >>> dt = datetime(2024, 11, 5, 19, 7, 6, tzinfo=z) >>> datetime_utcoffset(dt) -18000.0 >>> dt = datetime(2024, 11, 5, 19, 7, 6) >>> datetime_utcoffset(dt) Traceback (most recent call last): ... AssertionError :param datetime.datetime dt: a :class:`~datetime.datetime` instance; must be aware (that is, have a timezone attached) :return: the UTC offset of the supplied :class:`~datetime.datetime` in seconds :rtype: float """ assert dt.tzinfo is not None tz = dt.tzinfo offset = tz.utcoffset(dt) assert offset is not None return offset.total_seconds() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1755966309.4669907 pyrfc3339-2.1.0/setup.cfg0000644000175100001660000000004615052365545014415 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0