Skip to content
Open
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
6 changes: 3 additions & 3 deletions docs/manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Connecting to the Peregrino IP.21 data source using AspenTech Process Data REST
``` python
c = tagreader.IMSClient(datasource="PER",
imstype="aspenone",
tz="Brazil/East",
tz="America/Sao_Paulo",
url="https://aspenone-per.equinor.com/ProcessExplorer/ProcessData/AtProcessDataREST.dll")
c.connect()
```
Expand Down Expand Up @@ -303,7 +303,7 @@ There are two levels of determining which time zone input arguments should be in

The client-provided time zone can be specified with the optional `tz` argument (string, e.g. "*US/Central*") during client creation. If it is not specified, then the default value *Europe/Oslo* is used. Note that for the most common use case where Equinor employees want to fetch data from Norwegian assets and display them with Norwegian time stamps, nothing needs to be done.

*Note:* It is a good idea to update the `pytz` package rather frequently (at least twice per year) to ensure that time zone information is up to date. `pip install --upgrade pytz` .
*Note:* Tagreader uses Python's built-in `zoneinfo` module. Keep your system time zone database up to date. On environments without an IANA database, install/update `tzdata` with `pip install --upgrade tzdata`.

**Example (advanced usage)**

Expand All @@ -315,7 +315,7 @@ from datetime import datetime, timedelta
from dateutil import tz
c = tagreader.IMSClient("PINO", "pi", tz="US/Central") # Force output data to Houston time
c.connect()
tzinfo = tz.gettz("Brazil/East") # Generate timezone object for Rio local time
tzinfo = tz.gettz("America/Sao_Paulo") # Generate timezone object for Rio local time
event_time = datetime(2020, 7, 20, 15, 5, 0, tzinfo=tzinfo)
start_time = event_time - timedelta(minutes=30)
end_time = event_time + timedelta(minutes=10)
Expand Down
2 changes: 1 addition & 1 deletion documentation/docs/about/usage/basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ stamps as well as the returned data shall use Rio local time, and using the loca
``` python
c = tagreader.IMSClient(datasource="PER",
imstype="aspenone",
tz="Brazil/East",
tz="America/Sao_Paulo",
url="https://aspenone-per.equinor.com/ProcessExplorer/ProcessData/AtProcessDataREST.dll")
c.connect()
```
Expand Down
6 changes: 3 additions & 3 deletions documentation/docs/about/usage/time-zone.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ client creation. If it is not specified, then the default value *Europe/Oslo* is
use case where Equinor employees want to fetch data from Norwegian assets and display them with Norwegian time stamps,
nothing needs to be done.

*Note:* It is a good idea to update the `pytz` package rather frequently (at least twice per year) to ensure that time
zone information is up-to-date. `pip install --upgrade pytz` .
*Note:* Tagreader uses Python's built-in `zoneinfo` module. Keep your system time zone database up to date. On
environments without an IANA database, install/update `tzdata` with `pip install --upgrade tzdata`.

**Example (advanced usage)**

Expand All @@ -36,7 +36,7 @@ from datetime import datetime, timedelta
from dateutil import tz
c = tagreader.IMSClient("PINO", "pi", tz="US/Central") # Force output data to Houston time
c.connect()
tzinfo = tz.gettz("Brazil/East") # Generate timezone object for Rio local time
tzinfo = tz.gettz("America/Sao_Paulo") # Generate timezone object for Rio local time
event_time = datetime(2020, 7, 20, 15, 5, 0, tzinfo=tzinfo)
start_time = event_time - timedelta(minutes=30)
end_time = event_time + timedelta(minutes=10)
Expand Down
1,494 changes: 1,019 additions & 475 deletions poetry.lock

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions tagreader/cache.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union, cast

import pandas as pd
import pytz
from diskcache import Cache

from tagreader.logger import logger
Expand All @@ -21,7 +20,7 @@ def safe_tagname(tagname: str) -> str:
def timestamp_to_epoch(timestamp: datetime) -> int:
origin = datetime(1970, 1, 1)
if timestamp.tzinfo is not None:
timestamp = timestamp.astimezone(pytz.utc).replace(tzinfo=None)
timestamp = timestamp.astimezone(timezone.utc).replace(tzinfo=None)
return (timestamp - origin) // timedelta(seconds=1)


