diff --git a/datetime_range_timezone.module b/datetime_range_timezone.module
index 3094601..3306f91 100644
--- a/datetime_range_timezone.module
+++ b/datetime_range_timezone.module
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\datetime_range_timezone\Hook\DatetimeRangeTimezoneHooks;
+
 /**
  * @file
  * Module file.
@@ -8,21 +15,9 @@
 /**
  * Implements hook_theme().
  */
+#[LegacyHook]
 function datetime_range_timezone_theme($existing, $type, $theme, $path) {
-  return [
-    'datetime_range_timezone' => [
-      'path' => $path . '/templates',
-      'template' => 'datetime-range-timezone',
-      'variables' => [
-        'start_date' => NULL,
-        'iso_start_date' => NULL,
-        'end_date' => NULL,
-        'iso_end_date' => NULL,
-        'separator' => NULL,
-        'timezone' => NULL,
-      ],
-    ],
-  ];
+  return \Drupal::service(DatetimeRangeTimezoneHooks::class)->theme($existing, $type, $theme, $path);
 }
 
 /**
diff --git a/datetime_range_timezone.services.yml b/datetime_range_timezone.services.yml
new file mode 100644
index 0000000..ba981f5
--- /dev/null
+++ b/datetime_range_timezone.services.yml
@@ -0,0 +1,9 @@
+
+services:
+  Drupal\datetime_range_timezone\Hook\DatetimeRangeTimezoneHooks:
+    class: Drupal\datetime_range_timezone\Hook\DatetimeRangeTimezoneHooks
+    autowire: true
+
+  Drupal\datetime_range_timezone\Hook\DatetimeRangeTimezoneTokensHooks:
+    class: Drupal\datetime_range_timezone\Hook\DatetimeRangeTimezoneTokensHooks
+    autowire: true
diff --git a/datetime_range_timezone.tokens.inc b/datetime_range_timezone.tokens.inc
index 7231646..b81aadb 100644
--- a/datetime_range_timezone.tokens.inc
+++ b/datetime_range_timezone.tokens.inc
@@ -5,10 +5,9 @@
  * Token module integration.
  */
 
-use Drupal\Core\Datetime\DrupalDateTime;
-use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\datetime_range_timezone\Hook\DatetimeRangeTimezoneTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
-use Drupal\field\FieldStorageConfigInterface;
 
 /**
  * Implements hook_token_info_alter().
@@ -18,51 +17,9 @@ use Drupal\field\FieldStorageConfigInterface;
  * field_token_info_alter() from token module in order to provide support for
  * fields that are type of daterange_timezone.
  */
+#[LegacyHook]
 function datetime_range_timezone_token_info_alter(&$info) {
-  // Attach field tokens to their respective entity tokens.
-  foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
-    if (!$entity_type->entityClassImplements(ContentEntityInterface::class)) {
-      continue;
-    }
-
-    // Make sure a token type exists for this entity.
-    $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id);
-    if (empty($token_type) || !isset($info['types'][$token_type])) {
-      continue;
-    }
-
-    $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
-    /** @var \Drupal\field\FieldStorageConfigInterface $field */
-    foreach ($fields as $field_name => $field) {
-
-      // Provide format token for daterange_timezone fields.
-      if ($field->getType() != 'daterange_timezone') {
-        continue;
-      }
-
-      // Ensure the token implements FieldStorageConfigInterface or is defined
-      // in token module.
-      $provider = '';
-      if (isset($info['types'][$token_type]['module'])) {
-        $provider = $info['types'][$token_type]['module'];
-      }
-      if (!($field instanceof FieldStorageConfigInterface) && $provider != 'token') {
-        continue;
-      }
-
-      $field_token_name = $token_type . '-' . $field_name;
-
-      // We create the format tokens if the field value tokens are defined.
-      if (isset($info['tokens'][$field_token_name]['value'])) {
-        $info['tokens'][$field_token_name]['start_date'] = $info['tokens'][$field_token_name]['value'];
-        $info['tokens'][$field_token_name]['start_date']['name'] .= ' ' . t('format');
-        $info['tokens'][$field_token_name]['start_date']['type'] = 'date';
-        $info['tokens'][$field_token_name]['end_date'] = $info['tokens'][$field_token_name]['end_value'];
-        $info['tokens'][$field_token_name]['end_date']['name'] .= ' ' . t('format');
-        $info['tokens'][$field_token_name]['end_date']['type'] = 'date';
-      }
-    }
-  }
+  \Drupal::service(DatetimeRangeTimezoneTokensHooks::class)->tokenInfoAlter($info);
 }
 
 /**
@@ -72,85 +29,7 @@ function datetime_range_timezone_token_info_alter(&$info) {
  * executed after hook_tokens() functions. The function is based on the
  * field_tokens() field_property handling for daterange fields.
  */
