diff --git a/modules/data.eval.inc b/modules/data.eval.inc
index 341f0d3..6c91e5a 100644
--- a/modules/data.eval.inc
+++ b/modules/data.eval.inc
@@ -183,42 +183,89 @@ function rules_action_data_convert($arguments, RulesPlugin $element, $state) {
   $value_info = $element->getArgumentInfo('value');
   $from_type = $value_info['type'];
   $target_type = $arguments['type'];
+  if ($from_type == 'date' && !empty($arguments['date_format'])) {
+    $result = rules_action_data_convert_date2string($arguments);
+  }
+  else {
+    // First apply the rounding behavior if given.
+    if (isset($arguments['rounding_behavior'])) {
+      switch ($arguments['rounding_behavior']) {
+        case 'up':
+          $arguments['value'] = ceil($arguments['value']);
+          break;
+        case 'down':
+          $arguments['value'] = floor($arguments['value']);
+          break;
+        default:
+        case 'round':
+          $arguments['value'] = round($arguments['value']);
+          break;
+      }
+    }
 
-  // First apply the rounding behavior if given.
-  if (isset($arguments['rounding_behavior'])) {
-    switch ($arguments['rounding_behavior']) {
-      case 'up':
-        $arguments['value'] = ceil($arguments['value']);
+    switch ($target_type) {
+      case 'decimal':
+        $result = floatval($arguments['value']);
         break;
-      case 'down':
-        $arguments['value'] = floor($arguments['value']);
+      case 'integer':
+        $result = intval($arguments['value']);
         break;
-      default:
-      case 'round':
-        $arguments['value'] = round($arguments['value']);
+      case 'text':
+        $result = strval($arguments['value']);
         break;
     }
   }
 
-  switch ($target_type) {
-    case 'decimal':
-      $result = floatval($arguments['value']);
-      break;
-    case 'integer':
-      $result = intval($arguments['value']);
-      break;
-    case 'text':
-      $result = strval($arguments['value']);
-      break;
+  return array('conversion_result' => $result);
+}
+
+/**
+ * Converts a date to a string, using format and timezone.
+ * Code inspired of Views module's date field renderer.
+ *
+ * @see views_handler_field_date::render()
+ */
+function rules_action_data_convert_date2string($arguments) {
+  $value = $arguments['value'];
+  $format = $arguments['date_format'];
+  if (in_array($format, _rules_action_date_to_string_custom_formats(TRUE))) {
+    $custom_format = $arguments['custom_date_format'];
   }
 
-  return array('conversion_result' => $result);
+  if ($value) {
+    $timezone = !empty($arguments['timezone']) ? $arguments['timezone'] : NULL;
+    $time_diff = REQUEST_TIME - $value; // will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence)
+    switch ($format) {
+      case 'raw time ago':
+        return format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2);
+      case 'time ago':
+        return t('%time ago', array('%time' => format_interval($time_diff, is_numeric($custom_format) ? $custom_format : 2)));
+      case 'raw time hence':
+        return format_interval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2);
+      case 'time hence':
+        return t('%time hence', array('%time' => format_interval(-$time_diff, is_numeric($custom_format) ? $custom_format : 2)));
+      case 'raw time span':
+        return ($time_diff < 0 ? '-' : '') . format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2);
+      case 'inverse time span':
+        return ($time_diff > 0 ? '-' : '') . format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2);
+      case 'time span':
+        return t(($time_diff < 0 ? '%time hence' : '%time ago'), array('%time' => format_interval(abs($time_diff), is_numeric($custom_format) ? $custom_format : 2)));
+      case 'custom':
+        if ($custom_format == 'r') {
+          return format_date($value, $format, $custom_format, $timezone, 'en');
+        }
+        return format_date($value, $format, $custom_format, $timezone);
+      default:
+        return format_date($value, $format, '', $timezone);
+    }
+  }
 }
 
 /**
  * Info alteration callback for variable add action.
  */
 function rules_action_data_convert_info_alter(&$element_info, RulesAbstractPlugin $element) {
+  module_load_include('inc', 'rules', 'modules/data.rules');
 
   if (isset($element->settings['type']) && $type = $element->settings['type']) {
     $element_info['provides']['conversion_result']['type'] = $type;
@@ -228,6 +275,62 @@ function rules_action_data_convert_info_alter(&$element_info, RulesAbstractPlugi
       unset($element_info['parameter']['rounding_behavior']);
     }
 
+    if (array_key_exists('value:select', $element->settings)) {
+      $wrapper = $element->applyDataSelector($element->settings['value:select']);
+      if (!empty($wrapper)) {
+        $info = $wrapper->info();
+        if ($info['type'] == 'date' && $type == 'text') {
+          $element_info['parameter']['date_format'] = array(
+            'type' => 'token',
+            'label' => t('Date format'),
+            'description' => t('The date format the conversion should use.'),
+            'options list' => 'rules_action_date_to_string_format_options',
+            'restriction' => 'input',
+            'optional' => TRUE,
+            'default value' => '',
+          );
+          $custom_formats = _rules_action_date_to_string_custom_formats(TRUE);
+          if (!array_key_exists('date_format', $element->settings)) {
+            $element->settings['date_format'] = '';
+          }
+          if (in_array($element->settings['date_format'], $custom_formats)) {
+            $element_info['parameter']['custom_date_format'] = array(
+              'type' => 'token',
+              'label' => t('Custom date format'),
+              'description' => $element->settings['date_format'] == 'custom' ? t('See the <a href="@url" target="_blank">PHP manual</a> for date formats.', array('@url' => 'http://php.net/manual/function.date.php')) : t('Enter the number of different time units to display, which defaults to 2.'),
+              'default value' => '',
+              'optional' => $element->settings['date_format'] != 'custom',
+            );
+          }
+          else {
+            unset($element_info['parameter']['custom_date_format']);
+          }
+          // Only system date formats and 'custom' can have a timezone.
+          $timezoneable_formats = _rules_action_date_to_string_system_formats(TRUE);
+          $timezoneable_formats[] = 'custom';
+          if (in_array($element->settings['date_format'], $timezoneable_formats)) {
+            $element_info['parameter']['timezone'] = array(
+              'type' => 'token',
+              'label' => t('Timezone'),
+              'description' => t('Timezone to be used for date output.'),
+              'options list' => 'rules_action_date_to_string_timezone_options',
+              'restriction' => 'input',
+              'optional' => TRUE,
+              'default value' => '',
+            );
+          }
+          else {
+            unset($element_info['parameter']['timezone']);
+          }
+        }
+        else {
+          foreach (array('date_format', 'custom_date_format', 'timezone') as $key) {
+            unset($element_info['parameter'][$key]);
+          }
+        }
+      }
+    }
+
     // Configure compatible source-types:
     switch ($type) {
       case 'integer':
diff --git a/modules/data.rules.inc b/modules/data.rules.inc
index ebeb338..d8df604 100644
--- a/modules/data.rules.inc
+++ b/modules/data.rules.inc
@@ -228,7 +228,7 @@ function rules_data_action_info() {
     'base' => 'rules_action_data_convert',
     'named parameter' => TRUE,
     'callbacks' => array(
-      'form_alter' => 'rules_action_type_form_alter',
+      'form_alter' => 'rules_action_data_convert_form_alter',
     ),
   );
   return $return;
@@ -257,6 +257,67 @@ function rules_action_data_convert_rounding_behavior_options(RulesPlugin $elemen
 }
 
 /**
+ * Date to string conversion action: Options list callback for date format.
+ * Code inspired of Views module's date field options.
+ *
+ * @see views_handler_field_date::options_form()
+ */
+function rules_action_date_to_string_format_options(RulesPlugin $element = NULL, $param_name = '') {
+  $date_formats = array(
+    '' => t('Linux timestamp'),
+  );
+  $date_formats += _rules_action_date_to_string_system_formats();
+  $date_formats += _rules_action_date_to_string_custom_formats();
+  return $date_formats;
+}
+
+/**
+ * Returns system date formats.
+ *
+ * @see rules_action_date_to_string_format_options()
+ */
+function _rules_action_date_to_string_system_formats($keys = FALSE) {
+  $result = array();
+  $date_types = system_get_date_types();
+  foreach ($date_types as $key => $value) {
+    $result[$value['type']] = t('@date_format format', array('@date_format' => $value['title'])) . ': ' . format_date(REQUEST_TIME, $value['type']);
+  }
+  if ($keys) {
+    $result = array_keys($result);
+  }
+  return $result;
+}
+
+/**
+ * Returns date formats that accept a custom parameter.
+ *
+ * @see rules_action_date_to_string_format_options()
+ */
+function _rules_action_date_to_string_custom_formats($keys = FALSE) {
+  $result = array(
+    'custom' => t('Custom'),
+    'raw time ago' => t('Time ago'),
+    'time ago' => t('Time ago (with "ago" appended)'),
+    'raw time hence' => t('Time hence'),
+    'time hence' => t('Time hence (with "hence" appended)'),
+    'raw time span' => t('Time span (future dates have "-" prepended)'),
+    'inverse time span' => t('Time span (past dates have "-" prepended)'),
+    'time span' => t('Time span (with "ago/hence" appended)'),
+  );
+  if ($keys) {
+    $result = array_keys($result);
+  }
+  return $result;
+}
+
+/**
+ * Date to string conversion action: Options list callback for date timezone.
+ */
+function rules_action_date_to_string_timezone_options(RulesPlugin $element = NULL, $param_name = '') {
+  return array('' => t('- Default site/user timezone -')) + system_time_zones(FALSE);
+}
+
+/**
  * Customize access check for data set action.
  */
 function rules_action_data_set_access(RulesAbstractPlugin $element) {
@@ -365,7 +426,6 @@ function rules_data_list_form_alter(&$form, &$form_state, $options, RulesAbstrac
   }
 }
 
-
 /**
  * Form alter callback for actions relying on the entity type or the data type.
  */
@@ -407,6 +467,100 @@ function rules_action_type_form_alter(&$form, &$form_state, $options, RulesAbstr
 }
 
 /**
+ * Form alter callback for data convert action.
+ */
+function rules_action_data_convert_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
+  if (empty($element->settings['type'])) {
+    $step = 1;
+  }
+  elseif (empty($element->settings['value:select'])) {
+    $step = 2;
+  }
+  else {
+    $step = 3;
+  }
+
+  $form['reload'] = array(
+    '#weight' => 5,
+    '#type' => 'submit',
+    '#name' => 'reload',
+    '#value' => $step != 3 ? t('Continue') : t('Reload form'),
+    '#limit_validation_errors' => array(array('parameter', 'type')),
+    '#submit' => array('rules_action_type_form_submit_rebuild'),
+    '#ajax' => rules_ui_form_default_ajax(),
+  );
+  // Use ajax and trigger as the reload button.
+  $form['parameter']['type']['settings']['type']['#ajax'] = $form['reload']['#ajax'] + array(
+    'event' => 'change',
+    'trigger_as' => array('name' => 'reload'),
+  );
+  // Use ajax on date_format button too, if available.
+  if (!empty($form['parameter']['date_format'])) {
+    $form['parameter']['date_format']['settings']['date_format']['#ajax'] = $form['reload']['#ajax'] + array(
+      'event' => 'change',
+      'trigger_as' => array('name' => 'reload'),
+    );
+  }
+
+  if ($step != 3) {
+    // In two first steps, disable submit button and "provides variables" section.
+    unset($form['submit']);
+    unset($form['provides']);
+    // Also disable #ajax as they have troubles with lazy-loaded JS.
+    // @todo: Re-enable once JS lazy-loading is fixed in core.
+    unset($form['parameter']['type']['settings']['type']['#ajax']);
+    unset($form['reload']['#ajax']);
+  }
+
+  switch ($step) {
+    case 1:
+      // In the first step show only the type select.
+      $keys = array('type');
+      $remove = array_diff(element_children($form['parameter']), $keys);
+      foreach ($remove as $key) {
+        unset($form['parameter'][$key]);
+      }
+      break;
+    case 2:
+      // In the second step, show only type select and value field.
+      $keys = array('type', 'value');
+      $remove = array_diff(element_children($form['parameter']), $keys);
+      foreach ($remove as $key) {
+        unset($form['parameter'][$key]);
+      }
+      break;
+    case 3:
+      // Get the user to reload the form to load date-specific fields if needed,
+      // as we have no way to auto-refresh when a value to convert is set.
+      if (array_key_exists('value:select', $element->settings)) {
+        $wrapper = $element->applyDataSelector($element->settings['value:select']);
+        if (!empty($wrapper)) {
+          $info = $wrapper->info();
+          if ($element->settings['type'] == 'text' && $info['type'] != 'date') {
+            $form['parameter']['value']['#suffix'] = '<div class="description">' . t('If converting a date to text, please reload the form to display date-specific settings.') . '</div>';
+          }
+          else {
+            $form['parameter']['value']['#suffix'] = '<div class="description">' . t('If not converting a date to text, please reload the form to hide date-specific settings.') . '</div>';
+          }
+        }
+      }
+      // Make sure the date-specific elements stay in a constant order.
+      $date_elements = array(
+        'date_format' => 2,
+        'custom_date_format' => 4,
+        'timezone' => 6,
+      );
+      foreach ($date_elements as $date_element => $weight) {
+        if (!empty($form['parameter'][$date_element])) {
+          $form['parameter'][$date_element]['#weight'] = $weight;
+        }
+      }
+      break;
+    default: break; // Did something go wrong?
+  }
+}
+
+/**
  * FAPI submit callback for reloading the type form for entities or data types.
  */
 function rules_action_type_form_submit_rebuild($form, &$form_state) {
