Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Do not auto-set timezone, allow date (#1886)" #1908

Merged
merged 6 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# PyNWB Changelog

## PyNWB 2.8.0 (Upcoming)
## PyNWB 2.8.0 (May 28, 2024)

### Enhancements and minor changes
- Set rate default value inside `mock_ElectricalSeries` to avoid having to set `rate=None` explicitly when passing timestamps. @h-mayorquin [#1894](https://github.com/NeurodataWithoutBorders/pynwb/pull/1894)
- Integrate validation through the `TypeConfigurator`. @mavaylon1 [#1829](https://github.com/NeurodataWithoutBorders/pynwb/pull/1829)
- Exposed `aws_region` to `NWBHDF5IO`. @rly [#1903](https://github.com/NeurodataWithoutBorders/pynwb/pull/1903)

### Bug fixes
- Revert changes in PyNWB 2.7.0 that allow datetimes without a timezone and without a time while issues with DANDI upload are resolved. @rly [#1908](https://github.com/NeurodataWithoutBorders/pynwb/pull/1908)

## PyNWB 2.7.0 (May 2, 2024)

### Enhancements and minor changes
Expand Down
6 changes: 5 additions & 1 deletion docs/gallery/advanced_io/h5dataio.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@
#

from datetime import datetime

from dateutil.tz import tzlocal

from pynwb import NWBFile

start_time = datetime(2017, 4, 3, hour=11, minute=0)
start_time = datetime(2017, 4, 3, 11, tzinfo=tzlocal())

nwbfile = NWBFile(
session_description="demonstrate advanced HDF5 I/O features",
identifier="NWB123",
session_start_time=start_time,
)


####################
# Normally if we create a :py:class:`~pynwb.base.TimeSeries` we would do

Expand Down
7 changes: 5 additions & 2 deletions docs/gallery/advanced_io/linking_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,15 @@
# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_linking_data.png'

from datetime import datetime
from uuid import uuid4

import numpy as np
from dateutil.tz import tzlocal

from pynwb import NWBHDF5IO, NWBFile, TimeSeries
from uuid import uuid4

# Create the base data
start_time = datetime(2017, 4, 3, hour=11, minute=0)
start_time = datetime(2017, 4, 3, 11, tzinfo=tzlocal())
data = np.arange(1000).reshape((100, 10))
timestamps = np.arange(100)
filename1 = "external1_example.nwb"
Expand Down
2 changes: 1 addition & 1 deletion docs/gallery/advanced_io/parallelio.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
# from datetime import datetime
# from hdmf.backends.hdf5.h5_utils import H5DataIO
#
# start_time = datetime(2018, 4, 25, hour=2, minute=30, second=3)
# start_time = datetime(2018, 4, 25, 2, 30, 3, tzinfo=tz.gettz("US/Pacific"))
# fname = "test_parallel_pynwb.nwb"
# rank = MPI.COMM_WORLD.rank # The process ID (integer 0-3 for 4-process run)
#
Expand Down
8 changes: 6 additions & 2 deletions docs/gallery/advanced_io/plot_iterative_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,13 @@

# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_iterative_write.png'
from datetime import datetime
from pynwb import NWBHDF5IO, NWBFile, TimeSeries
from uuid import uuid4

from dateutil.tz import tzlocal

from pynwb import NWBHDF5IO, NWBFile, TimeSeries


def write_test_file(filename, data, close_io=True):
"""

Expand All @@ -125,7 +129,7 @@ def write_test_file(filename, data, close_io=True):
"""

# Create a test NWBfile
start_time = datetime(2017, 4, 3, hour=11, minute=30)
start_time = datetime(2017, 4, 3, 11, tzinfo=tzlocal())
nwbfile = NWBFile(
session_description="demonstrate iterative write",
identifier=str(uuid4()),
Expand Down
21 changes: 13 additions & 8 deletions docs/gallery/domain/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,23 @@
The following examples will reference variables that may not be defined within the block they are used in. For
clarity, we define them here:
"""
# Define file paths used in the tutorial

import os

# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_image_data.png'
from datetime import datetime
from uuid import uuid4

import numpy as np
import os
from dateutil import tz
from dateutil.tz import tzlocal
from PIL import Image

from pynwb import NWBHDF5IO, NWBFile
from pynwb.base import Images
from pynwb.image import GrayscaleImage, ImageSeries, OpticalSeries, RGBAImage, RGBImage
from uuid import uuid4

# Define file paths used in the tutorial
nwbfile_path = os.path.abspath("images_tutorial.nwb")
moviefiles_path = [
os.path.abspath("image/file_1.tiff"),
Expand All @@ -45,12 +50,12 @@
# Create an :py:class:`~pynwb.file.NWBFile` object with the required fields
# (``session_description``, ``identifier``, ``session_start_time``) and additional metadata.

session_start_time = datetime(2018, 4, 25, hour=2, minute=30)
session_start_time = datetime(2018, 4, 25, 2, 30, 3, tzinfo=tz.gettz("US/Pacific"))

nwbfile = NWBFile(
session_description="my first synthetic recording",
identifier=str(uuid4()),
session_start_time=session_start_time,
session_start_time=datetime.now(tzlocal()),
experimenter=[
"Baggins, Bilbo",
],
Expand Down Expand Up @@ -133,21 +138,21 @@
# ^^^^^^^^^^^^^^
#
# External files (e.g. video files of the behaving animal) can be added to the :py:class:`~pynwb.file.NWBFile`
# by creating an :py:class:`~pynwb.image.ImageSeries` object using the
# by creating an :py:class:`~pynwb.image.ImageSeries` object using the
# :py:attr:`~pynwb.image.ImageSeries.external_file` attribute that specifies
# the path to the external file(s) on disk.
# The file(s) path must be relative to the path of the NWB file.
# Either ``external_file`` or ``data`` must be specified, but not both.
#
# If the sampling rate is constant, use :py:attr:`~pynwb.base.TimeSeries.rate` and
# If the sampling rate is constant, use :py:attr:`~pynwb.base.TimeSeries.rate` and
# :py:attr:`~pynwb.base.TimeSeries.starting_time` to specify time.
# For irregularly sampled recordings, use :py:attr:`~pynwb.base.TimeSeries.timestamps` to specify time for each sample
# image.
#
# Each external image may contain one or more consecutive frames of the full :py:class:`~pynwb.image.ImageSeries`.
# The :py:attr:`~pynwb.image.ImageSeries.starting_frame` attribute serves as an index to indicate which frame
# each file contains.
# For example, if the ``external_file`` dataset has three paths to files and the first and the second file have 2
# For example, if the ``external_file`` dataset has three paths to files and the first and the second file have 2
# frames, and the third file has 3 frames, then this attribute will have values `[0, 2, 4]`.

external_file = [
Expand Down
4 changes: 2 additions & 2 deletions docs/gallery/general/add_remove_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
nwbfile = NWBFile(
session_description="demonstrate adding to an NWB file",
identifier="NWB123",
session_start_time=datetime.datetime.now(),
session_start_time=datetime.datetime.now(datetime.timezone.utc),
)

filename = "nwbfile.nwb"
Expand Down Expand Up @@ -91,7 +91,7 @@
nwbfile = NWBFile(
session_description="demonstrate export of an NWB file",
identifier="NWB123",
session_start_time=datetime.datetime.now(),
session_start_time=datetime.datetime.now(datetime.timezone.utc),
)
data1 = list(range(100, 200, 10))
timestamps1 = np.arange(10, dtype=float)
Expand Down
19 changes: 10 additions & 9 deletions docs/gallery/general/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,16 @@ def __init__(self, **kwargs):
# To demonstrate this, first we will make some simulated data using our extensions.

from datetime import datetime

from dateutil.tz import tzlocal

from pynwb import NWBFile
from uuid import uuid4

session_start_time = datetime(2017, 4, 3, hour=11, minute=0)
start_time = datetime(2017, 4, 3, 11, tzinfo=tzlocal())
create_date = datetime(2017, 4, 15, 12, tzinfo=tzlocal())

nwbfile = NWBFile(
session_description="demonstrate caching",
identifier=str(uuid4()),
session_start_time=session_start_time,
"demonstrate caching", "NWB456", start_time, file_create_date=create_date
)

device = nwbfile.create_device(name="trodes_rig123")
Expand Down Expand Up @@ -332,18 +333,18 @@ class PotatoSack(MultiContainerInterface):
# Then use the objects (again, this would often be done in a different file).

from datetime import datetime

from dateutil.tz import tzlocal

from pynwb import NWBHDF5IO, NWBFile

# You can add potatoes to a potato sack in different ways
potato_sack = PotatoSack(potatos=Potato(name="potato1", age=2.3, weight=3.0))
potato_sack.add_potato(Potato("potato2", 3.0, 4.0))
potato_sack.create_potato("big_potato", 10.0, 20.0)

session_start_time = datetime(2017, 4, 3, hour=12, minute=0)
nwbfile = NWBFile(
session_description="a file with metadata",
identifier=str(uuid4()),
session_start_time = session_start_time,
"a file with metadata", "NB123A", datetime(2018, 6, 1, tzinfo=tzlocal())
)

pmod = nwbfile.create_processing_module("module_name", "desc")
Expand Down
8 changes: 5 additions & 3 deletions docs/gallery/general/object_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@

"""

# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_objectid.png'

from datetime import datetime

import numpy as np
from dateutil.tz import tzlocal

# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_objectid.png'
from pynwb import NWBFile, TimeSeries

# set up the NWBFile
start_time = datetime(2019, 4, 3, hour=11, minute=0)
start_time = datetime(2019, 4, 3, 11, tzinfo=tzlocal())
nwbfile = NWBFile(
session_description="demonstrate NWB object IDs",
identifier="NWB456",
Expand Down
2 changes: 1 addition & 1 deletion docs/gallery/general/plot_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@
# Use keyword arguments when constructing :py:class:`~pynwb.file.NWBFile` objects.
#

session_start_time = datetime(2018, 4, 25, hour=2, minute=30, second=3, tzinfo=tz.gettz("US/Pacific"))
session_start_time = datetime(2018, 4, 25, 2, 30, 3, tzinfo=tz.gettz("US/Pacific"))

nwbfile = NWBFile(
session_description="Mouse exploring an open field", # required
Expand Down
7 changes: 5 additions & 2 deletions docs/gallery/general/plot_timeintervals.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,18 @@

# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_timeintervals.png'
from datetime import datetime
from uuid import uuid4

import numpy as np
from dateutil.tz import tzlocal

from pynwb import NWBFile, TimeSeries
from uuid import uuid4

# create the NWBFile
nwbfile = NWBFile(
session_description="my first synthetic recording", # required
identifier=str(uuid4()), # required
session_start_time=datetime(2017, 4, 3, hour=11), # required
session_start_time=datetime(2017, 4, 3, 11, tzinfo=tzlocal()), # required
experimenter="Baggins, Bilbo", # optional
lab="Bag End Laboratory", # optional
institution="University of Middle Earth at the Shire", # optional
Expand Down
12 changes: 8 additions & 4 deletions docs/gallery/general/scratch.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,24 @@
# To demonstrate linking and scratch space, lets assume we are starting with some acquired data.
#

# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_scratch.png'

from datetime import datetime

import numpy as np
from dateutil.tz import tzlocal

# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_scratch.png'
from pynwb import NWBHDF5IO, NWBFile, TimeSeries

# set up the NWBFile
start_time = datetime(2019, 4, 3, hour=11, minute=0)
start_time = datetime(2019, 4, 3, 11, tzinfo=tzlocal())
create_date = datetime(2019, 4, 15, 12, tzinfo=tzlocal())

nwb = NWBFile(
session_description="demonstrate NWBFile scratch", # required
identifier="NWB456", # required
session_start_time=start_time, # required
)
file_create_date=create_date,
) # optional

# make some fake data
timestamps = np.linspace(0, 100, 1024)
Expand Down
41 changes: 32 additions & 9 deletions src/pynwb/file.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from datetime import datetime, date, timedelta
from datetime import datetime, timedelta
from dateutil.tz import tzlocal
from collections.abc import Iterable
from warnings import warn
Expand Down Expand Up @@ -104,8 +104,8 @@
'doc': ('The weight of the subject, including units. Using kilograms is recommended. e.g., "0.02 kg". '
'If a float is provided, then the weight will be stored as "[value] kg".'),
'default': None},
{'name': 'date_of_birth', 'type': (datetime, date), 'default': None,
'doc': 'The date of birth, which may include time and timezone. May be supplied instead of age.'},
{'name': 'date_of_birth', 'type': datetime, 'default': None,
'doc': 'The datetime of the date of birth. May be supplied instead of age.'},
{'name': 'strain', 'type': str, 'doc': 'The strain of the subject, e.g., "C57BL/6J"', 'default': None},
)
def __init__(self, **kwargs):
Expand Down Expand Up @@ -141,6 +141,10 @@
if isinstance(args_to_set["age"], timedelta):
args_to_set["age"] = pd.Timedelta(args_to_set["age"]).isoformat()

date_of_birth = args_to_set['date_of_birth']
if date_of_birth and date_of_birth.tzinfo is None:
args_to_set['date_of_birth'] = _add_missing_timezone(date_of_birth)

Check warning on line 146 in src/pynwb/file.py

View check run for this annotation

Codecov / codecov/patch

src/pynwb/file.py#L146

Added line #L146 was not covered by tests

for key, val in args_to_set.items():
setattr(self, key, val)

Expand Down Expand Up @@ -304,11 +308,10 @@
@docval({'name': 'session_description', 'type': str,
'doc': 'a description of the session where this data was generated'},
{'name': 'identifier', 'type': str, 'doc': 'a unique text identifier for the file'},
{'name': 'session_start_time', 'type': (datetime, date),
'doc': 'the start date and time of the recording session'},
{'name': 'file_create_date', 'type': ('array_data', datetime, date),
{'name': 'session_start_time', 'type': datetime, 'doc': 'the start date and time of the recording session'},
{'name': 'file_create_date', 'type': ('array_data', datetime),
'doc': 'the date and time the file was created and subsequent modifications made', 'default': None},
{'name': 'timestamps_reference_time', 'type': (datetime, date),
{'name': 'timestamps_reference_time', 'type': datetime,
'doc': 'date and time corresponding to time zero of all timestamps; defaults to value '
'of session_start_time', 'default': None},
{'name': 'experimenter', 'type': (tuple, list, str),
Expand Down Expand Up @@ -463,18 +466,26 @@
kwargs['name'] = 'root'
super().__init__(**kwargs)

# add timezone to session_start_time if missing
session_start_time = args_to_set['session_start_time']
if session_start_time.tzinfo is None:
args_to_set['session_start_time'] = _add_missing_timezone(session_start_time)

Check warning on line 472 in src/pynwb/file.py

View check run for this annotation

Codecov / codecov/patch

src/pynwb/file.py#L472

Added line #L472 was not covered by tests

# set timestamps_reference_time to session_start_time if not provided
# if provided, ensure that it has a timezone
timestamps_reference_time = args_to_set['timestamps_reference_time']
if timestamps_reference_time is None:
args_to_set['timestamps_reference_time'] = args_to_set['session_start_time']
elif timestamps_reference_time.tzinfo is None:
raise ValueError("'timestamps_reference_time' must be a timezone-aware datetime object.")

# convert file_create_date to list and add timezone if missing
file_create_date = args_to_set['file_create_date']
if file_create_date is None:
file_create_date = datetime.now(tzlocal())
if isinstance(file_create_date, (datetime, date)):
if isinstance(file_create_date, datetime):
file_create_date = [file_create_date]
args_to_set['file_create_date'] = file_create_date
args_to_set['file_create_date'] = list(map(_add_missing_timezone, file_create_date))

# backwards-compatibility code for ic_electrodes / icephys_electrodes
icephys_electrodes = args_to_set['icephys_electrodes']
Expand Down Expand Up @@ -1144,6 +1155,18 @@
return NWBFile(**kwargs)


def _add_missing_timezone(date):
"""
Add local timezone information on a datetime object if it is missing.
"""
if not isinstance(date, datetime):
raise ValueError("require datetime object")

Check warning on line 1163 in src/pynwb/file.py

View check run for this annotation

Codecov / codecov/patch

src/pynwb/file.py#L1163

Added line #L1163 was not covered by tests
if date.tzinfo is None:
warn("Date is missing timezone information. Updating to local timezone.", stacklevel=2)
return date.replace(tzinfo=tzlocal())
return date


def _tablefunc(table_name, description, columns):
t = DynamicTable(name=table_name, description=description)
for c in columns:
Expand Down
Loading
Loading