+#[LegacyHook]
 function datetime_range_timezone_tokens_alter(array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata) {
-  if (empty($context['data']['field_property'])) {
-    return;
-  }
-
-  $data = $context['data'];
-  $tokens = $context['tokens'];
-  $options = $context['options'];
-  $token_service = \Drupal::token();
-  $date_formatter = \Drupal::service('date.formatter');
-  $language_manager = \Drupal::languageManager();
-
-  foreach ($tokens as $token => $original) {
-    $filtered_tokens = $tokens;
-    $delta = 0;
-    $parts = explode(':', $token);
-    if (is_numeric($parts[0])) {
-      if (count($parts) > 1) {
-        $delta = $parts[0];
-        $property_name = $parts[1];
-        // Pre-filter the tokens to select those with the correct delta.
-        $filtered_tokens = $token_service->findWithPrefix($tokens, $delta);
-        // Remove the delta to unify between having and not having one.
-        array_shift($parts);
-      }
-      else {
-        // Token is fieldname:delta, which is invalid.
-        continue;
-      }
-    }
-    else {
-      $property_name = $parts[0];
-    }
-
-    if (isset($data[$data['field_name']][$delta])) {
-      $field_item = $data[$data['field_name']][$delta];
-    }
-    else {
-      // The field has no such delta, abort replacement.
-      continue;
-    }
-
-    // We only care about daterange_timezone field items.
-    if ($field_item->getFieldDefinition()->getType() != 'daterange_timezone') {
-      continue;
-    }
-
-    if (($value = $field_item->{$property_name}) instanceof DrupalDateTime) {
-      $timestamp = $value->getTimestamp();
-
-      // If the token is an exact match for the property or the delta and the
-      // property, use the timestamp as-is.
-      if ($property_name == $token || "$delta:$property_name" == $token) {
-        $replacements[$original] = $timestamp;
-      }
-      else {
-        // Get the langcode for the date formatting.
-        $langcode = $options['langcode'] ?? $language_manager->getCurrentLanguage()->getId();
-
-        $date_tokens = $token_service->findWithPrefix($filtered_tokens, $property_name);
-        $timezone = $field_item->timezone;
-
-        $date_format_types = \Drupal::entityTypeManager()->getStorage('date_format')->loadMultiple();
-        foreach ($date_tokens as $name => $original_token) {
-          if (isset($date_format_types[$name])) {
-            $replacements[$original_token] = $date_formatter->format($timestamp, $name, '', $timezone, $langcode);
-          }
-          // If a custom date format is used we will apply during date format.
-          // @see system_tokens()
-          elseif ($created_tokens = $token_service->findWithPrefix($date_tokens, 'custom')) {
-            foreach ($created_tokens as $token_name => $token_original) {
-              $replacements[$token_original] = $date_formatter->format($timestamp, 'custom', $token_name, $timezone, $langcode);
-            }
-          }
-        }
-      }
-    }
-    else {
-      $replacements[$original] = $field_item->{$property_name};
-    }
-  }
+  \Drupal::service(DatetimeRangeTimezoneTokensHooks::class)->tokensAlter($replacements, $context, $bubbleable_metadata);
 }
