diff --git a/metricflow-semantics/metricflow_semantics/time/dateutil_adjuster.py b/metricflow-semantics/metricflow_semantics/time/dateutil_adjuster.py index cf553ce49e..9892764ac7 100644 --- a/metricflow-semantics/metricflow_semantics/time/dateutil_adjuster.py +++ b/metricflow-semantics/metricflow_semantics/time/dateutil_adjuster.py @@ -22,7 +22,20 @@ class DateutilTimePeriodAdjuster(TimePeriodAdjuster): def _relative_delta_for_window(self, time_granularity: TimeGranularity, count: int) -> relativedelta: """Relative-delta to cover time windows specified at different grains.""" - if time_granularity is TimeGranularity.DAY: + if time_granularity is TimeGranularity.NANOSECOND: + # TODO: figure out a workaround when enabling time constraints + raise ValueError("`relativedelta` does not support nanoseconds.") + elif time_granularity is TimeGranularity.MICROSECOND: + return relativedelta(microseconds=count) + elif time_granularity is TimeGranularity.MILLISECOND: + return relativedelta(microseconds=count * 1000) + elif time_granularity is TimeGranularity.SECOND: + return relativedelta(seconds=count) + elif time_granularity is TimeGranularity.MINUTE: + return relativedelta(minutes=count) + elif time_granularity is TimeGranularity.HOUR: + return relativedelta(hours=count) + elif time_granularity is TimeGranularity.DAY: return relativedelta(days=count) elif time_granularity is TimeGranularity.WEEK: return relativedelta(weeks=count) @@ -53,8 +66,18 @@ def expand_time_constraint_to_fill_granularity( def adjust_to_start_of_period( self, time_granularity: TimeGranularity, date_to_adjust: datetime.datetime ) -> datetime.datetime: - if time_granularity is TimeGranularity.DAY: + # TODO: update these options once time constraints support a full timestamp + if ( + time_granularity is TimeGranularity.NANOSECOND + or time_granularity is TimeGranularity.MICROSECOND + or time_granularity is TimeGranularity.MILLISECOND + or time_granularity is TimeGranularity.SECOND + or time_granularity is TimeGranularity.MINUTE + or time_granularity is TimeGranularity.HOUR + or time_granularity is TimeGranularity.DAY + ): return date_to_adjust + elif time_granularity is TimeGranularity.WEEK: return date_to_adjust + relativedelta(weekday=dateutil.relativedelta.MO(-1)) elif time_granularity is TimeGranularity.MONTH: @@ -77,8 +100,18 @@ def adjust_to_start_of_period( def adjust_to_end_of_period( self, time_granularity: TimeGranularity, date_to_adjust: datetime.datetime ) -> datetime.datetime: - if time_granularity is TimeGranularity.DAY: + # TODO: update these options once time constraints support a full timestamp + if ( + time_granularity is TimeGranularity.NANOSECOND + or time_granularity is TimeGranularity.MICROSECOND + or time_granularity is TimeGranularity.MILLISECOND + or time_granularity is TimeGranularity.SECOND + or time_granularity is TimeGranularity.MINUTE + or time_granularity is TimeGranularity.HOUR + or time_granularity is TimeGranularity.DAY + ): return date_to_adjust + elif time_granularity is TimeGranularity.WEEK: return date_to_adjust + relativedelta(weekday=dateutil.relativedelta.SU(1)) elif time_granularity is TimeGranularity.MONTH: diff --git a/metricflow-semantics/metricflow_semantics/time/time_constants.py b/metricflow-semantics/metricflow_semantics/time/time_constants.py index 3c66216a1d..0df05e98ac 100644 --- a/metricflow-semantics/metricflow_semantics/time/time_constants.py +++ b/metricflow-semantics/metricflow_semantics/time/time_constants.py @@ -1,15 +1,5 @@ from __future__ import annotations -from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity - # Python formatting string to use for converting datetime to ISO8601 ISO8601_PYTHON_FORMAT = "%Y-%m-%d" ISO8601_PYTHON_TS_FORMAT = "%Y-%m-%d %H:%M:%S" - -SUPPORTED_GRANULARITIES = [ - TimeGranularity.DAY, - TimeGranularity.WEEK, - TimeGranularity.MONTH, - TimeGranularity.QUARTER, - TimeGranularity.YEAR, -] diff --git a/metricflow-semantics/tests_metricflow_semantics/time/test_time_adjuster.py b/metricflow-semantics/tests_metricflow_semantics/time/test_time_adjuster.py index 3b7ab65fed..d48e3275bc 100644 --- a/metricflow-semantics/tests_metricflow_semantics/time/test_time_adjuster.py +++ b/metricflow-semantics/tests_metricflow_semantics/time/test_time_adjuster.py @@ -54,6 +54,8 @@ def test_start_and_end_periods( # noqa: D103 rows: List[Tuple[str, ...]] = [] for date_time in date_times_to_check: for time_granularity in TimeGranularity: + if time_granularity.to_int() < TimeGranularity.DAY.to_int(): + continue dateutil_start_of_period = dateutil_adjuster.adjust_to_start_of_period(time_granularity, date_time) dateutil_end_of_period = dateutil_adjuster.adjust_to_end_of_period(time_granularity, date_time) rows.append( diff --git a/metricflow/dataset/convert_semantic_model.py b/metricflow/dataset/convert_semantic_model.py index 964d0d4b50..f45d2fe102 100644 --- a/metricflow/dataset/convert_semantic_model.py +++ b/metricflow/dataset/convert_semantic_model.py @@ -283,7 +283,7 @@ def _convert_time_dimension( time_dimension_instances: List[TimeDimensionInstance] = [] select_columns: List[SqlSelectColumn] = [] - defined_time_granularity = TimeGranularity.DAY + defined_time_granularity = DEFAULT_TIME_GRANULARITY if dimension.type_params and dimension.type_params.time_granularity: defined_time_granularity = dimension.type_params.time_granularity diff --git a/metricflow/plan_conversion/time_spine.py b/metricflow/plan_conversion/time_spine.py index 38669b4e3e..d45a30e51d 100644 --- a/metricflow/plan_conversion/time_spine.py +++ b/metricflow/plan_conversion/time_spine.py @@ -6,6 +6,7 @@ from dbt_semantic_interfaces.protocols import SemanticManifest from dbt_semantic_interfaces.type_enums.time_granularity import TimeGranularity from metricflow_semantics.mf_logging.pretty_print import mf_pformat +from metricflow_semantics.specs.spec_classes import DEFAULT_TIME_GRANULARITY from metricflow.sql.sql_table import SqlTable @@ -23,7 +24,7 @@ class TimeSpineSource: # Name of the column in the table that contains the dates. time_column_name: str = "ds" # The time granularity of the dates in the spine table. - time_column_granularity: TimeGranularity = TimeGranularity.DAY + time_column_granularity: TimeGranularity = DEFAULT_TIME_GRANULARITY @property def spine_table(self) -> SqlTable: @@ -37,10 +38,10 @@ def create_from_manifest(semantic_manifest: SemanticManifest) -> TimeSpineSource if not ( len(time_spine_table_configurations) == 1 - and time_spine_table_configurations[0].grain == TimeGranularity.DAY + and time_spine_table_configurations[0].grain == DEFAULT_TIME_GRANULARITY ): raise NotImplementedError( - f"Only a single time spine table configuration with {TimeGranularity.DAY} is currently " + f"Only a single time spine table configuration with {DEFAULT_TIME_GRANULARITY} is currently " f"supported. Got:\n" f"{mf_pformat(time_spine_table_configurations)}" )