Skip to content

Commit b75ab16

Browse files
authored
Merge pull request #86 from HydrologicEngineeringCenter/Delete-and-TimeZone
Delete and time zone
2 parents f9ddefb + 3ba9e1e commit b75ab16

10 files changed

Lines changed: 233 additions & 20 deletions

File tree

.github/workflows/test.yml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,20 @@ jobs:
2424
- name: Checkout code
2525
uses: actions/checkout@v4
2626

27-
- name: Install requests
28-
run: pip install requests
27+
# On all platforms install the PyPI tzdata distribution
28+
# (stdlib zoneinfo will auto‐pick it up; the backport will too)
29+
- name: Install tzdata (Python)
30+
run: pip install tzdata
31+
32+
- name: Install runtime dependencies
33+
run: |
34+
pip install requests
35+
pip install numpy
36+
pip install pytest
2937
3038
- name: add dll
3139
run: python3 src/hecdss/download_hecdss.py
3240

33-
- name: Install Packages
34-
run: pip install pytest
35-
36-
- name: Install numpy
37-
run: pip install numpy
38-
3941
- name: Run tests
4042
run: pytest tests
4143

src/hecdss/dateconverter.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
]
7878
class DateConverter:
7979

80-
8180
@staticmethod
8281
def dss_datetime_strings_from_datetime(dt:datetime):
8382
"""
@@ -95,6 +94,22 @@ def dss_datetime_strings_from_datetime(dt:datetime):
9594

9695
return dtstr[:9],dtstr[-8:]
9796

97+
@staticmethod
98+
def datetime_from_dss_datetime_string(datestr: str):
99+
"""
100+
convert DSS 24:00 style string to python datetime
101+
25Aug2023 09:23:47 -> 2023-08-25 09:23:47
102+
24Aug2023 24:00 -> 2023-08-25 00:00:00
103+
25Aug2023 00:10 -> 2023-08-25 00:10:00
104+
"""
105+
date_part, time_part = datestr.split()
106+
dt = datetime.strptime(date_part, "%d%b%Y")
107+
if time_part == "24:00":
108+
dt += timedelta(days=1)
109+
time_part = "00:00"
110+
time_obj = datetime.strptime(time_part, "%H:%M").time()
111+
return datetime.combine(dt.date(), time_obj)
112+
98113
@staticmethod
99114
def date_time_from_julian_second(time_julian, seconds_julian):
100115
""""
@@ -142,6 +157,7 @@ def julian_array_from_date_times(date_times, time_granularity_seconds=60, start_
142157
raise ValueError("Time Series Times array was None. Something didn't work right in DSS.")
143158
start_date_base = start_date_base.replace(hour=0, minute=0, second=0, microsecond=0)-timedelta(days=1)
144159
return [int(((i-start_date_base).days*86400 + i.hour * 3600 + i.minute * 60 + i.second)/time_granularity_seconds) for i in date_times]
160+
145161
@staticmethod
146162
def intervalString_to_sec(interval):
147163
if isinstance(interval, str):

src/hecdss/download_hecdss.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def download_and_unzip(url, zip_file, destination_dir):
3636
print(f"Failed to download zip file. Status code: {response.status_code}")
3737

3838
base_url = "https://www.hec.usace.army.mil/nexus/repository/maven-public/mil/army/usace/hec/hecdss/"
39-
version = "7-IU-6"
39+
version = "7-IU-10"
4040

4141
destination_dir = Path(__file__).parent.joinpath("lib")
4242
zip_url = f"{base_url}{version}-win-x86_64/hecdss-{version}-win-x86_64.zip"

src/hecdss/hecdss.py

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Docstring for public module."""
22
from datetime import datetime, timedelta
3+
from zoneinfo import ZoneInfo
34

45
import numpy as np
56

@@ -275,6 +276,8 @@ def _get_paired_data(self, pathname):
275276
unitsDependent2 = [""]
276277
typeIndependent2 = [""]
277278
typeDependent2 = [""]
279+
timeZoneName = [""]
280+
timeZoneNameLength = [""]
278281
status = self._native.hec_dss_pdRetrieve(pathname,
279282
doubleOrdinates, numberOrdinates[0],
280283
doubleValues, numberCurves[0] * numberOrdinates[0],
@@ -283,7 +286,8 @@ def _get_paired_data(self, pathname):
283286
typeIndependent2, len(typeIndependent[0]) + 1,
284287
unitsDependent2, len(unitsDependent[0]) + 1,
285288
typeDependent2, len(typeDependent[0]) + 1,
286-
labels, labelsLength[0])
289+
labels, labelsLength[0],
290+
timeZoneName, len(timeZoneNameLength[0]) + 1)
287291

288292
if status != 0:
289293
print(f"Error reading paired-data from '{pathname}'")
@@ -300,6 +304,7 @@ def _get_paired_data(self, pathname):
300304
pd.type_dependent = typeDependent2[0]
301305
pd.units_independent = unitsIndependent2[0]
302306
pd.units_dependent = unitsDependent2[0]
307+
pd.time_zone_name = timeZoneName[0]
303308
pd.id = pathname
304309
pd.location_info = self._get_location_info(pathname)
305310

@@ -392,6 +397,7 @@ def _get_timeseries(self, pathname, startDateTime, endDateTime, trim):
392397
units = [""]
393398
bufferLength = 40
394399
dataType = [""]
400+
timeZoneName = [""]
395401

396402
status = self._native.hec_dss_tsRetrieve(
397403
pathname,
@@ -411,6 +417,8 @@ def _get_timeseries(self, pathname, startDateTime, endDateTime, trim):
411417
bufferLength,
412418
dataType,
413419
bufferLength,
420+
timeZoneName,
421+
bufferLength
414422
)
415423

416424
# print("units = "+units[0])
@@ -453,8 +461,11 @@ def _get_timeseries(self, pathname, startDateTime, endDateTime, trim):
453461
start_date = [] if len(new_times) == 0 else new_times[0]
454462
time_granularity_seconds = timeGranularitySeconds[0]
455463
julian_base_date = julianBaseDate[0]
464+
timeZoneName = timeZoneName[0]
465+
if(timeZoneName):
466+
new_times = [i.replace(tzinfo=ZoneInfo(timeZoneName)) for i in new_times]
456467
location_info = self._get_location_info(pathname)
457-
ts = ts.create(values=values, times=new_times, quality=quality, units=units, data_type=data_type, start_date=start_date, time_granularity_seconds=time_granularity_seconds, julian_base_date=julian_base_date, path=pathname, location_info=location_info)
468+
ts = ts.create(values=values, times=new_times, quality=quality, units=units, data_type=data_type, start_date=start_date, time_granularity_seconds=time_granularity_seconds, julian_base_date=julian_base_date, time_zone_name=timeZoneName, path=pathname, location_info=location_info)
458469
return ts
459470

460471
def _get_location_info(self, pathname: str):
@@ -544,6 +555,8 @@ def put(self, container) -> int:
544555
False,
545556
ts.units,
546557
ts.data_type,
558+
ts.time_zone_name,
559+
0
547560
)
548561
self._catalog = None
549562
elif type(container) is IrregularTimeSeries:
@@ -567,6 +580,8 @@ def put(self, container) -> int:
567580
False,
568581
its.units,
569582
its.data_type,
583+
its.time_zone_name,
584+
1
570585
)
571586
self._catalog = None
572587
elif type(container) is PairedData:
@@ -600,6 +615,91 @@ def put(self, container) -> int:
600615

601616
return status
602617

618+
def delete(self, pathname: str, allrecords: bool = False, startdatetime=None, enddatetime=None) -> int:
619+
"""deletes a record from the DSS file
620+
Args:
621+
pathname (str): dss pathname, if only the pathname is provided, delete only the record with this pathname
622+
allRecords (bool): if True, delete all records with this pathname that have different dates
623+
startdatetime (datetime): start date for query, if only start date is provided, delete all records at or after this date
624+
enddatetime (datetime): end date for the query, if only end date is provided, delete all records at or before this date
625+
Returns:
626+
int: status of zero when successful. Non zero for errors.
627+
"""
628+
rt = self.get_record_type(pathname)
629+
delete_path = DssPath(pathname)
630+
if (rt == RecordType.RegularTimeSeries or rt == RecordType.IrregularTimeSeries) and allrecords:
631+
for i in self.get_catalog().uncondensed_paths:
632+
uncondensed_path = DssPath(i)
633+
if uncondensed_path.path_without_date().__eq__(delete_path.path_without_date()):
634+
status = self._native.hec_dss_delete(i)
635+
if status == 0:
636+
self._catalog = None
637+
elif rt == RecordType.RegularTimeSeries and (startdatetime or enddatetime):
638+
newStartDateTime, newEndDateTime = self._get_date_time_range(pathname, 1)
639+
if startdatetime:
640+
newStartDateTime = startdatetime
641+
if enddatetime:
642+
newEndDateTime = enddatetime
643+
644+
interval_seconds=DateConverter.intervalString_to_sec(delete_path.E)
645+
646+
firstMinutes = DateConverter.julian_array_from_date_times([newStartDateTime])[0]
647+
firstSeconds, firstJulian = [firstMinutes % 1440 * 60], [firstMinutes // 1440]
648+
649+
lastMinutes = DateConverter.julian_array_from_date_times([newEndDateTime])[0]
650+
lastSeconds, lastJulian = [lastMinutes % 1440 * 60], [lastMinutes // 1440]
651+
652+
number_periods = self._native.hec_dss_numberPeriods(
653+
interval_seconds,
654+
firstJulian[0],
655+
firstSeconds[0],
656+
lastJulian[0],
657+
lastSeconds[0],
658+
)
659+
660+
RTS = RegularTimeSeries()
661+
RTS.values = [DSS_UNDEFINED_VALUE]*number_periods
662+
RTS.times = [newStartDateTime, newEndDateTime]
663+
RTS.start_date = startdatetime
664+
RTS.id = pathname
665+
666+
startDate, startTime = DateConverter.dss_datetime_strings_from_datetime(RTS.times[0])
667+
668+
status = self._native.hec_dss_tsStoreRegular(
669+
RTS.id,
670+
startDate,
671+
startTime,
672+
RTS.values,
673+
RTS.quality,
674+
False,
675+
RTS.units,
676+
RTS.data_type,
677+
RTS.time_zone_name,
678+
3
679+
)
680+
self._catalog = None
681+
elif rt == RecordType.IrregularTimeSeries and (startdatetime or enddatetime):
682+
newStartDateTime, newEndDateTime = self._get_date_time_range(pathname, 1)
683+
if startdatetime:
684+
newStartDateTime = startdatetime
685+
if enddatetime:
686+
newEndDateTime = enddatetime
687+
688+
IRTS = IrregularTimeSeries()
689+
IRTS.values = [DSS_UNDEFINED_VALUE, DSS_UNDEFINED_VALUE]
690+
IRTS.times = [newStartDateTime, newEndDateTime]
691+
IRTS.start_date = startdatetime
692+
IRTS.id = pathname
693+
694+
dss.put(IRTS)
695+
else:
696+
status = self._native.hec_dss_delete(pathname)
697+
if status == 0:
698+
self._catalog = None
699+
else:
700+
print(f"Error deleting record from '{pathname}', Record does not exist or timeseries path must be uncondensed")
701+
return status
702+
603703
def get_catalog(self) -> Catalog:
604704
"""gets the DSS Catalog of all items in the DSS file
605705

src/hecdss/irregular_timeseries.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def __init__(self):
2121
self.start_date = ""
2222
self.time_granularity_seconds = 1
2323
self.julian_base_date = 0
24+
self.time_zone_name = ""
2425
self.id = ""
2526
self.location_info = None
2627

@@ -85,7 +86,7 @@ def print_to_console(self):
8586
for time, value in zip(self.times, self.values):
8687
print(f"Time: {time}, Value: {value}")
8788
@staticmethod
88-
def create(values, times, quality=[], units="", data_type="", interval=0, start_date="", time_granularity_seconds=1, julian_base_date=None, path=None, location_info=None):
89+
def create(values, times, quality=[], units="", data_type="", interval=0, start_date="", time_granularity_seconds=1, julian_base_date=None, time_zone_name="", path=None, location_info=None):
8990
"""
9091
Retrieve the value at a specific date in the time-series.
9192
@@ -107,6 +108,7 @@ def create(values, times, quality=[], units="", data_type="", interval=0, start_
107108
irts.julian_base_date = 0
108109
if julian_base_date is None and len(times):
109110
irts.julian_base_date = (times[0]-datetime(1900, 1, 1)).days
111+
irts.time_zone_name = time_zone_name
110112
irts.id = path
111113
irts.location_info = location_info
112114
return irts

0 commit comments

Comments
 (0)