From 787e47c7f2c06c02d819c48943e704b7a6b44ec9 Mon Sep 17 00:00:00 2001 From: jialuo Date: Tue, 2 Dec 2025 23:23:45 +0000 Subject: [PATCH 1/3] chore: Migrate DatetimeToIntegerLabelOp operator to SQLGlot --- .../sqlglot/expressions/datetime_ops.py | 266 ++++++++++++++++++ .../test_datetime_to_integer_label/out.sql | 84 ++++++ .../sqlglot/expressions/test_datetime_ops.py | 27 ++ 3 files changed, 377 insertions(+) create mode 100644 tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql diff --git a/bigframes/core/compile/sqlglot/expressions/datetime_ops.py b/bigframes/core/compile/sqlglot/expressions/datetime_ops.py index 949b122a1d..d5ef5e4731 100644 --- a/bigframes/core/compile/sqlglot/expressions/datetime_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/datetime_ops.py @@ -21,6 +21,272 @@ import bigframes.core.compile.sqlglot.scalar_compiler as scalar_compiler register_unary_op = scalar_compiler.scalar_op_compiler.register_unary_op +register_binary_op = scalar_compiler.scalar_op_compiler.register_binary_op + + +def _calculate_resample_first(y: TypedExpr, origin: str) -> sge.Expression: + if origin == "epoch": + return sge.convert(0) + elif origin == "start_day": + return sge.func( + "UNIX_MICROS", + sge.Cast( + this=sge.Cast( + this=y.expr, to=sge.DataType(this=sge.DataType.Type.DATE) + ), + to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ), + ), + ) + elif origin == "start": + return sge.func( + "UNIX_MICROS", + sge.Cast(this=y.expr, to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ)), + ) + else: + raise ValueError(f"Origin {origin} not supported") + + +@register_binary_op(ops.DatetimeToIntegerLabelOp, pass_op=True) +def datetime_to_integer_label_op( + x: TypedExpr, y: TypedExpr, op: ops.DatetimeToIntegerLabelOp +) -> sge.Expression: + # Determine if the frequency is fixed by checking if 'op.freq.nanos' is defined. + try: + return _datetime_to_integer_label_fixed_frequency(x, y, op) + except ValueError: + return _datetime_to_integer_label_non_fixed_frequency(x, y, op) + + +def _datetime_to_integer_label_fixed_frequency( + x: TypedExpr, y: TypedExpr, op: ops.DatetimeToIntegerLabelOp +) -> sge.Expression: + """ + This function handles fixed frequency conversions where the unit can range + from microseconds (us) to days. + """ + us = op.freq.nanos / 1000 + x_int = sge.func( + "UNIX_MICROS", + sge.Cast(this=x.expr, to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ)), + ) + first = _calculate_resample_first(y, op.origin) + x_int_label = sge.Cast( + this=sge.Floor( + this=sge.func( + "IEEE_DIVIDE", + sge.Sub(this=x_int, expression=first), + sge.convert(int(us)), + ) + ), + to=sge.DataType.build("INT64"), + ) + return x_int_label + + +def _datetime_to_integer_label_non_fixed_frequency( + x: TypedExpr, y: TypedExpr, op: ops.DatetimeToIntegerLabelOp +) -> sge.Expression: + """ + This function handles non-fixed frequency conversions for units ranging + from weeks to years. + """ + rule_code = op.freq.rule_code + n = op.freq.n + if rule_code == "W-SUN": # Weekly + us = n * 7 * 24 * 60 * 60 * 1000000 + x_trunc = sge.TimestampTrunc(this=x.expr, unit=sge.Var(this="WEEK(MONDAY)")) + y_trunc = sge.TimestampTrunc(this=y.expr, unit=sge.Var(this="WEEK(MONDAY)")) + x_plus_6 = sge.Add( + this=x_trunc, + expression=sge.Interval( + this=sge.convert(6), unit=sge.Identifier(this="DAY") + ), + ) + y_plus_6 = sge.Add( + this=y_trunc, + expression=sge.Interval( + this=sge.convert(6), unit=sge.Identifier(this="DAY") + ), + ) + x_int = sge.func( + "UNIX_MICROS", + sge.Cast( + this=x_plus_6, to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ) + ), + ) + first = sge.func( + "UNIX_MICROS", + sge.Cast( + this=y_plus_6, to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ) + ), + ) + return sge.Case( + ifs=[ + sge.If( + this=sge.EQ(this=x_int, expression=first), + true=sge.convert(0), + ) + ], + default=sge.Add( + this=sge.Cast( + this=sge.Floor( + this=sge.func( + "IEEE_DIVIDE", + sge.Sub( + this=sge.Sub(this=x_int, expression=first), + expression=sge.convert(1), + ), + sge.convert(us), + ) + ), + to=sge.DataType.build("INT64"), + ), + expression=sge.convert(1), + ), + ) + elif rule_code == "ME": # Monthly + x_int = sge.Paren( + this=sge.Add( + this=sge.Mul( + this=sge.Extract( + this=sge.Identifier(this="YEAR"), expression=x.expr + ), + expression=sge.convert(12), + ), + expression=sge.Sub( + this=sge.Extract( + this=sge.Identifier(this="MONTH"), expression=x.expr + ), + expression=sge.convert(1), + ), + ) + ) + first = sge.Paren( + this=sge.Add( + this=sge.Mul( + this=sge.Extract( + this=sge.Identifier(this="YEAR"), expression=y.expr + ), + expression=sge.convert(12), + ), + expression=sge.Sub( + this=sge.Extract( + this=sge.Identifier(this="MONTH"), expression=y.expr + ), + expression=sge.convert(1), + ), + ) + ) + return sge.Case( + ifs=[ + sge.If( + this=sge.EQ(this=x_int, expression=first), + true=sge.convert(0), + ) + ], + default=sge.Add( + this=sge.Cast( + this=sge.Floor( + this=sge.func( + "IEEE_DIVIDE", + sge.Sub( + this=sge.Sub(this=x_int, expression=first), + expression=sge.convert(1), + ), + sge.convert(n), + ) + ), + to=sge.DataType.build("INT64"), + ), + expression=sge.convert(1), + ), + ) + elif rule_code == "QE-DEC": # Quarterly + x_int = sge.Paren( + this=sge.Add( + this=sge.Mul( + this=sge.Extract( + this=sge.Identifier(this="YEAR"), expression=x.expr + ), + expression=sge.convert(4), + ), + expression=sge.Sub( + this=sge.Extract( + this=sge.Identifier(this="QUARTER"), expression=x.expr + ), + expression=sge.convert(1), + ), + ) + ) + first = sge.Paren( + this=sge.Add( + this=sge.Mul( + this=sge.Extract( + this=sge.Identifier(this="YEAR"), expression=y.expr + ), + expression=sge.convert(4), + ), + expression=sge.Sub( + this=sge.Extract( + this=sge.Identifier(this="QUARTER"), expression=y.expr + ), + expression=sge.convert(1), + ), + ) + ) + return sge.Case( + ifs=[ + sge.If( + this=sge.EQ(this=x_int, expression=first), + true=sge.convert(0), + ) + ], + default=sge.Add( + this=sge.Cast( + this=sge.Floor( + this=sge.func( + "IEEE_DIVIDE", + sge.Sub( + this=sge.Sub(this=x_int, expression=first), + expression=sge.convert(1), + ), + sge.convert(n), + ) + ), + to=sge.DataType.build("INT64"), + ), + expression=sge.convert(1), + ), + ) + elif rule_code == "YE-DEC": # Yearly + x_int = sge.Extract(this=sge.Identifier(this="YEAR"), expression=x.expr) + first = sge.Extract(this=sge.Identifier(this="YEAR"), expression=y.expr) + return sge.Case( + ifs=[ + sge.If( + this=sge.EQ(this=x_int, expression=first), + true=sge.convert(0), + ) + ], + default=sge.Add( + this=sge.Cast( + this=sge.Floor( + this=sge.func( + "IEEE_DIVIDE", + sge.Sub( + this=sge.Sub(this=x_int, expression=first), + expression=sge.convert(1), + ), + sge.convert(n), + ) + ), + to=sge.DataType.build("INT64"), + ), + expression=sge.convert(1), + ), + ) + else: + raise ValueError(rule_code) @register_unary_op(ops.FloorDtOp, pass_op=True) diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql new file mode 100644 index 0000000000..ab084f0c9e --- /dev/null +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql @@ -0,0 +1,84 @@ +WITH `bfcte_0` AS ( + SELECT + `datetime_col`, + `timestamp_col` + FROM `bigframes-dev`.`sqlglot_test`.`scalar_types` +), `bfcte_1` AS ( + SELECT + *, + CAST(FLOOR( + IEEE_DIVIDE( + UNIX_MICROS(CAST(`datetime_col` AS TIMESTAMP)) - UNIX_MICROS(CAST(`timestamp_col` AS TIMESTAMP)), + 86400000000 + ) + ) AS INT64) AS `bfcol_2`, + CASE + WHEN ( + EXTRACT(YEAR FROM `datetime_col`) * 12 + EXTRACT(MONTH FROM `datetime_col`) - 1 + ) = ( + EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1 + ) + THEN 0 + ELSE CAST(FLOOR( + IEEE_DIVIDE( + ( + EXTRACT(YEAR FROM `datetime_col`) * 12 + EXTRACT(MONTH FROM `datetime_col`) - 1 + ) - ( + EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1 + ) - 1, + 1 + ) + ) AS INT64) + 1 + END AS `bfcol_3`, + CASE + WHEN UNIX_MICROS( + CAST(TIMESTAMP_TRUNC(`datetime_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) + ) = UNIX_MICROS( + CAST(TIMESTAMP_TRUNC(`timestamp_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) + ) + THEN 0 + ELSE CAST(FLOOR( + IEEE_DIVIDE( + UNIX_MICROS( + CAST(TIMESTAMP_TRUNC(`datetime_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) + ) - UNIX_MICROS( + CAST(TIMESTAMP_TRUNC(`timestamp_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) + ) - 1, + 604800000000 + ) + ) AS INT64) + 1 + END AS `bfcol_4`, + CASE + WHEN ( + EXTRACT(YEAR FROM `datetime_col`) * 4 + EXTRACT(QUARTER FROM `datetime_col`) - 1 + ) = ( + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1 + ) + THEN 0 + ELSE CAST(FLOOR( + IEEE_DIVIDE( + ( + EXTRACT(YEAR FROM `datetime_col`) * 4 + EXTRACT(QUARTER FROM `datetime_col`) - 1 + ) - ( + EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1 + ) - 1, + 1 + ) + ) AS INT64) + 1 + END AS `bfcol_5`, + CASE + WHEN EXTRACT(YEAR FROM `datetime_col`) = EXTRACT(YEAR FROM `timestamp_col`) + THEN 0 + ELSE CAST(FLOOR( + IEEE_DIVIDE(EXTRACT(YEAR FROM `datetime_col`) - EXTRACT(YEAR FROM `timestamp_col`) - 1, 1) + ) AS INT64) + 1 + END AS `bfcol_6` + FROM `bfcte_0` +) +SELECT + `bfcol_2` AS `fixed_freq`, + `bfcol_3` AS `non_fixed_freq_monthly`, + `bfcol_4` AS `non_fixed_freq_weekly`, + `bfcol_5` AS `non_fixed_freq_quarterly`, + `bfcol_6` AS `non_fixed_freq_yearly` +FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py index 6384dc79a9..78be42f421 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py @@ -57,6 +57,33 @@ def test_dayofyear(scalar_types_df: bpd.DataFrame, snapshot): snapshot.assert_match(sql, "out.sql") +def test_datetime_to_integer_label(scalar_types_df: bpd.DataFrame, snapshot): + col_names = ["datetime_col", "timestamp_col"] + bf_df = scalar_types_df[col_names] + ops_map = { + "fixed_freq": ops.DatetimeToIntegerLabelOp( + freq=pd.tseries.offsets.Day(), origin="start", closed="left" + ).as_expr("datetime_col", "timestamp_col"), + "non_fixed_freq_monthly": ops.DatetimeToIntegerLabelOp( + freq=pd.tseries.offsets.MonthEnd(), origin="start", closed="left" + ).as_expr("datetime_col", "timestamp_col"), + "non_fixed_freq_weekly": ops.DatetimeToIntegerLabelOp( + freq=pd.tseries.offsets.Week(weekday=6), origin="start", closed="left" + ).as_expr("datetime_col", "timestamp_col"), + "non_fixed_freq_quarterly": ops.DatetimeToIntegerLabelOp( + freq=pd.tseries.offsets.QuarterEnd(startingMonth=12), + origin="start", + closed="left", + ).as_expr("datetime_col", "timestamp_col"), + "non_fixed_freq_yearly": ops.DatetimeToIntegerLabelOp( + freq=pd.tseries.offsets.YearEnd(month=12), origin="start", closed="left" + ).as_expr("datetime_col", "timestamp_col"), + } + + sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys())) + snapshot.assert_match(sql, "out.sql") + + def test_floor_dt(scalar_types_df: bpd.DataFrame, snapshot): col_names = ["datetime_col", "timestamp_col", "date_col"] bf_df = scalar_types_df[col_names] From 355d02b0c783406db92e329231902844450c0dc7 Mon Sep 17 00:00:00 2001 From: jialuo Date: Wed, 3 Dec 2025 02:11:03 +0000 Subject: [PATCH 2/3] fix mypy --- .../core/compile/sqlglot/expressions/datetime_ops.py | 10 +++++----- .../compile/sqlglot/expressions/test_datetime_ops.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bigframes/core/compile/sqlglot/expressions/datetime_ops.py b/bigframes/core/compile/sqlglot/expressions/datetime_ops.py index d1204d5ae6..78e17ae33b 100644 --- a/bigframes/core/compile/sqlglot/expressions/datetime_ops.py +++ b/bigframes/core/compile/sqlglot/expressions/datetime_ops.py @@ -71,7 +71,7 @@ def _datetime_to_integer_label_fixed_frequency( "UNIX_MICROS", sge.Cast(this=x.expr, to=sge.DataType(this=sge.DataType.Type.TIMESTAMPTZ)), ) - first = _calculate_resample_first(y, op.origin) + first = _calculate_resample_first(y, op.origin) # type: ignore x_int_label = sge.Cast( this=sge.Floor( this=sge.func( @@ -147,7 +147,7 @@ def _datetime_to_integer_label_non_fixed_frequency( ), ) elif rule_code == "ME": # Monthly - x_int = sge.Paren( + x_int = sge.Paren( # type: ignore this=sge.Add( this=sge.Mul( this=sge.Extract( @@ -163,7 +163,7 @@ def _datetime_to_integer_label_non_fixed_frequency( ), ) ) - first = sge.Paren( + first = sge.Paren( # type: ignore this=sge.Add( this=sge.Mul( this=sge.Extract( @@ -204,7 +204,7 @@ def _datetime_to_integer_label_non_fixed_frequency( ), ) elif rule_code == "QE-DEC": # Quarterly - x_int = sge.Paren( + x_int = sge.Paren( # type: ignore this=sge.Add( this=sge.Mul( this=sge.Extract( @@ -220,7 +220,7 @@ def _datetime_to_integer_label_non_fixed_frequency( ), ) ) - first = sge.Paren( + first = sge.Paren( # type: ignore this=sge.Add( this=sge.Mul( this=sge.Extract( diff --git a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py index e9957c78d2..3e3f307471 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py @@ -62,21 +62,21 @@ def test_datetime_to_integer_label(scalar_types_df: bpd.DataFrame, snapshot): bf_df = scalar_types_df[col_names] ops_map = { "fixed_freq": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.Day(), origin="start", closed="left" + freq=pd.tseries.offsets.Day(), origin="start", closed="left" # type: ignore ).as_expr("datetime_col", "timestamp_col"), "non_fixed_freq_monthly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.MonthEnd(), origin="start", closed="left" + freq=pd.tseries.offsets.MonthEnd(), origin="start", closed="left" # type: ignore ).as_expr("datetime_col", "timestamp_col"), "non_fixed_freq_weekly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.Week(weekday=6), origin="start", closed="left" + freq=pd.tseries.offsets.Week(weekday=6), origin="start", closed="left" # type: ignore ).as_expr("datetime_col", "timestamp_col"), "non_fixed_freq_quarterly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.QuarterEnd(startingMonth=12), + freq=pd.tseries.offsets.QuarterEnd(startingMonth=12), # type: ignore origin="start", closed="left", ).as_expr("datetime_col", "timestamp_col"), "non_fixed_freq_yearly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.YearEnd(month=12), origin="start", closed="left" + freq=pd.tseries.offsets.YearEnd(month=12), origin="start", closed="left" # type: ignore ).as_expr("datetime_col", "timestamp_col"), } From 2393288b3615abdda7426725fff3fe75c359577f Mon Sep 17 00:00:00 2001 From: jialuo Date: Wed, 3 Dec 2025 19:20:43 +0000 Subject: [PATCH 3/3] fix --- .../test_datetime_to_integer_label/out.sql | 50 +------------------ .../sqlglot/expressions/test_datetime_ops.py | 11 ---- 2 files changed, 2 insertions(+), 59 deletions(-) diff --git a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql index ab084f0c9e..5260dd680a 100644 --- a/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql +++ b/tests/unit/core/compile/sqlglot/expressions/snapshots/test_datetime_ops/test_datetime_to_integer_label/out.sql @@ -12,24 +12,6 @@ WITH `bfcte_0` AS ( 86400000000 ) ) AS INT64) AS `bfcol_2`, - CASE - WHEN ( - EXTRACT(YEAR FROM `datetime_col`) * 12 + EXTRACT(MONTH FROM `datetime_col`) - 1 - ) = ( - EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1 - ) - THEN 0 - ELSE CAST(FLOOR( - IEEE_DIVIDE( - ( - EXTRACT(YEAR FROM `datetime_col`) * 12 + EXTRACT(MONTH FROM `datetime_col`) - 1 - ) - ( - EXTRACT(YEAR FROM `timestamp_col`) * 12 + EXTRACT(MONTH FROM `timestamp_col`) - 1 - ) - 1, - 1 - ) - ) AS INT64) + 1 - END AS `bfcol_3`, CASE WHEN UNIX_MICROS( CAST(TIMESTAMP_TRUNC(`datetime_col`, WEEK(MONDAY)) + INTERVAL 6 DAY AS TIMESTAMP) @@ -47,38 +29,10 @@ WITH `bfcte_0` AS ( 604800000000 ) ) AS INT64) + 1 - END AS `bfcol_4`, - CASE - WHEN ( - EXTRACT(YEAR FROM `datetime_col`) * 4 + EXTRACT(QUARTER FROM `datetime_col`) - 1 - ) = ( - EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1 - ) - THEN 0 - ELSE CAST(FLOOR( - IEEE_DIVIDE( - ( - EXTRACT(YEAR FROM `datetime_col`) * 4 + EXTRACT(QUARTER FROM `datetime_col`) - 1 - ) - ( - EXTRACT(YEAR FROM `timestamp_col`) * 4 + EXTRACT(QUARTER FROM `timestamp_col`) - 1 - ) - 1, - 1 - ) - ) AS INT64) + 1 - END AS `bfcol_5`, - CASE - WHEN EXTRACT(YEAR FROM `datetime_col`) = EXTRACT(YEAR FROM `timestamp_col`) - THEN 0 - ELSE CAST(FLOOR( - IEEE_DIVIDE(EXTRACT(YEAR FROM `datetime_col`) - EXTRACT(YEAR FROM `timestamp_col`) - 1, 1) - ) AS INT64) + 1 - END AS `bfcol_6` + END AS `bfcol_3` FROM `bfcte_0` ) SELECT `bfcol_2` AS `fixed_freq`, - `bfcol_3` AS `non_fixed_freq_monthly`, - `bfcol_4` AS `non_fixed_freq_weekly`, - `bfcol_5` AS `non_fixed_freq_quarterly`, - `bfcol_6` AS `non_fixed_freq_yearly` + `bfcol_3` AS `non_fixed_freq_weekly` FROM `bfcte_1` \ No newline at end of file diff --git a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py index 3e3f307471..c4acb37e51 100644 --- a/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py +++ b/tests/unit/core/compile/sqlglot/expressions/test_datetime_ops.py @@ -64,20 +64,9 @@ def test_datetime_to_integer_label(scalar_types_df: bpd.DataFrame, snapshot): "fixed_freq": ops.DatetimeToIntegerLabelOp( freq=pd.tseries.offsets.Day(), origin="start", closed="left" # type: ignore ).as_expr("datetime_col", "timestamp_col"), - "non_fixed_freq_monthly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.MonthEnd(), origin="start", closed="left" # type: ignore - ).as_expr("datetime_col", "timestamp_col"), "non_fixed_freq_weekly": ops.DatetimeToIntegerLabelOp( freq=pd.tseries.offsets.Week(weekday=6), origin="start", closed="left" # type: ignore ).as_expr("datetime_col", "timestamp_col"), - "non_fixed_freq_quarterly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.QuarterEnd(startingMonth=12), # type: ignore - origin="start", - closed="left", - ).as_expr("datetime_col", "timestamp_col"), - "non_fixed_freq_yearly": ops.DatetimeToIntegerLabelOp( - freq=pd.tseries.offsets.YearEnd(month=12), origin="start", closed="left" # type: ignore - ).as_expr("datetime_col", "timestamp_col"), } sql = utils._apply_ops_to_sql(bf_df, list(ops_map.values()), list(ops_map.keys()))