Skip to content

Commit

Permalink
fix: Expr.iwd.isoweek_to_datetime in with_columns context (#84)
Browse files Browse the repository at this point in the history
* fix: isoweek_to_datetime in expr

* polars exception
  • Loading branch information
FBruzzesi authored Sep 15, 2024
1 parent 3316dd7 commit 9fc078a
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 73 deletions.
54 changes: 28 additions & 26 deletions iso_week_date/pandas_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@

from iso_week_date._patterns import (
ISOWEEK__DATE_FORMAT,
ISOWEEK__FORMAT,
ISOWEEK_PATTERN,
ISOWEEKDATE__DATE_FORMAT,
ISOWEEKDATE__FORMAT,
ISOWEEKDATE_PATTERN,
)
from iso_week_date._utils import parse_version
Expand Down Expand Up @@ -142,6 +140,8 @@ def isoweek_to_datetime(
series: pd.Series,
offset: OffsetType = 0,
weekday: int = 1,
*,
strict: bool = True,
) -> pd.Series:
"""Converts series of `str` values in ISO Week format to a series of `datetime` values.
Expand All @@ -151,10 +151,11 @@ def isoweek_to_datetime(
week, 7 is the last one.
Arguments:
series: series of `str` values in ISO Week format
offset: offset in days or pd.Timedelta. It represents how many days to add to the date before converting to
IsoWeek, it can be negative
weekday: weekday to use for conversion (1-7)
series: Series of `str` values in ISO Week format.
offset: Offset in days or pd.Timedelta. It represents how many days to add to the date before converting to
IsoWeek, it can be negative.
weekday: Weekday to use for conversion (1-7).
strict: Raise an error if the values cannot be converted to datetime. Otherwise mask out with a null value.
Returns:
Series of converted datetime values
Expand Down Expand Up @@ -182,10 +183,6 @@ def isoweek_to_datetime(
'''
```
"""
if not is_isoweek_series(series):
msg = f"`series` values must match ISO Week date format {ISOWEEK__FORMAT}"
raise ValueError(msg)

if not isinstance(offset, (pd.Timedelta, int)):
msg = f"`offset` must be of type `pd.Timedelta` or `int`, found {type(offset)}"
raise TypeError(msg)
Expand All @@ -195,21 +192,25 @@ def isoweek_to_datetime(
raise ValueError(msg)

_offset = pd.Timedelta(days=offset) if isinstance(offset, int) else offset
return pd.to_datetime(series + "-" + f"{weekday}", format=ISOWEEKDATE__DATE_FORMAT) + _offset
errors = "raise" if strict else "coerce"
return pd.to_datetime(series + "-" + f"{weekday}", errors=errors, format=ISOWEEKDATE__DATE_FORMAT) + _offset


def isoweekdate_to_datetime(
series: pd.Series,
offset: OffsetType = 0,
*,
strict: bool = True,
) -> pd.Series:
"""Converts series of `str` values in ISO Week date format to a series of `datetime` values.
`offset` represents how many days to add to the date before converting to datetime and it can be negative.
Arguments:
series: series of `str` in ISO Week date format
series: series of `str` in ISO Week date format.
offset: offset in days or pd.Timedelta. It represents how many days to add to the date before converting to
IsoWeek, it can be negative
IsoWeek, it can be negative.
strict: Raise an error if the values cannot be converted to datetime. Otherwise mask out with a null value.
Returns:
Series of converted datetime values
Expand All @@ -235,16 +236,13 @@ def isoweekdate_to_datetime(
'''
```
"""
if not is_isoweekdate_series(series):
msg = f"`series` values must match ISO Week date format {ISOWEEKDATE__FORMAT}"
raise ValueError(msg)

if not isinstance(offset, (pd.Timedelta, int)):
msg = f"`offset` must be of type `pd.Timedelta` or `int`, found {type(offset)}"
raise TypeError(msg)

_offset = pd.Timedelta(days=offset) if isinstance(offset, int) else offset
return pd.to_datetime(series, format=ISOWEEKDATE__DATE_FORMAT) + _offset
errors = "raise" if strict else "coerce"
return pd.to_datetime(series, errors=errors, format=ISOWEEKDATE__DATE_FORMAT) + _offset


def _match_series(series: pd.Series, pattern: str) -> bool:
Expand Down Expand Up @@ -405,6 +403,8 @@ def isoweek_to_datetime(
self: Self,
offset: OffsetType = 0,
weekday: int = 1,
*,
strict: bool = True,
) -> pd.Series:
"""Converts series of `str` values in ISO Week format to a series of `datetime` values.
Expand All @@ -414,9 +414,10 @@ def isoweek_to_datetime(
week, 7 is the last one.
Arguments:
offset: offset in days or pd.Timedelta. It represents how many days to add to the date before converting to
IsoWeek, it can be negative
weekday: weekday to use for conversion (1-7)
offset: Offset in days or pd.Timedelta. It represents how many days to add to the date before converting to
IsoWeek, it can be negative.
weekday: Weekday to use for conversion (1-7).
strict: Raise an error if the values cannot be converted to datetime. Otherwise mask out with a null value.
Returns:
Series of converted datetime values
Expand All @@ -440,16 +441,17 @@ def isoweek_to_datetime(
'''
```
"""
return isoweek_to_datetime(self._series, offset=offset, weekday=weekday)
return isoweek_to_datetime(self._series, offset=offset, weekday=weekday, strict=strict)

def isoweekdate_to_datetime(self: Self, offset: OffsetType = 0) -> pd.Series:
def isoweekdate_to_datetime(self: Self, offset: OffsetType = 0, *, strict: bool = True) -> pd.Series:
"""Converts series of `str` values in ISO Week date format to a series of `datetime` values.
`offset` represents how many days to add to the date before converting to datetime and it can be negative.
Arguments:
offset: offset in days or pd.Timedelta. It represents how many days to add to the date before converting to
IsoWeek, it can be negative
offset: Offset in days or pd.Timedelta. It represents how many days to add to the date before converting to
IsoWeek, it can be negative.
strict: Raise an error if the values cannot be converted to datetime. Otherwise mask out with a null value.
Returns:
Series of converted datetime values
Expand All @@ -473,7 +475,7 @@ def isoweekdate_to_datetime(self: Self, offset: OffsetType = 0) -> pd.Series:
'''
```
"""
return isoweekdate_to_datetime(self._series, offset=offset)
return isoweekdate_to_datetime(self._series, offset=offset, strict=strict)

def is_isoweek(self: Self) -> bool:
"""Checks if series contains only values in ISO Week format.
Expand Down
60 changes: 32 additions & 28 deletions iso_week_date/polars_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@

from iso_week_date._patterns import (
ISOWEEK__DATE_FORMAT,
ISOWEEK__FORMAT,
ISOWEEK_PATTERN,
ISOWEEKDATE__DATE_FORMAT,
ISOWEEKDATE__FORMAT,
ISOWEEKDATE_PATTERN,
)
from iso_week_date._utils import parse_version
Expand Down Expand Up @@ -142,6 +140,8 @@ def isoweek_to_datetime(
series: T,
offset: OffsetType = timedelta(days=0),
weekday: int = 1,
*,
strict: bool = True,
) -> T:
"""Converts series or expr of `str` values in ISO Week format YYYY-WNN to a series or expr of `pl.Date` values.
Expand All @@ -151,10 +151,11 @@ def isoweek_to_datetime(
week, 7 is the last one.
Arguments:
series: series or expr of `str` values in ISO Week format
offset: offset in days or `timedelta`. It represents how many days to add to the date before converting to
IsoWeek, it can be negative
weekday: weekday to use for conversion (1-7)
series: Series or Expr of `str` values in ISO Week format.
offset: Offset in days or `timedelta`. It represents how many days to add to the date before converting to
IsoWeek, it can be negative.
weekday: Weekday to use for conversion (1-7)
strict: Raise an error if the values cannot be converted to datetime. Otherwise mask out with a null value.
Returns:
Series or Expr of converted date values
Expand Down Expand Up @@ -183,10 +184,6 @@ def isoweek_to_datetime(
'''
```
"""
if not is_isoweek_series(series):
msg = f"`series` values must match ISO Week format {ISOWEEK__FORMAT}"
raise ValueError(msg)

if not isinstance(offset, (timedelta, int)):
msg = f"`offset` must be of type `timedelta` or `int`, found {type(offset)}"
raise TypeError(msg)
Expand All @@ -197,21 +194,24 @@ def isoweek_to_datetime(

_offset = timedelta(days=offset) if isinstance(offset, int) else offset

return (series + f"-{weekday}").str.strptime(pl.Date, ISOWEEKDATE__DATE_FORMAT) + _offset
return (series + f"-{weekday}").str.strptime(pl.Date, ISOWEEKDATE__DATE_FORMAT, strict=strict) + _offset


def isoweekdate_to_datetime(
series: T,
offset: OffsetType = timedelta(days=0),
*,
strict: bool = True,
) -> T:
"""Converts `series/expr` of values in ISO Week date format YYYY-WNN-D to a series or expr of `pl.Date` values.
`offset` represents how many days to add to the date before converting to `pl.Date`, and it can be negative.
Arguments:
series: series or expr of `str` values in ISO Week date format
offset: offset in days or `timedelta`. It represents how many days to add to the date before converting to
series: Series or Expr of `str` values in ISO Week date format
offset: Offset in days or `timedelta`. It represents how many days to add to the date before converting to
IsoWeek, it can be negative
strict: Raise an error if the values cannot be converted to datetime. Otherwise mask out with a null value.
Returns:
Series or Expr of converted date values
Expand Down Expand Up @@ -239,17 +239,13 @@ def isoweekdate_to_datetime(
'''
```
"""
if not is_isoweekdate_series(series):
msg = f"`series` values must match ISO Week date format {ISOWEEKDATE__FORMAT}"
raise ValueError(msg)

if not isinstance(offset, (timedelta, int)):
msg = f"`offset` must be of type `timedelta` or `int`, found {type(offset)}"
raise TypeError(msg)

_offset = timedelta(days=offset) if isinstance(offset, int) else offset

return series.str.strptime(pl.Date, ISOWEEKDATE__DATE_FORMAT) + _offset
return series.str.strptime(pl.Date, ISOWEEKDATE__DATE_FORMAT, strict=strict) + _offset


def _match_series(series: T, pattern: str) -> bool:
Expand All @@ -270,7 +266,7 @@ def _match_series(series: T, pattern: str) -> bool:
raise TypeError(msg)

try:
return series.str.extract(pattern).is_not_null().all() # type: ignore[return-value]
return series.str.contains(rf"^{pattern}$").all() # type: ignore[return-value]
except Exception: # noqa: BLE001
return False

Expand Down Expand Up @@ -413,7 +409,13 @@ def datetime_to_isoweekdate(self: Self, offset: OffsetType = timedelta(0)) -> T:
"""
return datetime_to_isoweekdate(self._series, offset=offset)

def isoweek_to_datetime(self: Self, offset: OffsetType = timedelta(0), weekday: int = 1) -> T:
def isoweek_to_datetime(
self: Self,
offset: OffsetType = timedelta(0),
weekday: int = 1,
*,
strict: bool = True,
) -> T:
"""Converts series or expr of `str` values in ISO Week format YYYY-WNN to a series or expr of `pl.Date` values.
`offset` represents how many days to add to the date before converting to `pl.Date`, and it can be negative.
Expand All @@ -422,9 +424,10 @@ def isoweek_to_datetime(self: Self, offset: OffsetType = timedelta(0), weekday:
week, 7 is the last one.
Arguments:
offset: offset in days or `timedelta`. It represents how many days to add to the date before converting to
IsoWeek, it can be negative
weekday: weekday to use for conversion (1-7)
offset: Offset in days or `timedelta`. It represents how many days to add to the date before converting to
IsoWeek, it can be negative.
weekday: Weekday to use for conversion (1-7).
strict: Raise an error if the values cannot be converted to datetime. Otherwise mask out with a null value.
Returns:
Series or Expr of converted date values
Expand All @@ -450,16 +453,17 @@ def isoweek_to_datetime(self: Self, offset: OffsetType = timedelta(0), weekday:
'''
```
"""
return isoweek_to_datetime(self._series, offset=offset, weekday=weekday)
return isoweek_to_datetime(self._series, offset=offset, weekday=weekday, strict=strict)

def isoweekdate_to_datetime(self: Self, offset: OffsetType = timedelta(0)) -> T:
def isoweekdate_to_datetime(self: Self, offset: OffsetType = timedelta(0), *, strict: bool = True) -> T:
"""Converts `str` series or expr of ISO Week date format YYYY-WNN-D to a series or expr of `pl.Date` values.
`offset` represents how many days to add to the date before converting to `pl.Date`, and it can be negative.
Arguments:
offset: offset in days or `timedelta`. It represents how many days to add to the date before converting to
IsoWeek, it can be negative
offset: Offset in days or `timedelta`. It represents how many days to add to the date before converting to
IsoWeek, it can be negative.
strict: Raise an error if the values cannot be converted to datetime. Otherwise mask out with a null value.
Returns:
Series or Expr of converted date values
Expand All @@ -484,7 +488,7 @@ def isoweekdate_to_datetime(self: Self, offset: OffsetType = timedelta(0)) -> T:
'''
```
"""
return isoweekdate_to_datetime(self._series, offset=offset)
return isoweekdate_to_datetime(self._series, offset=offset, strict=strict)

def is_isoweek(self: Self) -> bool:
"""Checks if a series or expr contains only values in ISO Week format.
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "iso-week-date"
version = "1.3.0"
version = "1.4.0"
description = "Toolkit to work with str representing ISO Week date format"

license = {file = "LICENSE"}
Expand Down Expand Up @@ -93,7 +93,8 @@ ignore = [
"ISC001", # Checks for implicitly concatenated strings on a single line.
"RET505", # Checks for else statements with a return statement in the preceding if block.
"TRY003", # Checks for long exception messages that are not defined in the exception class itself.
"DTZ"
"DTZ",
"COM812",
]

[tool.ruff.lint.per-file-ignores]
Expand Down
9 changes: 2 additions & 7 deletions tests/pandas_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ class CustomWeekDate(IsoWeekDate):
@pytest.mark.parametrize(
"kwargs, context",
[
({"series": pd.DataFrame()}, pytest.raises(TypeError, match="`series` must be of type `pd.Series`")),
(
{"series": pd.Series(["2023-W01", "2023-W02"]), "offset": "abc"},
pytest.raises(TypeError, match="`offset` must be of type `pd.Timedelta` or `int`"),
Expand All @@ -155,7 +154,7 @@ class CustomWeekDate(IsoWeekDate):
),
(
{"series": pd.Series(["2023-Wab", "2023-W02"]), "weekday": 1},
pytest.raises(ValueError, match="`series` values must match ISO Week date format"),
pytest.raises(ValueError, match='time data "2023-Wab-1" doesn\'t match format'),
),
],
)
Expand All @@ -168,13 +167,9 @@ def test_isoweek_to_datetime_raise(kwargs, context):
@pytest.mark.parametrize(
"kwargs, context",
[
(
{"series": pd.DataFrame()},
pytest.raises(TypeError, match="`series` must be of type `pd.Series`"),
),
(
{"series": pd.Series(["2023-W01-a", "2023-W02-b"]), "offset": 1},
pytest.raises(ValueError, match="`series` values must match ISO Week date format"),
pytest.raises(ValueError, match='time data "2023-W01-a" doesn\'t match format'),
),
(
{"series": pd.Series(["2023-W01-1", "2023-W02-1"]), "offset": "abc"},
Expand Down
Loading

0 comments on commit 9fc078a

Please sign in to comment.