Expand Down
16 changes: 9 additions & 7 deletions tagreader/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from itertools import groupby
from operator import itemgetter
from typing import Any, Dict, List, Optional, Tuple, Union
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

import numpy as np
import pandas as pd
import pytz
import requests

from tagreader.cache import BucketCache, SmartCache
Expand All @@ -26,7 +26,7 @@
list_piwebapi_sources,
)

NONE_START_TIME = datetime(1970, 1, 1, tzinfo=pytz.UTC)
NONE_START_TIME = datetime(1970, 1, 1, tzinfo=timezone.utc)


def list_sources(
Expand Down Expand Up @@ -193,7 +193,7 @@ def __init__(
self,
datasource: str,
imstype: Optional[Union[str, IMSType]] = None,
tz: Union[tzinfo, str] = pytz.timezone("Europe/Oslo"),
tz: Union[tzinfo, str] = ZoneInfo("Europe/Oslo"),
url: Optional[str] = None,
handler_options: Dict[str, Union[int, float, str]] = {}, # noqa:
verify_ssl: Optional[Union[bool, str]] = True,
Expand All @@ -210,10 +210,12 @@ def __init__(
)

if isinstance(tz, str):
if tz in pytz.all_timezones:
self.tz = pytz.timezone(tz)
else:
raise ValueError(f"Invalid timezone string Given type was {type(tz)}")
try:
self.tz = ZoneInfo(tz)
except ZoneInfoNotFoundError:
raise ValueError(
f"Invalid timezone string. Given type was {type(tz)}"
) from None
elif isinstance(tz, tzinfo):
self.tz = tz
else:
Expand Down
6 changes: 3 additions & 3 deletions tagreader/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
from enum import Enum
from pathlib import Path
from typing import Union
from zoneinfo import ZoneInfo

import certifi
import pandas as pd
import pytz
import requests
from platformdirs import user_data_dir

Expand Down Expand Up @@ -50,12 +50,12 @@ def convert_to_pydatetime(date_stamp: Union[datetime, str, pd.Timestamp]) -> dat

def ensure_datetime_with_tz(
date_stamp: Union[datetime, str, pd.Timestamp],
tz: tzinfo = pytz.timezone("Europe/Oslo"),
tz: tzinfo = ZoneInfo("Europe/Oslo"),
) -> datetime:
date_stamp = convert_to_pydatetime(date_stamp)

if not date_stamp.tzinfo:
date_stamp = tz.localize(date_stamp)
date_stamp = date_stamp.replace(tzinfo=tz)

return date_stamp

Expand Down
9 changes: 4 additions & 5 deletions tagreader/web_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
import re
import urllib.parse
from abc import ABC, abstractmethod
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from hashlib import new as hashlib_new_method
from json.decoder import JSONDecodeError
from typing import Any, Dict, List, Optional, Tuple, Union

import numpy as np
import pandas as pd
import pytz
import requests
import urllib3
from Crypto.Hash import MD4 as _MD4
Expand Down Expand Up @@ -437,7 +436,7 @@ def _get_maps(self, tagname: str):
return ret

def _get_default_mapname(self, tagname: str):
(tagname, _) = self.split_tagmap(tagname)
tagname, _ = self.split_tagmap(tagname)
all_maps = self._get_maps(tagname)
for k, v in all_maps.items():
if v:
Expand Down Expand Up @@ -729,7 +728,7 @@ def __init__(

@staticmethod
def _time_to_UTC_string(time: datetime) -> str:
return time.astimezone(pytz.UTC).strftime("%d-%b-%y %H:%M:%S")
return time.astimezone(timezone.utc).strftime("%d-%b-%y %H:%M:%S")

@staticmethod
def escape(s: str) -> str:
Expand Down Expand Up @@ -994,7 +993,7 @@ def read_tag(
if not web_id:
return pd.DataFrame()

(url, params) = self.generate_read_query(
url, params = self.generate_read_query(
tag=web_id,
start=start,
end=end,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_AspenHandlerREST_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
SOURCE = "TRB"
TAG = "AverageCPUTimeVals"
FAKE_TAG = "so_random_it_cant_exist"
START_TIME = datetime(2023, 5, 1, 10, 0, 0)
STOP_TIME = datetime(2023, 5, 1, 11, 0, 0)
START_TIME = datetime(2025, 5, 1, 10, 0, 0)
STOP_TIME = datetime(2025, 5, 1, 11, 0, 0)
SAMPLE_TIME = timedelta(seconds=60)


Expand Down
6 changes: 3 additions & 3 deletions tests/test_PIHandlerREST.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def test_generate_read_query(pi_handler: PIHandlerWeb, read_type: str) -> None:
stop = ensure_datetime_with_tz(STOP_TIME)
ts = timedelta(seconds=SAMPLE_TIME)

(url, params) = pi_handler.generate_read_query(
url, params = pi_handler.generate_read_query(
tag=pi_handler.tag_to_web_id(tag="alreadyknowntag"), # type: ignore[arg-type]
start=start,
end=stop,
Expand Down Expand Up @@ -176,7 +176,7 @@ def test_generate_read_query_with_status(
stop = ensure_datetime_with_tz(STOP_TIME)
ts = timedelta(seconds=SAMPLE_TIME)

(url, params) = pi_handler.generate_read_query(
url, params = pi_handler.generate_read_query(
tag=pi_handler.tag_to_web_id("alreadyknowntag"), # type: ignore[arg-type]
start=start,
end=stop,
Expand Down Expand Up @@ -234,7 +234,7 @@ def test_generate_read_query_long_sample_time(pi_handler: PIHandlerWeb) -> None:
stop = ensure_datetime_with_tz(STOP_TIME)
ts = timedelta(seconds=86410)

(url, params) = pi_handler.generate_read_query(
url, params = pi_handler.generate_read_query(
tag=pi_handler.tag_to_web_id("alreadyknowntag"), # type: ignore[arg-type]
start=start,
end=stop,
Expand Down
4 changes: 2 additions & 2 deletions tests/test_bucketcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,9 +541,9 @@ def test_store_overlapping_df(cache: BucketCache) -> None:
)
df_expected = pd.concat(
[
DF1[START_TIME_1 : START_TIME_3 - pd.Timedelta(TS, unit="s")],
DF1[START_TIME_1 : START_TIME_3 - TS],
DF3[START_TIME_3:END_TIME_3],
DF2[END_TIME_3 + pd.Timedelta(TS, unit="s") : END_TIME_2],
DF2[END_TIME_3 + TS : END_TIME_2],
]
)

Expand Down
20 changes: 10 additions & 10 deletions tests/test_clients.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

import pandas as pd
import pytest
import pytz

from tagreader.clients import IMSClient, get_missing_intervals, get_next_timeslice
from tagreader.utils import IMSType, ReaderType
Expand All @@ -21,32 +21,32 @@ def test_init_client_with_tzinfo() -> None:
datasource="mock", imstype=IMSType.PIWEBAPI, cache=None, tz="US/Eastern"
)
print(client.tz)
assert client.tz == pytz.timezone("US/Eastern")
assert client.tz == ZoneInfo("US/Eastern")

client = IMSClient(
datasource="mock",
imstype=IMSType.PIWEBAPI,
cache=None,
tz=pytz.timezone("US/Eastern"),
tz=ZoneInfo("US/Eastern"),
)
print(client.tz)
assert client.tz == pytz.timezone("US/Eastern")
assert client.tz == ZoneInfo("US/Eastern")

client = IMSClient(
datasource="mock", imstype=IMSType.PIWEBAPI, cache=None, tz="Europe/Oslo"
)
print(client.tz)
assert client.tz == pytz.timezone("Europe/Oslo")
assert client.tz == ZoneInfo("Europe/Oslo")

client = IMSClient(
datasource="mock", imstype=IMSType.PIWEBAPI, cache=None, tz="US/Central"
)
print(client.tz)
assert client.tz == pytz.timezone("US/Central")
assert client.tz == ZoneInfo("US/Central")

client = IMSClient(datasource="mock", imstype=IMSType.PIWEBAPI, cache=None)
print(client.tz)
assert client.tz == pytz.timezone("Europe/Oslo")
assert client.tz == ZoneInfo("Europe/Oslo")

with pytest.raises(ValueError):
_ = IMSClient(
Expand All @@ -63,15 +63,15 @@ def test_init_client_with_datasource() -> None:
datasource="mock", imstype=IMSType.PIWEBAPI, cache=None, tz="US/Eastern"
)
print(client.tz)
assert client.tz == pytz.timezone("US/Eastern")
assert client.tz == ZoneInfo("US/Eastern")
client = IMSClient(
datasource="mock", imstype=IMSType.PIWEBAPI, cache=None, tz="US/Central"
)
print(client.tz)
assert client.tz == pytz.timezone("US/Central")
assert client.tz == ZoneInfo("US/Central")
client = IMSClient(datasource="mock", imstype=IMSType.PIWEBAPI, cache=None)
print(client.tz)
assert client.tz == pytz.timezone("Europe/Oslo")
assert client.tz == ZoneInfo("Europe/Oslo")
with pytest.raises(ValueError):
_ = IMSClient(
datasource="mock", imstype=IMSType.PIWEBAPI, cache=None, tz="WRONGVALUE"
Expand Down
37 changes: 20 additions & 17 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import datetime
import os
from zoneinfo import ZoneInfo

import pandas as pd
import pytz
from pytz import timezone

from tagreader.utils import ensure_datetime_with_tz, is_equinor, urljoin

Expand All @@ -12,37 +11,41 @@


def test_ensure_is_datetime_string() -> None:
assert ensure_datetime_with_tz("10. jan. 2018 13:45:15") == timezone(
"Europe/Oslo"
).localize(datetime.datetime(2018, 1, 10, 13, 45, 15))
assert ensure_datetime_with_tz("01.02.03 00:00:00") == timezone(
"Europe/Oslo"
).localize(datetime.datetime(2003, 2, 1, 0, 0, 0))
assert ensure_datetime_with_tz("10. jan. 2018 13:45:15") == datetime.datetime(
2018, 1, 10, 13, 45, 15, tzinfo=ZoneInfo("Europe/Oslo")
)
assert ensure_datetime_with_tz("01.02.03 00:00:00") == datetime.datetime(
2003, 2, 1, 0, 0, 0, tzinfo=ZoneInfo("Europe/Oslo")
)
assert ensure_datetime_with_tz("02.01.03 00:00:00") == ensure_datetime_with_tz(
"2003-02-01 0:00:00am"
)
assert ensure_datetime_with_tz(
"02.01.03 00:00:00", pytz.timezone("America/Sao_Paulo")
) == timezone("America/Sao_Paulo").localize(datetime.datetime(2003, 1, 2, 0, 0, 0))
"02.01.03 00:00:00", ZoneInfo("America/Sao_Paulo")
) == datetime.datetime(2003, 1, 2, 0, 0, 0, tzinfo=ZoneInfo("America/Sao_Paulo"))
assert ensure_datetime_with_tz(
"02.01.03 00:00:00", pytz.timezone("Brazil/East")
) == timezone("Brazil/East").localize(datetime.datetime(2003, 1, 2, 0, 0, 0))
"02.01.03 00:00:00", ZoneInfo("America/Sao_Paulo")
) == datetime.datetime(2003, 1, 2, 0, 0, 0, tzinfo=ZoneInfo("America/Sao_Paulo"))
assert ensure_datetime_with_tz(
timezone("Brazil/East").localize(datetime.datetime(2003, 1, 2, 0, 0, 0)),
pytz.timezone("Brazil/East"),
) == timezone("Brazil/East").localize(datetime.datetime(2003, 1, 2, 0, 0, 0))
datetime.datetime(2003, 1, 2, 0, 0, 0, tzinfo=ZoneInfo("America/Sao_Paulo")),
ZoneInfo("America/Sao_Paulo"),
) == datetime.datetime(2003, 1, 2, 0, 0, 0, tzinfo=ZoneInfo("America/Sao_Paulo"))


def test_ensure_is_datetime_pd_timestamp() -> None:
ts = datetime.datetime(2018, 1, 10, 13, 45, 15)
ts_with_tz = timezone("Europe/Oslo").localize(ts)
ts_with_tz = datetime.datetime(
2018, 1, 10, 13, 45, 15, tzinfo=ZoneInfo("Europe/Oslo")
)
assert ensure_datetime_with_tz(ts_with_tz) == ts_with_tz
assert ensure_datetime_with_tz(ts) == ts_with_tz


def test_ensure_is_datetime_datetime() -> None:
dt = datetime.datetime(2018, 1, 10, 13, 45, 15)
dt_with_tz = timezone("Europe/Oslo").localize(dt)
dt_with_tz = datetime.datetime(
2018, 1, 10, 13, 45, 15, tzinfo=ZoneInfo("Europe/Oslo")
)

assert ensure_datetime_with_tz(dt_with_tz) == dt_with_tz
assert ensure_datetime_with_tz(dt) == dt_with_tz
Expand Down
Loading