diff --git a/src/Hook/DatetimeRangeTimezoneHooks.php b/src/Hook/DatetimeRangeTimezoneHooks.php
new file mode 100644
index 0000000..b0d0138
--- /dev/null
+++ b/src/Hook/DatetimeRangeTimezoneHooks.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\datetime_range_timezone\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for datetime_range_timezone.
+ */
+class DatetimeRangeTimezoneHooks {
+  /**
+   * @file
+   * Module file.
+   */
+
+  /**
+   * Implements hook_theme().
+   */
+  #[Hook('theme')]
+  public function theme($existing, $type, $theme, $path) {
+    return [
+      'datetime_range_timezone' => [
+        'path' => $path . '/templates',
+        'template' => 'datetime-range-timezone',
+        'variables' => [
+          'start_date' => NULL,
+          'iso_start_date' => NULL,
+          'end_date' => NULL,
+          'iso_end_date' => NULL,
+          'separator' => NULL,
+          'timezone' => NULL,
+        ],
+      ],
+    ];
+  }
+
+}
diff --git a/src/Hook/DatetimeRangeTimezoneTokensHooks.php b/src/Hook/DatetimeRangeTimezoneTokensHooks.php
new file mode 100644
index 0000000..7d64b89
--- /dev/null
+++ b/src/Hook/DatetimeRangeTimezoneTokensHooks.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace Drupal\datetime_range_timezone\Hook;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\field\FieldStorageConfigInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for datetime_range_timezone.
+ */
+class DatetimeRangeTimezoneTokensHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_token_info_alter().
+   *
+   * We use hook_token_info_alter() rather than hook_token_info() as other
+   * modules may already have defined some field tokens. The code is based on
+   * field_token_info_alter() from token module in order to provide support for
+   * fields that are type of daterange_timezone.
+   */
+  #[Hook('token_info_alter')]
+  public function tokenInfoAlter(&$info) {
+    // Attach field tokens to their respective entity tokens.
+    foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
+      if (!$entity_type->entityClassImplements(ContentEntityInterface::class)) {
+        continue;
+      }
+      // Make sure a token type exists for this entity.
+      $token_type = \Drupal::service('token.entity_mapper')->getTokenTypeForEntityType($entity_type_id);
+      if (empty($token_type) || !isset($info['types'][$token_type])) {
+        continue;
+      }
+      $fields = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id);
+      /** @var \Drupal\field\FieldStorageConfigInterface $field */
+      foreach ($fields as $field_name => $field) {
+        // Provide format token for daterange_timezone fields.
+        if ($field->getType() != 'daterange_timezone') {
+          continue;
+        }
+        // Ensure the token implements FieldStorageConfigInterface or is defined
+        // in token module.
+        $provider = '';
+        if (isset($info['types'][$token_type]['module'])) {
+          $provider = $info['types'][$token_type]['module'];
+        }
+        if (!$field instanceof FieldStorageConfigInterface && $provider != 'token') {
+          continue;
+        }
+        $field_token_name = $token_type . '-' . $field_name;
+        // We create the format tokens if the field value tokens are defined.
+        if (isset($info['tokens'][$field_token_name]['value'])) {
+          $info['tokens'][$field_token_name]['start_date'] = $info['tokens'][$field_token_name]['value'];
+          $info['tokens'][$field_token_name]['start_date']['name'] .= ' ' . $this->t('format');
+          $info['tokens'][$field_token_name]['start_date']['type'] = 'date';
+          $info['tokens'][$field_token_name]['end_date'] = $info['tokens'][$field_token_name]['end_value'];
+          $info['tokens'][$field_token_name]['end_date']['name'] .= ' ' . $this->t('format');
+          $info['tokens'][$field_token_name]['end_date']['type'] = 'date';
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements hook_tokens_alter().
+   *
+   * We are using hook_tokens_alter() in order to ensure the replacement is
+   * executed after hook_tokens() functions. The function is based on the
+   * field_tokens() field_property handling for daterange fields.
+   */
+  #[Hook('tokens_alter')]
+  public function tokensAlter(array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata) {
+    if (empty($context['data']['field_property'])) {
+      return;
+    }
+    $data = $context['data'];
+    $tokens = $context['tokens'];
+    $options = $context['options'];
+    $token_service = \Drupal::token();
+    $date_formatter = \Drupal::service('date.formatter');
+    $language_manager = \Drupal::languageManager();
+    foreach ($tokens as $token => $original) {
+      $filtered_tokens = $tokens;
+      $delta = 0;
+      $parts = explode(':', $token);
+      if (is_numeric($parts[0])) {
+        if (count($parts) > 1) {
+          $delta = $parts[0];
+          $property_name = $parts[1];
+          // Pre-filter the tokens to select those with the correct delta.
+          $filtered_tokens = $token_service->findWithPrefix($tokens, $delta);
+          // Remove the delta to unify between having and not having one.
+          array_shift($parts);
+        }
+        else {
+          // Token is fieldname:delta, which is invalid.
+          continue;
+        }
+      }
+      else {
+        $property_name = $parts[0];
+      }
+      if (isset($data[$data['field_name']][$delta])) {
+        $field_item = $data[$data['field_name']][$delta];
+      }
+      else {
+        // The field has no such delta, abort replacement.
+        continue;
+      }
+      // We only care about daterange_timezone field items.
+      if ($field_item->getFieldDefinition()->getType() != 'daterange_timezone') {
+        continue;
+      }
+      if (($value = $field_item->{$property_name}) instanceof DrupalDateTime) {
+        $timestamp = $value->getTimestamp();
+        // If the token is an exact match for the property or the delta and the
+        // property, use the timestamp as-is.
+        if ($property_name == $token || "{$delta}:{$property_name}" == $token) {
+          $replacements[$original] = $timestamp;
+        }
+        else {
+          // Get the langcode for the date formatting.
+          $langcode = $options['langcode'] ?? $language_manager->getCurrentLanguage()->getId();
+          $date_tokens = $token_service->findWithPrefix($filtered_tokens, $property_name);
+          $timezone = $field_item->timezone;
+          $date_format_types = \Drupal::entityTypeManager()->getStorage('date_format')->loadMultiple();
+          foreach ($date_tokens as $name => $original_token) {
+            if (isset($date_format_types[$name])) {
+              $replacements[$original_token] = $date_formatter->format($timestamp, $name, '', $timezone, $langcode);
+            }
+            elseif ($created_tokens = $token_service->findWithPrefix($date_tokens, 'custom')) {
+              foreach ($created_tokens as $token_name => $token_original) {
+                $replacements[$token_original] = $date_formatter->format($timestamp, 'custom', $token_name, $timezone, $langcode);
+              }
+            }
+          }
+        }
+      }
+      else {
+        $replacements[$original] = $field_item->{$property_name};
+      }
+    }
+  }
+
+}
diff --git a/tests/src/Functional/DateRangeTimezoneWidgetTest.php b/tests/src/Functional/DateRangeTimezoneWidgetTest.php
index 9fd9730..866e466 100644
--- a/tests/src/Functional/DateRangeTimezoneWidgetTest.php
+++ b/tests/src/Functional/DateRangeTimezoneWidgetTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\datetime_range_timezone\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Datetime\Entity\DateFormat;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
@@ -13,6 +15,8 @@ use Drupal\field\Entity\FieldConfig;
  *
  * @group datetime_range_timezone
  */
+#[Group('datetime_range_timezone')]
+#[RunTestsInSeparateProcesses]
 class DateRangeTimezoneWidgetTest extends DateRangeTimezoneTestBase {
 
   /**
diff --git a/tests/src/Kernel/DateRangeTimezoneFormatterTest.php b/tests/src/Kernel/DateRangeTimezoneFormatterTest.php
index 040726a..d031095 100644
--- a/tests/src/Kernel/DateRangeTimezoneFormatterTest.php
+++ b/tests/src/Kernel/DateRangeTimezoneFormatterTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\datetime_range_timezone\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Datetime\Entity\DateFormat;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
@@ -12,6 +14,8 @@ use Drupal\KernelTests\KernelTestBase;
  *
  * @group datetime_range_timezone
  */
+#[Group('datetime_range_timezone')]
+#[RunTestsInSeparateProcesses]
 class DateRangeTimezoneFormatterTest extends KernelTestBase {
 
   use DateRangeTimezoneHelperTrait;
diff --git a/tests/src/Kernel/TokenReplacementTest.php b/tests/src/Kernel/TokenReplacementTest.php
index 8bcbabb..befe89b 100755
--- a/tests/src/Kernel/TokenReplacementTest.php
+++ b/tests/src/Kernel/TokenReplacementTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\datetime_range_timezone\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
@@ -16,6 +18,8 @@ use Drupal\Tests\token\Functional\TokenTestTrait;
  *
  * @group datetime_range_timezone
  */
+#[Group('datetime_range_timezone')]
+#[RunTestsInSeparateProcesses]
 class TokenReplacementTest extends KernelTestBase {
 
   use DateRangeTimezoneHelperTrait;
