From 8196808c7cf441ed4f8323704a39f515a6874cf5 Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Fri, 3 May 2024 11:51:44 -0500 Subject: [PATCH 01/10] add check for missing timezone. Also add best practice docs and tests for check --- docs/best_practices/nwbfile_metadata.rst | 13 +++++++++---- src/nwbinspector/checks/nwbfile_metadata.py | 18 +++++++++++++++++- tests/unit_tests/test_nwbfile_metadata.py | 21 ++++++++++++++++++++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/docs/best_practices/nwbfile_metadata.rst b/docs/best_practices/nwbfile_metadata.rst index 5e897ce7e..1f47af16b 100644 --- a/docs/best_practices/nwbfile_metadata.rst +++ b/docs/best_practices/nwbfile_metadata.rst @@ -19,21 +19,26 @@ objects in the :ref:`nwb-schema:sec-NWBFile` is the ``timestamps_reference_time` ``session_start_time``, but when writing multiple NWBFiles that are all designed to align to the same time reference, the ``timestamp_reference_time`` used across all of the NWBFiles may be set separately from the ``session_start_time``. -All time-related data in the NWBFile should be synchronized to the ``timestamps_reference_time`` so that future users -are able to understand the timing of all events contained within the NWBFile. +All time-related data in the ``NWBFile`` should be synchronized to the ``timestamps_reference_time`` so that future users +are able to understand the timing of all events contained within the ``NWBFile``. The ``timestamps_reference_time`` should also be the earliest timestamp in the file, giving all other time references a positive value relative to that. There should be no time references which are negative. Given the importance of this field within an :ref:`nwb-schema:sec-NWBFile`, is it critical that it be set to a proper value. Default values should generally not be used for this field. If the true date is unknown, use your -best guess. If the exact start time is unknown, then it is fine to simply set it to midnight on that date. +best guess. If working with human participants and such data is protected information, use the date 1900-01-01. + +**New in PyNWB 2.7.0, released May 2, 2024:** + +The ``session_start_time`` is no longer required to have a timezone offset. This information is now optional, but recommended. If the timezone offset is not provided, it should **not** be assumed to be UTC. Check functions: :py:meth:`~nwbinspector.checks.nwbfile_metadata.check_session_start_time_old_date`, :py:meth:`~nwbinspector.checks.nwbfile_metadata.check_session_start_time_future_date`, :py:meth:`~nwbinspector.checks.time_series.check_timestamp_of_the_first_sample_is_not_negative` -:py:meth:`~nwbinspector.checks.tables.check_table_time_columns_are_not_negative` +:py:meth:`~nwbinspector.checks.tables.check_table_time_columns_are_not_negative`, +:py:meth:`~nwbinspector.checks.nwbfile_metadata.check_session_start_time_contains_time_zone`, diff --git a/src/nwbinspector/checks/nwbfile_metadata.py b/src/nwbinspector/checks/nwbfile_metadata.py index a5fbae66e..852d66ef9 100644 --- a/src/nwbinspector/checks/nwbfile_metadata.py +++ b/src/nwbinspector/checks/nwbfile_metadata.py @@ -1,7 +1,7 @@ """Check functions that examine general NWBFile metadata.""" import re -from datetime import datetime +from datetime import datetime, date from isodate import parse_duration, Duration from pynwb import NWBFile, ProcessingModule @@ -36,6 +36,22 @@ def check_session_start_time_old_date(nwbfile: NWBFile): ) +@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=NWBFile) +def check_session_start_time_contains_time_zone(nwbfile: NWBFile): + """ + Check if the session_start_time contains a time zone. + + Best Practice: :ref:`best_practice_global_time_reference` + """ + session_start_time = nwbfile.session_start_time + if session_start_time.tzinfo is None: + return InspectorMessage( + message=( + f"The session_start_time ({session_start_time}) does not contain a time zone." + ) + ) + + @register_check(importance=Importance.CRITICAL, neurodata_type=NWBFile) def check_session_start_time_future_date(nwbfile: NWBFile): """ diff --git a/tests/unit_tests/test_nwbfile_metadata.py b/tests/unit_tests/test_nwbfile_metadata.py index d86140965..b8350e8a6 100644 --- a/tests/unit_tests/test_nwbfile_metadata.py +++ b/tests/unit_tests/test_nwbfile_metadata.py @@ -1,5 +1,5 @@ from uuid import uuid4 -from datetime import datetime, timezone +from datetime import datetime, timezone, date from pynwb import NWBFile, ProcessingModule from pynwb.file import Subject @@ -23,6 +23,7 @@ check_processing_module_name, check_session_start_time_old_date, check_session_start_time_future_date, + check_session_start_time_contains_time_zone, PROCESSING_MODULE_CONFIG, ) from nwbinspector.tools import make_minimal_nwbfile @@ -56,6 +57,24 @@ def test_check_session_start_time_future_date_pass(): assert check_session_start_time_future_date(nwbfile) is None +def test_check_session_start_time_contains_time_zone_pass(): + nwbfile = NWBFile(session_description="", identifier=str(uuid4()), session_start_time=datetime(2010, 1, 1, 0, 0, 0, 0, timezone.utc)) + assert check_session_start_time_contains_time_zone(nwbfile) is None + + +def test_check_session_start_time_contains_time_zone_fail(): + session_start_time = datetime(2010, 1, 1, 0, 0, 0, 0) + nwbfile = NWBFile(session_description="", identifier=str(uuid4()), session_start_time=session_start_time) + assert check_session_start_time_contains_time_zone(nwbfile) == InspectorMessage( + message=f"The session_start_time ({session_start_time}) does not contain a time zone.", + importance=Importance.BEST_PRACTICE_SUGGESTION, + check_function_name="check_session_start_time_contains_time_zone", + object_type="NWBFile", + object_name="root", + location="/", + ) + + def test_check_session_start_time_future_date_fail(): nwbfile = NWBFile( session_description="", From a9ace70d50b658164bec8543d269acae89c2a314 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 May 2024 16:53:12 +0000 Subject: [PATCH 02/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/nwbinspector/checks/nwbfile_metadata.py | 4 +--- tests/unit_tests/test_nwbfile_metadata.py | 6 +++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/nwbinspector/checks/nwbfile_metadata.py b/src/nwbinspector/checks/nwbfile_metadata.py index 852d66ef9..a29a91cc7 100644 --- a/src/nwbinspector/checks/nwbfile_metadata.py +++ b/src/nwbinspector/checks/nwbfile_metadata.py @@ -46,9 +46,7 @@ def check_session_start_time_contains_time_zone(nwbfile: NWBFile): session_start_time = nwbfile.session_start_time if session_start_time.tzinfo is None: return InspectorMessage( - message=( - f"The session_start_time ({session_start_time}) does not contain a time zone." - ) + message=(f"The session_start_time ({session_start_time}) does not contain a time zone.") ) diff --git a/tests/unit_tests/test_nwbfile_metadata.py b/tests/unit_tests/test_nwbfile_metadata.py index b8350e8a6..45a01b7f5 100644 --- a/tests/unit_tests/test_nwbfile_metadata.py +++ b/tests/unit_tests/test_nwbfile_metadata.py @@ -58,7 +58,11 @@ def test_check_session_start_time_future_date_pass(): def test_check_session_start_time_contains_time_zone_pass(): - nwbfile = NWBFile(session_description="", identifier=str(uuid4()), session_start_time=datetime(2010, 1, 1, 0, 0, 0, 0, timezone.utc)) + nwbfile = NWBFile( + session_description="", + identifier=str(uuid4()), + session_start_time=datetime(2010, 1, 1, 0, 0, 0, 0, timezone.utc), + ) assert check_session_start_time_contains_time_zone(nwbfile) is None From 4f67b29324236daf636cea9d52167ea90d139d94 Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Fri, 3 May 2024 12:55:40 -0400 Subject: [PATCH 03/10] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb11a883..60307f39f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Upcoming + +### New Checks + +* Add check for missing timezone for `session_start_time` [#458](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/458). + # v0.4.35 ### Fixes From 283ebab63821b73e8c24fb0e8b43b6aba05065f0 Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Fri, 3 May 2024 12:55:55 -0400 Subject: [PATCH 04/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60307f39f..43157115d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Upcoming +# Upcoming (0.5.0) ### New Checks From 5a5b2293176b3115aeb575516d7203de0d06403c Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Fri, 3 May 2024 12:56:57 -0400 Subject: [PATCH 05/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43157115d..cb21d3e42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### New Checks -* Add check for missing timezone for `session_start_time` [#458](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/458). +* Add: `check_session_start_time_contains_time_zone` [#458](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/458). # v0.4.35 From 7a28782b798df68858f2172b4b25ea062cf64296 Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Fri, 3 May 2024 13:42:00 -0500 Subject: [PATCH 06/10] add skip for earlier versions of pynwb --- tests/unit_tests/test_nwbfile_metadata.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/test_nwbfile_metadata.py b/tests/unit_tests/test_nwbfile_metadata.py index b8350e8a6..dac4b7fcb 100644 --- a/tests/unit_tests/test_nwbfile_metadata.py +++ b/tests/unit_tests/test_nwbfile_metadata.py @@ -1,6 +1,8 @@ from uuid import uuid4 -from datetime import datetime, timezone, date +from datetime import datetime, timezone +import pynwb +import pytest from pynwb import NWBFile, ProcessingModule from pynwb.file import Subject @@ -57,11 +59,13 @@ def test_check_session_start_time_future_date_pass(): assert check_session_start_time_future_date(nwbfile) is None +@pytest.mark.skipif(pynwb.__version__ < "2.7.0", reason="Feature not supported in pynwb < 2.7.0") def test_check_session_start_time_contains_time_zone_pass(): nwbfile = NWBFile(session_description="", identifier=str(uuid4()), session_start_time=datetime(2010, 1, 1, 0, 0, 0, 0, timezone.utc)) assert check_session_start_time_contains_time_zone(nwbfile) is None +@pytest.mark.skipif(pynwb.__version__ < "2.7.0", reason="Feature not supported in pynwb < 2.7.0") def test_check_session_start_time_contains_time_zone_fail(): session_start_time = datetime(2010, 1, 1, 0, 0, 0, 0) nwbfile = NWBFile(session_description="", identifier=str(uuid4()), session_start_time=session_start_time) From 8b5300670952d8d0978e9e94d5c19513f514dc99 Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Fri, 3 May 2024 17:58:23 -0500 Subject: [PATCH 07/10] add handling of date to time zone check --- src/nwbinspector/checks/nwbfile_metadata.py | 15 ++++++++++++++- tests/unit_tests/test_nwbfile_metadata.py | 12 +++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/nwbinspector/checks/nwbfile_metadata.py b/src/nwbinspector/checks/nwbfile_metadata.py index a29a91cc7..bdebbdfe8 100644 --- a/src/nwbinspector/checks/nwbfile_metadata.py +++ b/src/nwbinspector/checks/nwbfile_metadata.py @@ -44,6 +44,8 @@ def check_session_start_time_contains_time_zone(nwbfile: NWBFile): Best Practice: :ref:`best_practice_global_time_reference` """ session_start_time = nwbfile.session_start_time + if isinstance(session_start_time, date): + return if session_start_time.tzinfo is None: return InspectorMessage( message=(f"The session_start_time ({session_start_time}) does not contain a time zone.") @@ -58,8 +60,19 @@ def check_session_start_time_future_date(nwbfile: NWBFile): Best Practice: :ref:`best_practice_global_time_reference` """ session_start_time = nwbfile.session_start_time + + if isinstance(session_start_time, date): + current_date = date.today() + if session_start_time > current_date: + return InspectorMessage( + message=( + f"The session_start_time ({session_start_time}) is set to a future date. " + "Please ensure that the session_start_time is set to the correct date and time." + ) + ) + current_time = datetime.now() - if session_start_time.tzinfo is not None: + if isinstance(session_start_time, datetime) and session_start_time.tzinfo is not None: current_time = current_time.astimezone() if session_start_time >= current_time: return InspectorMessage( diff --git a/tests/unit_tests/test_nwbfile_metadata.py b/tests/unit_tests/test_nwbfile_metadata.py index 8b58015ba..9a7d72c2e 100644 --- a/tests/unit_tests/test_nwbfile_metadata.py +++ b/tests/unit_tests/test_nwbfile_metadata.py @@ -1,5 +1,5 @@ from uuid import uuid4 -from datetime import datetime, timezone +from datetime import datetime, timezone, date import pynwb import pytest @@ -69,6 +69,16 @@ def test_check_session_start_time_contains_time_zone_pass(): assert check_session_start_time_contains_time_zone(nwbfile) is None +@pytest.mark.skipif(pynwb.__version__ < "2.7.0", reason="Feature not supported in pynwb < 2.7.0") +def test_check_session_start_time_is_date_contains_time_zone_pass(): + nwbfile = NWBFile( + session_description="", + identifier=str(uuid4()), + session_start_time=date(2010, 1, 1), + ) + assert check_session_start_time_contains_time_zone(nwbfile) is None + + @pytest.mark.skipif(pynwb.__version__ < "2.7.0", reason="Feature not supported in pynwb < 2.7.0") def test_check_session_start_time_contains_time_zone_fail(): session_start_time = datetime(2010, 1, 1, 0, 0, 0, 0) From bf54b4d777f67e068cf4ab412affa333c534632f Mon Sep 17 00:00:00 2001 From: Cody Baker <51133164+CodyCBakerPhD@users.noreply.github.com> Date: Fri, 24 May 2024 08:59:22 -0400 Subject: [PATCH 08/10] ignore assertin for codespell --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2b3b8638f..0bd4542e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,7 @@ extend-exclude = ''' ''' [tool.codespell] -# Ref: https://github.com/codespell-project/codespell#using-a-config-file skip = '.git*,*.pdf,*.css' check-hidden = true # ignore-regex = '' -# ignore-words-list = '' +ignore-words-list = 'assertin' From d5a14d31b7062acdc01b18998c075668fa076048 Mon Sep 17 00:00:00 2001 From: Cody Baker <51133164+CodyCBakerPhD@users.noreply.github.com> Date: Fri, 24 May 2024 09:04:11 -0400 Subject: [PATCH 09/10] remove codespell workflow --- .github/workflows/codespell.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/codespell.yml diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml deleted file mode 100644 index a7e91c56e..000000000 --- a/.github/workflows/codespell.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Codespell configuration is within pyproject.toml ---- -name: Codespell - -on: - push: - branches: [dev] - pull_request: - branches: [dev] - -permissions: - contents: read - -jobs: - codespell: - name: Check for spelling errors - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Codespell - uses: codespell-project/actions-codespell@v2 From bf5c91217c14ab182d33334f7e5fdafcc942ebb5 Mon Sep 17 00:00:00 2001 From: Cody Baker <51133164+CodyCBakerPhD@users.noreply.github.com> Date: Fri, 24 May 2024 09:05:31 -0400 Subject: [PATCH 10/10] bump version in pre-commit --- .pre-commit-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2a2336f35..ffeb26674 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,8 +11,7 @@ repos: - id: black - repo: https://github.com/codespell-project/codespell - # Configuration for codespell is in pyproject.toml - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell additional_dependencies: