diff --git a/composer.json b/composer.json
index 6eaf4c5..193ed05 100644
--- a/composer.json
+++ b/composer.json
@@ -28,7 +28,7 @@
     "forum": "https://drupal.stackexchange.com/questions/tagged/views_date_format_sql"
   },
   "require": {
-    "drupal/core": "^10 || ^11"
+    "drupal/core": "^10.1 || ^11 || ^12"
   },
   "repositories": {
     "drupal": {
diff --git a/src/Hook/ViewsDateFormatSqlViewsHooks.php b/src/Hook/ViewsDateFormatSqlViewsHooks.php
new file mode 100644
index 0000000..ccd6006
--- /dev/null
+++ b/src/Hook/ViewsDateFormatSqlViewsHooks.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\views_date_format_sql\Hook;
+
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for views_date_format_sql.
+ */
+class ViewsDateFormatSqlViewsHooks {
+
+  /**
+   * Implements hook_views_data_alter().
+   */
+  #[Hook('views_data_alter')]
+  public static function viewsDataAlter(&$data) {
+    // Loops through fields definitions looking for timestamp fields and changes
+    // the standard date handler with our own.
+    foreach ($data as &$table) {
+      // Skip files because they have very specific fields.
+      if (isset($table['table']['provider']) && $table['table']['provider'] === 'file') {
+        continue;
+      }
+      foreach ($table as &$field) {
+        // Check to see if the field being used here is a field_api timestamp.
+        if (!empty($field['field']['entity_type']) && !empty($field['field']['field_name'])) {
+          $field_storage = FieldStorageConfig::loadByName($field['field']['entity_type'], $field['field']['field_name']);
+          if ($field_storage && $field_storage->getType() == 'timestamp') {
+            $field['field']['id'] = 'views_date_format_sql_field';
+          }
+        }
+        elseif (!empty($field['argument']['entity_type']) && !empty($field['argument']['field_name'])) {
+          $field_storage = FieldStorageConfig::loadByName($field['argument']['entity_type'], $field['argument']['field_name']);
+          if ($field_storage && $field_storage->getType() == 'timestamp') {
+            $field['argument']['id'] = 'views_date_format_sql_argument';
+          }
+        }
+        elseif (!empty($field['field']['id']) && !empty($field['argument']['id'])) {
+          if ($field['field']['id'] === 'field' && $field['argument']['id'] === 'date') {
+            $field['field']['id'] = 'views_date_format_sql_field';
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/views_date_format_sql.info.yml b/views_date_format_sql.info.yml
index e9bf80f..09b6fad 100644
--- a/views_date_format_sql.info.yml
+++ b/views_date_format_sql.info.yml
@@ -1,5 +1,5 @@
 name: Views Date Format SQL
 description: 'Allows to format date fields using SQL. This enables group aggregation.'
 type: module
-core_version_requirement: ^10 || ^11
+core_version_requirement: ^10.1 || ^11 || ^12
 package: Views
diff --git a/views_date_format_sql.services.yml b/views_date_format_sql.services.yml
new file mode 100644
index 0000000..d8029bc
--- /dev/null
+++ b/views_date_format_sql.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\views_date_format_sql\Hook\ViewsDateFormatSqlViewsHooks:
+    class: Drupal\views_date_format_sql\Hook\ViewsDateFormatSqlViewsHooks
+    autowire: true
diff --git a/views_date_format_sql.views.inc b/views_date_format_sql.views.inc
index b3d66bf..0d89411 100644
--- a/views_date_format_sql.views.inc
+++ b/views_date_format_sql.views.inc
@@ -5,39 +5,13 @@
  * This file contains the hook to assign this handler as default to date fields.
  */
 
-use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\views_date_format_sql\Hook\ViewsDateFormatSqlViewsHooks;
 
 /**
  * Implements hook_views_data_alter().
  */
+#[LegacyHook]
 function views_date_format_sql_views_data_alter(&$data) {
-  // Loops through fields definitions looking for timestamp fields and changes
-  // the standard date handler with our own.
-  foreach ($data as &$table) {
-    // Skip files because they have very specific fields.
-    if (isset($table['table']['provider']) && $table['table']['provider'] === 'file') {
-      continue;
-    }
-
-    foreach ($table as &$field) {
-      // Check to see if the field being used here is a field_api timestamp.
-      if (!empty($field['field']['entity_type']) && !empty($field['field']['field_name'])) {
-        $field_storage = FieldStorageConfig::loadByName($field['field']['entity_type'], $field['field']['field_name']);
-        if ($field_storage && $field_storage->getType() == 'timestamp') {
-          $field['field']['id'] = 'views_date_format_sql_field';
-        }
-      }
-      elseif (!empty($field['argument']['entity_type']) && !empty($field['argument']['field_name'])) {
-        $field_storage = FieldStorageConfig::loadByName($field['argument']['entity_type'], $field['argument']['field_name']);
-        if ($field_storage && $field_storage->getType() == 'timestamp') {
-          $field['argument']['id'] = 'views_date_format_sql_argument';
-        }
-      }
-      elseif (!empty($field['field']['id']) && !empty($field['argument']['id'])) {
-        if ($field['field']['id'] === 'field' && $field['argument']['id'] === 'date') {
-          $field['field']['id'] = 'views_date_format_sql_field';
-        }
-      }
-    }
-  }
+  \Drupal::service(ViewsDateFormatSqlViewsHooks::class)->viewsDataAlter($data);
 }
