Skip to content

Document the index convention of read_epw - #2894

Open
cdeil wants to merge 2 commits into
pvlib:mainfrom
cdeil:epw-index-convention
Open

cdeil wants to merge 2 commits into
pvlib:mainfrom
cdeil:epw-index-convention

Conversation

@cdeil

@cdeil cdeil commented Oct 9, 2026

Copy link
Copy Markdown

read_epw labels each EPW record with the start of its hour (EPW hour 1 becomes 00:00, from hour - 1 in _parse_epw), but the docstring only says "Days run from 0-23h". EPW irradiance and illuminance are totals over the hour ending at the time in the file, while temperature, humidity and pressure are values at that time, and read_tmy3 labels its records with the end of the hour and warns about it. So the same data read with read_epw and read_tmy3 are one hour apart, which is what #1156 reports.

This PR documents the convention with a warning like the one in read_tmy3, including the one-line shift that labels the records with the time given in the file. Changing the index itself would break existing users; if you would rather do that (perhaps behind a parameter), I'm happy to follow up with a PR. I ran into this while aligning EPW weather for a building simulation side project.

Checked: read_epw on tests/data/NLD_Amsterdam062400_IWEC.epw with coerce_year=2001 gives 2001-01-01 00:00 for EPW hour 1 and 2001-12-31 23:00 for the last record; tests/iotools/test_epw.py passes; flake8 is clean. I'll add the :pull: reference to the what's-new entry once this has a number.

Prepared with AI assistance (Claude); I reviewed it.

read_epw labels each EPW record with the start of its hour (EPW hour 1
becomes 00:00), while read_tmy3 labels each record with the end of its
hour, so the same data read from an EPW and a TMY3 file are one hour
apart. Say so in a warning, as read_tmy3 does, and show how to label
the records with the time given in the EPW file. See pvlib#1156.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

Hey @cdeil! 🎉

Thanks for opening your first pull request! We appreciate your
contribution. Please ensure you have reviewed and understood the
contributing guidelines.

If AI is used for any portion of this PR, you must vet the content
for technical accuracy.

Finally, be sure the PR description includes the PR
checklist,
and complete the items you are able to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@RDaxini RDaxini left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A docs-only change was never agreed in #1156 as an appropriate solution. Your own thoughts on the questions raised in the original issue regarding read_epw and end-of-hour labels would be welcome. A discussion there (in the issue thread) first, leading to a more comprehensive and lasting fix, could be more helpful.

Having said all that, thanks for your contribution. The docstring clarification is still useful, at least in the interim. I have recommended some wording revisions, but other than that this LGTM.

I'd leave #1156 open since this PR does not resolve it.

Comment thread pvlib/iotools/epw.py
Comment on lines +211 to +221
.. warning:: EPW irradiance and illuminance values are totals over the
hour *ending* at the time in the file, while temperatures, humidity
and pressure are values *at* that time [1]_. ``read_epw`` labels
each record with the *start* of its hour: EPW hour 1 (00:00-01:00)
becomes 00:00, so a full year runs from 00:00 on 1 January to 23:00
on 31 December. :py:func:`~pvlib.iotools.read_tmy3` labels each record
with the *end* of its hour instead, so the same data read from an EPW
file and from a TMY3 file are one hour apart. To label the records
with the time given in the EPW file, add one hour to the index:
``data.index = data.index + pd.Timedelta('1h')``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand, illuminance values are averages, not totals.
Suggested some minor clarification regarding time labels and year-end record too.

Suggested change
.. warning:: EPW irradiance and illuminance values are totals over the
hour *ending* at the time in the file, while temperatures, humidity
and pressure are values *at* that time [1]_. ``read_epw`` labels
each record with the *start* of its hour: EPW hour 1 (00:00-01:00)
becomes 00:00, so a full year runs from 00:00 on 1 January to 23:00
on 31 December. :py:func:`~pvlib.iotools.read_tmy3` labels each record
with the *end* of its hour instead, so the same data read from an EPW
file and from a TMY3 file are one hour apart. To label the records
with the time given in the EPW file, add one hour to the index:
``data.index = data.index + pd.Timedelta('1h')``.
.. warning:: In hourly EPW files, radiation totals and illuminance
averages cover the hour *ending* at the file time; temperature,
humidity and pressure are values *at* that time [1]_.
``read_epw`` labels all fields with the *start* of the hour (EPW
hour 1 becomes 00:00), while :py:func:`~pvlib.iotools.read_tmy3`
uses the *end*.
To use end-of-hour labels, add one hour to the index:
``data.index = data.index + pd.Timedelta('1h')``. The record for
31 December's final hour then has the label 1 January 00:00 of
the following year. No records are added or removed.

* Fix the :py:func:`pvlib.spa.solar_position` docstring, which listed
outputs ``elevation`` and ``apparent_elevation`` in the wrong order.
(:issue:`2886`, :pull:`2887`)
* Document in :py:func:`pvlib.iotools.read_epw` that each record is labelled

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pvlib uses US English

Suggested change
* Document in :py:func:`pvlib.iotools.read_epw` that each record is labelled
* Document in :py:func:`pvlib.iotools.read_epw` that each record is labeled

@RDaxini RDaxini added this to the v0.16.2 milestone Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants