A repository storing our code to go from era5 hourly data, taken with era5cli, to daily data that is usable by ESMValTool.
- This code is run from a linux environment, so the
path.home()variable might differ from yours. - You do need correct data from a leap and non-leap year
- This code works, but it is slow and there are improvements to be made for sure.
- use the era5cli bash script in
era5-hourly-to-daily/data_to_be_cmorized/cmor.shand get the correct years and variables - make sure you have data that is already correct, 2 datasets per variable: 1 non-leap year and 1 leap year.
- go into:
py_cmor.pythen from the top line to the bottom:- make sure the
rename_dictis correct and has the variables you want, in 'era5': 'cmip variable name' - check
home_dir - check the
conversion_dict, if you are not sure what it is for your variable, you can always set it to 1 and then change accordingly when testing your results - In this section:
# Build time boundswe set the time bounds for 'tas' differently this is because of this reason - In the final 'if-block' of the function you need to check if you want the file name to be of 'Eday' or 'day' or maybe even 'CFDay'
- then in the final block
if __name__ == "__main__":check your home folder again
- make sure the
- from the command line in
era5-hourly-to-daily/runpy_cmor.pywith:python py_cmor.sh &to make sure it becomes a background task. - The data should then appear in the
era5-hourly-to-daily/cmorized_outputfolder - You can check the
testing_values.ipynbnotebook to visually check your data, it also has some other built in checks- here you need to check the home folders again
- and the
correct_data_dict - you can change the
test_variablesbeware that the plot that is generated is hardcoded to be 2x2, so 4 variables.
py_cmor.py needs you to download netCDF files first. zarr_version/zarr_era5.py skips
that step and streams the hourly data straight out of the analysis-ready (ARCO) zarr
stores that the CDS publishes, then writes the same ESMValTool/OBS6 files. It can produce
daily output (like py_cmor.py) and hourly output.
Supported collections:
| flag | dataset | resolution | covers | filenames |
|---|---|---|---|---|
--collection era5-land (default) |
ERA5-Land | 0.1° | 1950-01-02 → | OBS6_ERA5-Land_reanaly_1_... |
--collection era5 |
ERA5 single levels | 0.25° | 1940 → | OBS6_ERA5_reanaly_1_... |
The dataset name in the filename and in the dataset_id global attribute matches
ESMValCore's native6 dataset names, so ESMValTool picks the right fixes up.
pip install -r zarr_version/requirements.txt
cp zarr_version/.env.example zarr_version/.env # then paste your CDS token into itThe token is your CDS personal access token from https://cds.climate.copernicus.eu/profile.
# always look before you leap - prints the output sizes, downloads nothing
python zarr_version/zarr_era5.py --years 2015 --dry-run
# daily only, ERA5-Land, three variables (the default set)
python zarr_version/zarr_era5.py --years 2015 --frequency day
# daily + hourly, ERA5 single levels, hourly split into monthly files
python zarr_version/zarr_era5.py --collection era5 --years 1990-1995 \
--frequency both --hourly-split month --output-dir ~/cmorized_outputUseful flags: --variables, --reference-dir, --overwrite, --complevel, --progress,
--time-chunk/--space-chunk (dask chunking), --chunking time|geo (which ARCO store to
read). Run with --help for the full list.
Every run plans all its files up front and then reports per-file timing, cumulative progress and an ETA:
[PLAN] 6 file(s) to write, reading 52560 hourly time steps in total
[1/6] OBS6_ERA5-Land_reanaly_1_day_tas_2010-2010.nc: {'lat': 1801, 'lon': 3600, 'time': 365} -> ~8.8 GB uncompressed
[1/6] [OK] OBS6_ERA5-Land_reanaly_1_day_tas_2010-2010.nc in 15m 46s (1.6 GB on disk, 9.5 MB/s)
[1/6] elapsed 1h 12m 30s | 16.7% | ETA 6h 02m 30s
...
[DONE] 6/6 files, 19.1 GB written in 7h 15m 03s (0 failures)
The percentage and ETA are weighted by hours of source data read, not by file count, because the ARCO download dominates the runtime — a daily file costs the same to read as the hourly file for the same period. It's an estimate: writing hourly output adds some compression time on top, so hourly files land slightly behind the projection.
The per-file timer and ETA are always printed — there is no flag to enable them. dask's
live progress bar is also on by default; pass --no-progress to suppress it. It only
works on a terminal, so if you redirect to a log it disables itself automatically (it
redraws with \r, which would turn a log file into thousands of lines) and you keep the
per-file timer and ETA.
Output goes to ./cmorized_output relative to wherever you run the script from, and the
resolved absolute path is printed as the first line of every run. Use --output-dir to
put it elsewhere.
Reads from the ARCO store time out fairly regularly on long jobs. Each file is retried
--retries times (default 3, with 10s/20s/40s backoff) before it is given up on, and
--http-timeout (default 300s) sets how long a single read may stall first.
Each file is written to <name>.nc.partial and renamed only once the write succeeds, so
a failed or killed run can never leave a truncated file under the real name — which
matters, because the resume logic treats an existing file as complete. Any .partial
left over from a killed run is swept at the start of the next one.
For a long run, detach it and tail the log:
nohup python zarr_version/zarr_era5.py --years 2010 --frequency day > era5land_2010.log 2>&1 &
tail -f era5land_2010.logThe script switches stdout to line buffering itself, so the log fills in as it goes — you
don't need python -u. If a run dies partway, just rerun the same command: finished files
are skipped unless you pass --overwrite.
- The script works on the full global grid. One year of global hourly ERA5-Land is
~212 GB per variable (~9 GB daily); ERA5 single levels is ~34 GB hourly and ~1.4 GB
daily. Use
--dry-runfirst, and--hourly-split monthto keep individual files manageable. Existing output files are skipped unless you pass--overwrite, so an interrupted run resumes. tas,prandrsdscome from ARCO zarr.evspsblpotandevspsbldo not — evaporation is not published as ARCO in either collection, so those two are fetched from the classic CDS request API instead (pip install cdsapi; the same token is used). They are daily-only, and only for--collection era5-land. Because ERA5-Land accumulates from 00 UTC there, only the 00:00 field of each day is downloaded — that value is already the previous day's total, so it is 24x less data than the hourly series. Use--area N W S Eto fetch a box instead of the globe, and--cds-cacheto control where downloads are kept (reruns do not refetch).- Both evaporation variables carry a
-1sign correction, matching ESMValCore's native6 fixes, which flip ERA5's downward-positive evaporation to the CMOR convention. Note thatpy_cmor.pydoes not do this, so itsevspsblpot/evspsbloutput has the opposite sign. - Accumulated fields (
tp,ssrd) are stored as hourly accumulations in both ARCO collections — ERA5-Land is not stored with the usual "accumulated since 00 UTC" convention here. So the conversion factors frompy_cmor.pycarry over unchanged and a plain daily mean is correct. - Time stamps and bounds follow
py_cmor.py:tasis stamped at 11:30 with bounds[00:00, 23:00], the accumulated variables at 12:00 with bounds[00:00, 24:00](why). --reference-diris optional. Without it the CMOR metadata is generated from the CMIP6 tables (via ESMValCore if installed, otherwise a built-in fallback). With it, daily output inherits attributes and time bounds from your known-good files incorrect_data/, exactly likepy_cmor.pydoes.- To add a variable, add a
VarSpectoVARIABLESinzarr_era5.py(and aCMOR_FALLBACKentry if you don't have ESMValCore). The ARCO stores also holdd2m,sp,strd,skt,u10,v10, soil temperature/water, and snow.
For questions or if you need some data to test it on, please reach out to the eWaterCycle team!