Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented DATEDIFF function #262

Merged
merged 6 commits into from
Feb 15, 2025
Merged
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
32 changes: 32 additions & 0 deletions documentation/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Below is the list of every function/operator currently supported in PyDough as a
* [HOUR](#hour)
* [MINUTE](#minute)
* [SECOND](#second)
* [DATEDIFF] (#datediff)
- [Conditional Functions](#conditional-functions)
* [IFF](#iff)
* [ISIN](#isin)
Expand Down Expand Up @@ -290,6 +291,37 @@ is from 0-59:
Orders(is_lt_30_seconds = SECOND(order_date) < 30)
```

<!-- TOC --><a name="datediff"></a>
### DATEDIFF

Calling `DATEDIFF` between 2 timestamps returns the difference in one of `years`, `months`,`days`,`hours`,`minutes` or`seconds`.

- `DATEDIFF("years", x, y)`: Returns the **number of full years since x that y occurred**. For example, if **x** is December 31, 2009, and **y** is January 1, 2010, it counts as **1 year apart**, even though they are only 1 day apart.
- `DATEDIFF("months", x, y)`: Returns the **number of full months since x that y occurred**. For example, if **x** is January 31, 2014, and **y** is February 1, 2014, it counts as **1 month apart**, even though they are only 1 day apart.
- `DATEDIFF("days", x, y)`: Returns the **number of full days since x that y occurred**. For example, if **x** is 11:59 PM on one day, and **y** is 12:01 AM the next day, it counts as **1 day apart**, even though they are only 2 minutes apart.
- `DATEDIFF("hours", x, y)`: Returns the **number of full hours since x that y occurred**. For example, if **x** is 6:59 PM and **y** is 7:01 PM on the same day, it counts as **1 hour apart**, even though the difference is only 2 minutes.
- `DATEDIFF("minutes", x, y)`: Returns the **number of full minutes since x that y occurred**. For example, if **x** is 7:00 PM and **y** is 7:01 PM, it counts as **1 minute apart**, even though the difference is exactly 60 seconds.
- `DATEDIFF("seconds", x, y)`: Returns the **number of full seconds since x that y occurred**. For example, if **x** is at 7:00:01 PM and **y** is at 7:00:02 PM, it counts as **1 second apart**.

```py
# Calculates, for each order, the number of days since January 1st 1992
# that the order was placed:
orders(
days_since=DATEDIFF("days",datetime.date(1992, 1, 1), order_date)
)
```

The first argument in the `DATEDIFF` function supports the following aliases for each unit of time. The argument is **case-insensitive**, and if a unit is not one of the provided options, an error will be thrown:

- **Years**: Supported aliases are `"years"`, `"year"`, and `"y"`.
- **Months**: Supported aliases are `"months"`, `"month"`, and `"mm"`.
- **Days**: Supported aliases are `"days"`, `"day"`, and `"d"`.
- **Hours**: Supported aliases are `"hours"`, `"hour"`, and `"h"`.
- **Minutes**: Supported aliases are `"minutes"`, `"minute"`, and `"m"`.
- **Seconds**: Supported aliases are `"seconds"`, `"second"`, and `"s"`.

Invalid or unrecognized units will result in an error. For example, `"Days"`, `"DAYS"`, and `"d"` are all treated the same due to case insensitivity.

<!-- TOC --><a name="conditional-functions"></a>
## Conditional Functions

Expand Down
2 changes: 2 additions & 0 deletions pydough/pydough_operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"CONTAINS",
"COUNT",
"ConstantType",
"DATEDIFF",
"DAY",
"DEFAULT_TO",
"DIV",
Expand Down Expand Up @@ -83,6 +84,7 @@
BXR,
CONTAINS,
COUNT,
DATEDIFF,
DAY,
DEFAULT_TO,
DIV,
Expand Down
7 changes: 7 additions & 0 deletions pydough/pydough_operators/expression_operators/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ These functions must be called on singular data as a function.
- `HOUR`: Returns the hour component of a datetime.
- `MINUTE`: Returns the minute component of a datetime.
- `SECOND`: Returns the second component of a datetime.
- `DATEDIFF("unit",x,y)`: Returns the difference between two dates (y-x) in one of
- **Years**: `"years"`, `"year"`, `"y"`
- **Months**: `"months"`, `"month"`, `"mm"`
- **Days**: `"days"`, `"day"`, `"d"`
- **Hours**: `"hours"`, `"hour"`, `"h"`
- **Minutes**: `"minutes"`, `"minute"`, `"m"`
- **Seconds**: `"seconds"`, `"second"`, `"s"`.

##### Conditional Functions

Expand Down
2 changes: 2 additions & 0 deletions pydough/pydough_operators/expression_operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"BinaryOperator",
"CONTAINS",
"COUNT",
"DATEDIFF",
"DAY",
"DEFAULT_TO",
"DIV",
Expand Down Expand Up @@ -77,6 +78,7 @@
BXR,
CONTAINS,
COUNT,
DATEDIFF,
DAY,
DEFAULT_TO,
DIV,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Definition bindings of builtin PyDough operators that reutrn an expression.
Definition bindings of builtin PyDough operators that return an expression.
"""

__all__ = [
Expand All @@ -12,6 +12,7 @@
"BXR",
"CONTAINS",
"COUNT",
"DATEDIFF",
"DAY",
"DEFAULT_TO",
"DIV",
Expand Down Expand Up @@ -149,6 +150,9 @@
SECOND = ExpressionFunctionOperator(
"SECOND", False, RequireNumArgs(1), ConstantType(Int64Type())
)
DATEDIFF = ExpressionFunctionOperator(
"DATEDIFF", False, RequireNumArgs(3), ConstantType(Int64Type())
)
SLICE = ExpressionFunctionOperator(
"SLICE", False, RequireNumArgs(4), SelectArgumentType(0)
)
Expand Down
37 changes: 37 additions & 0 deletions pydough/pydough_operators/type_inference/type_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,40 @@ def accepts(self, args: list[Any], error_on_fail: bool = True) -> bool:
)
return False
return True


class RequireArgRange(TypeVerifier):
"""
Type verifier implementation class that requires the
number of arguments to be within a range, both ends inclusive.
"""

def __init__(self, low_range: int, high_range: int):
self._low_range: int = low_range
self._high_range: int = high_range

@property
def low_range(self) -> int:
"""
The lower end of the range.
"""
return self._low_range

@property
def high_range(self) -> int:
"""
The higher end of the range.
"""
return self._high_range

def accepts(self, args: list[Any], error_on_fail: bool = True) -> bool:
from pydough.qdag.errors import PyDoughQDAGException

if not (self.low_range <= len(args) <= self.high_range):
if error_on_fail:
raise PyDoughQDAGException(
f"Expected between {self.low_range} and {self.high_range} arguments,\
received {len(args)}"
)
return False
return True
Loading