From 51381d8d09666037d202008fb1abefa6f03c96e8 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Thu, 31 Jan 2019 15:32:37 -0800 Subject: [PATCH 01/12] wip edtf 2018 --- .../Field/FieldFormatter/EDTFFormatter.php | 136 ++++++++---------- src/Plugin/Field/FieldWidget/EDTFWidget.php | 128 +++++++++++------ 2 files changed, 144 insertions(+), 120 deletions(-) diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php index 0720c36..364eda1 100644 --- a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -43,6 +43,25 @@ class EDTFFormatter extends FormatterBase { '22' => ['mmm' => 'Sum', 'mmmm' => 'Summer'], '23' => ['mmm' => 'Aut', 'mmmm' => 'Autumn'], '24' => ['mmm' => 'Win', 'mmmm' => 'Winter'], + '25' => ['mmm' => 'Spr', 'mmmm' => 'Spring - Northern Hemisphere'], + '26' => ['mmm' => 'Sum', 'mmmm' => 'Summer - Northern Hemisphere'], + '27' => ['mmm' => 'Aut', 'mmmm' => 'Autumn - Northern Hemisphere'], + '28' => ['mmm' => 'Win', 'mmmm' => 'Winter - Northern Hemisphere'], + '29' => ['mmm' => 'Spr', 'mmmm' => 'Spring - Southern Hemisphere'], + '30' => ['mmm' => 'Sum', 'mmmm' => 'Summer - Southern Hemisphere'], + '31' => ['mmm' => 'Aut', 'mmmm' => 'Autumn - Southern Hemisphere'], + '32' => ['mmm' => 'Win', 'mmmm' => 'Winter - Southern Hemisphere'], + '33' => ['mmm' => 'Q1', 'mmmm' => 'Quarter 1'], + '34' => ['mmm' => 'Q2', 'mmmm' => 'Quarter 2'], + '35' => ['mmm' => 'Q3', 'mmmm' => 'Quarter 3'], + '36' => ['mmm' => 'Q4', 'mmmm' => 'Quarter 4'], + // I'm making up the rest of these abbreviations + // because I can't find standardized ones. + '37' => ['mmm' => 'Quad1', 'mmmm' => 'Quadrimester 1'], + '38' => ['mmm' => 'Quad2', 'mmmm' => 'Quadrimester 2'], + '39' => ['mmm' => 'Quad3', 'mmmm' => 'Quadrimester 3'], + '40' => ['mmm' => 'Sem1', 'mmmm' => 'Semestral 1'], + '41' => ['mmm' => 'Sem2', 'mmmm' => 'Semestral 2'], ]; /** @@ -57,38 +76,6 @@ class EDTFFormatter extends FormatterBase { 'space' => ' ', ]; - /** - * Northern hemisphere season map. - * - * @var array - */ - private $seasonMapNorth = [ - // Spring => March. - '21' => '03', - // Summer => June. - '22' => '06', - // Autumn => September. - '23' => '09', - // Winter => December. - '24' => '12', - ]; - - /** - * Southern hemisphere season map. - * - * @var array - */ - private $seasonMapSouth = [ - // Spring => September. - '21' => '03', - // Summer => December. - '22' => '06', - // Autumn => March. - '23' => '09', - // Winter => June. - '24' => '12', - ]; - /** * {@inheritdoc} */ @@ -96,14 +83,9 @@ public static function defaultSettings() { return [ // ISO 8601 bias. 'date_separator' => 'dash', - // ISO 8601 bias. 'date_order' => 'big_endian', - // ISO 8601 bias. 'month_format' => 'mm', - // ISO 8601 bias. 'day_format' => 'dd', - // Northern bias, sorry. - 'season_hemisphere' => 'north', ] + parent::defaultSettings(); } @@ -154,18 +136,6 @@ public function settingsForm(array $form, FormStateInterface $form_state) { 'd' => t('one-digit day of the month for days below 10, e.g. 2'), ], ]; - $form['season_hemisphere'] = [ - '#title' => t('Hemisphere Seasons'), - '#type' => 'select', - '#default_value' => $this->getSetting('season_hemisphere'), - '#description' => t("Seasons don't have digit months so we map them - to their respective equinox and solstice months. - Select a hemisphere to use for the mapping."), - '#options' => [ - 'north' => t('Northern Hemisphere'), - 'south' => t('Southern Hemisphere'), - ], - ]; return $form; } @@ -187,32 +157,49 @@ public function viewElements(FieldItemListInterface $items, $langcode) { foreach ($items as $delta => $item) { // Interval. - list($begin, $end) = explode('/', $item->value); + if (strpos($item->value, '/') !== FALSE) { + list($begin, $end) = explode('/', $item->value); - $formatted_begin = $this->formatDate($begin); + if (empty($begin) || $begin === '..') { + $formatted_begin = "open start"; + } + else { + $formatted_begin = $this->formatDate($begin); + } + + if (empty($end) || $end === '..') { + $formatted_end = "open end"; + } + else { + $formatted_end = $this->formatDate($end); + } - // End either empty or valid extended interval values (5.2.3.) - if (empty($end)) { - $element[$delta] = ['#markup' => $formatted_begin]; - } - elseif ($end === 'unknown' || $end === 'open') { $element[$delta] = [ '#markup' => t('@begin to @end', [ '@begin' => $formatted_begin, - '@end' => $end, + '@end' => $formatted_end, ]), ]; + continue; } - else { - $formatted_end = $this->formatDate($end); + // Sets. + if (strpos($item->value, '[') !== FALSE || strpos($item->value, '{') !== FALSE) { + $set_qualifier = (strpos($item->value, '[') !== FALSE) ? t('one of the dates:') : t('all of the dates:'); + foreach (preg_split('/(,|\.\.)/', trim($item->value, '{}[]')) as $date) { + $formatted_dates[] = $this->formatDate($date); + } $element[$delta] = [ - '#markup' => t('@begin to @end', [ - '@begin' => $formatted_begin, - '@end' => $formatted_end, + '#markup' => t('@qualifier @list', [ + '@qualifier' => $set_qualifier, + '@list' => implode(', ', $formatted_dates), ]), ]; + continue; } + $element[$delta] = [ + '#markup' => $this->formatDate($item->value), + ]; } return $element; } @@ -232,6 +219,7 @@ protected function formatDate($edtf_text) { // TODO: Time? $qualifiers_format = '%s'; // Uncertainty. + // TODO: Group Qualification if (!(strpos($edtf_text, '~') === FALSE)) { $qualifiers_format = t('approximately'); $qualifiers_format .= ' %s'; @@ -240,24 +228,28 @@ protected function formatDate($edtf_text) { $qualifiers_format = '%s '; $qualifiers_format .= t('(uncertain)'); } - $cleaned_datetime = str_replace(['?', '~'], '', $cleaned_datetime); + if (!(strpos($edtf_text, '%') === FALSE)) { + $qualifiers_format = '%s '; + $qualifiers_format .= t('(approximate and uncertain)'); + } + $cleaned_datetime = str_replace(['?', '~', '%'], '', $cleaned_datetime); list($year, $month, $day) = explode('-', $cleaned_datetime, 3); // Which unspecified, if any? $which_unspecified = ''; - if (!(strpos($year, 'uu') === FALSE)) { + if (!(strpos($year, 'XX') === FALSE)) { $which_unspecified = t('decade'); } - if (!(strpos($year, 'u') === FALSE)) { + if (!(strpos($year, 'X') === FALSE)) { $which_unspecified = t('year'); } - if (!(strpos($month, 'u') === FALSE)) { + if (!(strpos($month, 'X') === FALSE)) { $which_unspecified = t('month'); // No partial months. $month = ''; } - if (!(strpos($day, 'u') === FALSE)) { + if (!(strpos($day, 'X') === FALSE)) { $which_unspecified = t('day'); // No partial days. $day = ''; @@ -268,8 +260,8 @@ protected function formatDate($edtf_text) { } // Clean-up unspecified year/decade. - if (!(strpos($year, 'u') === FALSE)) { - $year = str_replace('u', '0', $year); + if (!(strpos($year, 'X') === FALSE)) { + $year = str_replace('X', '0', $year); $year = t("the @year's", ['@year' => $year]); } @@ -279,12 +271,6 @@ protected function formatDate($edtf_text) { if ($settings['month_format'] === 'mmm' || $settings['month_format'] === 'mmmm') { $month = $this->MONTHS[$month][$settings['month_format']]; } - // Digit Seasons. - elseif (in_array($month, ['21', '22', '23', '24'])) { - $season_mapping = ($settings['season_hemisphere'] === 'north' ? $this->seasonMapNorth : $this->seasonMapSouth); - $month = $season_mapping[$month]; - } - if ($settings['month_format'] === 'm') { $month = ltrim($month, ' 0'); } diff --git a/src/Plugin/Field/FieldWidget/EDTFWidget.php b/src/Plugin/Field/FieldWidget/EDTFWidget.php index 8d59cca..30369e0 100644 --- a/src/Plugin/Field/FieldWidget/EDTFWidget.php +++ b/src/Plugin/Field/FieldWidget/EDTFWidget.php @@ -10,10 +10,8 @@ /** * Plugin implementation of the 'edtf' widget. * - * Validates text values for compliance with EDTF 1.0, level 1. - * http://www.loc.gov/standards/datetime/pre-submission.html. - * - * // TODO: maybe some day support level 2. + * Validates text values for compliance with EDTF (2018). + * https://www.loc.gov/standards/datetime/edtf.html. * * @FieldWidget( * id = "edtf_default", @@ -32,6 +30,7 @@ public static function defaultSettings() { return [ 'strict_dates' => FALSE, 'intervals' => FALSE, + 'sets' => FALSE, ] + parent::defaultSettings(); } @@ -40,21 +39,19 @@ public static function defaultSettings() { */ public function settingsForm(array $form, FormStateInterface $form_state) { $description_string = $this->t( - 'Negative dates, and the level 1 features unspecified dates, - extended years, and seasons - are not supported with strict date checking.' + 'Most level 1 and 2 features are not supported with strict date checking.' ); $description_string .= '
'; $description_string .= $this->t( 'Uncertain/Approximate dates will have their markers removed before - checking. (For example, "1984~?" will be checked as "1984".)' + checking. (For example, "1984?", "1984~", and "1984%" will be checked as "1984".)' ); $element = parent::settingsForm($form, $form_state); $element['description'] = [ '#type' => 'markup', '#prefix' => '
', '#suffix' => '
', - '#markup' => $this->t('See Library of Congress EDTF Draft Submission for details on formatting options.', ['@locedtf' => 'http://www.loc.gov/standards/datetime/pre-submission.html']), + '#markup' => $this->t('See Library of Congress EDTF Specification for details on formatting options.', ['@locedtf' => 'https://www.loc.gov/standards/datetime/edtf.html']), ]; $element['strict_dates'] = [ '#type' => 'checkbox', @@ -67,6 +64,11 @@ public function settingsForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Permit date intervals.'), '#default_value' => $this->getSetting('intervals'), ]; + $element['sets'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Permit date sets. (Not recommended; make the field repeatable instead.)'), + '#default_value' => $this->getSetting('sets'), + ]; return $element; } @@ -85,6 +87,12 @@ public function settingsSummary() { else { $summary[] = t('Date intervals are not permitted'); } + if ($this->getSetting('sets')) { + $summary[] = t('Date sets permitted'); + } + else { + $summary[] = t('Date sets are not permitted'); + } return $summary; } @@ -115,33 +123,48 @@ public function validate($element, FormStateInterface $form_state) { $form_state->setValueForElement($element, ''); return; } - + // No whitespace. + if (preg_match('/\s/', $value) !== FALSE) { + $form_state->setError($element, t("Dates cannot include spaces.")); + return; + } + // Sets. + if ($this->getSetting('sets')) { + if (strpos($value, '[') !== FALSE || strpos($value, '{') !== FALSE) { + // Test for valid enclosing characters and valid characters inside. + $match = preg_match('/^([\[,\{])[\d,\-,X,Y,E,S,.]*([\],\}])$/', $value); + if (!$match || $match[1] !== $match[2]) { + $form_state->setError($element, t("The set is improperly encoded.")) + } + // Test each date in set. + foreach (preg_split('/(,|\.\.)/', trim($value, '{}[]')) as $date) { + $error_message = $this->dateValidation($date); + if ($error_message) { + $form_state->setError($element, $error_message); + } + } + return; + } + } // Intervals. if ($this->getSetting('intervals')) { if (strpos($value, 'T') !== FALSE) { $form_state->setError($element, t("Date intervals cannot include times.")); } - - list($begin, $end) = explode('/', $value); - // Begin. - $error_message = $this->dateValidation($begin); - if ($error_message) { - $form_state->setError($element, $error_message); - } - // End either empty or valid extended interval values (5.2.3.) - if (empty($end) || $end === 'unknown' || $end === 'open') { - return; - } - $error_message = $this->dateValidation($end); - if ($error_message) { - $form_state->setError($element, $error_message); + foreach (explode('/', $$value) as $date) { + if (!empty($date) && !$date === '..') { + $error_message = $this->dateValidation($begin); + if ($error_message) { + $form_state->setError($element, $error_message); + } + } } + return; } - else { - $error_message = $this->dateValidation($value); - if ($error_message) { - $form_state->setError($element, $error_message); - } + // Single date (we assume at this point). + $error_message = $this->dateValidation($value); + if ($error_message) { + $form_state->setError($element, $error_message); } } @@ -155,27 +178,38 @@ public function validate($element, FormStateInterface $form_state) { * False if valid or a string explaining the reason for invalidation. */ protected function dateValidation($datetime_str) { - + // TODO: Level 2 Unspecified Digits list($date, $time) = explode('T', $datetime_str); $date = trim($date); - $extended_year = (strpos($date, 'y') === 0 ? TRUE : FALSE); - if ($extended_year && $this->getSetting('strict_dates')) { - return "Extended years (5.2.4.) are not supported with the 'strict dates' option enabled."; + $extended_year = (strpos($date, 'Y') === 0 ? TRUE : FALSE); + if (&& $this->getSetting('strict_dates')) { + return "Extended years are not supported with the 'strict dates' option enabled."; + } + // Uncertainty characters on the end are valid Level 1 features. + // But only one should be used. + if (preg_match_all('/[~?%]/', $date) > 1) { + return "Only one uncertainty indicator ('~', '?', and '%') may be used per date." } - // Uncertainty characters on the end are valid Level 1 features (5.2.1.), - // pull them off to make checking the rest easier. - $date = rtrim($date, '?~'); // Negative year? That is fine, but remove it // and the extended year indicator before exploding the date. - $date = ltrim($date, 'y-'); + $date = ltrim($date, 'Y-'); // Now to check the parts. list($year, $month, $day) = explode('-', $date, 3); // Year. - if (!preg_match('/^\d\d(\d\d|\du|uu)$/', $year) && !$extended_year) { + // Pull off uncertainty characters to make checking the rest easier. + $year = trim($year, '?~%'); + // Trim significant digits. + $year = substr($year, 0, strpos($year, 'S')); + // Expand exponents. + if (strps($year, 'E') > 0) { + list($base, $exponent) = explode('E', $year); + $year = strval((10 ** intval($exponent)) * intval($base)); + } + if (!$extended_year && !preg_match('/^\d\d(\d\d|\dX|XX)$/', $year)) { return "The year '$year' is invalid. Please enter a four-digit year."; } elseif ($extended_year && !preg_match('/^\d{5,}$/', $year)) { @@ -184,22 +218,27 @@ protected function dateValidation($datetime_str) { $strict_pattern = 'Y'; // Month. - if (!empty($month) && !preg_match('/^(\d\d|\du|uu)$/', $month)) { + $month = trim($month, '?~%'); + if (!empty($month) && !preg_match('/^(\d\d|\dX|XX)$/', $month)) { return "The month '$month' is invalid. Please enter a two-digit month."; } if (!empty($month)) { - if (strpos($year, 'u') !== FALSE && strpos($month, 'u') === FALSE) { + if (strpos($year, 'X') !== FALSE && strpos($month, 'X') === FALSE) { return "The month must either be blank or unspecified when the year is unspecified."; } + if (strpos($month, 'X') === FALSE && !in_array(intval($month), array_merge(range(1, 12), range(21, 41)))) { + return "The specified month '$month' in '$datetime_str' is invalid."; + } $strict_pattern = 'Y-m'; } // Day. - if (!empty($day) && !preg_match('/^(\d\d|\du|uu)$/', $day)) { + $day = trim($day, '?~%'); + if (!empty($day) && !preg_match('/^(\d\d|\dX|XX)$/', $day)) { return "The day '$day' is invalid. Please enter a two-digit day."; } if (!empty($day)) { - if (strpos($month, 'u') !== FALSE && strpos($day, 'u') === FALSE) { + if (strpos($month, 'X') !== FALSE && strpos($day, 'X') === FALSE) { return "The day must either be blank or unspecified when the month is unspecified."; } $strict_pattern = 'Y-m-d'; @@ -212,9 +251,8 @@ protected function dateValidation($datetime_str) { if ($time) { if (!preg_match('/^-?(\d{4})(-\d{2}){2}T\d{2}(:\d{2}){2}(Z|(\+|-)\d{2}:\d{2})?$/', $datetime_str, $matches)) { - return "The date/time '$datetime_str' is invalid. See EDTF 1.0, 5.1.2."; + return "The date/time '$datetime_str' is invalid."; } - drupal_set_message(print_r($matches, TRUE)); $strict_pattern = 'Y-m-d\TH:i:s'; if (count($matches) > 4) { if ($matches[4] === 'Z') { @@ -228,7 +266,7 @@ protected function dateValidation($datetime_str) { if ($this->getSetting('strict_dates')) { // Clean the date/time string to ensure it parses correctly. - $cleaned_datetime = str_replace('u', '1', $datetime_str); + $cleaned_datetime = str_replace('X', '1', $datetime_str); $datetime_obj = DateTime::createFromFormat('!' . $strict_pattern, $cleaned_datetime); $errors = DateTime::getLastErrors(); if (!$datetime_obj || From b720089bc61490424bb128a4f780629fa9f80f9d Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Fri, 1 Feb 2019 15:23:16 -0800 Subject: [PATCH 02/12] wip edtf 2018 added EDTFUtils --- src/EDTFUtils.php | 235 ++++++++++++++++++ .../Field/FieldFormatter/EDTFFormatter.php | 146 ++++------- src/Plugin/Field/FieldWidget/EDTFWidget.php | 162 +----------- 3 files changed, 284 insertions(+), 259 deletions(-) create mode 100644 src/EDTFUtils.php diff --git a/src/EDTFUtils.php b/src/EDTFUtils.php new file mode 100644 index 0000000..0d0d13f --- /dev/null +++ b/src/EDTFUtils.php @@ -0,0 +1,235 @@ + ['mmm' => 'Jan', 'mmmm' => 'January'], + '02' => ['mmm' => 'Feb', 'mmmm' => 'February'], + '03' => ['mmm' => 'Mar', 'mmmm' => 'March'], + '04' => ['mmm' => 'Apr', 'mmmm' => 'April'], + '05' => ['mmm' => 'May', 'mmmm' => 'May'], + '06' => ['mmm' => 'Jun', 'mmmm' => 'June'], + '07' => ['mmm' => 'Jul', 'mmmm' => 'July'], + '08' => ['mmm' => 'Aug', 'mmmm' => 'August'], + '09' => ['mmm' => 'Sep', 'mmmm' => 'September'], + '10' => ['mmm' => 'Oct', 'mmmm' => 'October'], + '11' => ['mmm' => 'Nov', 'mmmm' => 'November'], + '12' => ['mmm' => 'Dec', 'mmmm' => 'December'], + '21' => ['mmm' => 'Spr', 'mmmm' => 'Spring'], + '22' => ['mmm' => 'Sum', 'mmmm' => 'Summer'], + '23' => ['mmm' => 'Aut', 'mmmm' => 'Autumn'], + '24' => ['mmm' => 'Win', 'mmmm' => 'Winter'], + '25' => ['mmm' => 'Spr', 'mmmm' => 'Spring - Northern Hemisphere'], + '26' => ['mmm' => 'Sum', 'mmmm' => 'Summer - Northern Hemisphere'], + '27' => ['mmm' => 'Aut', 'mmmm' => 'Autumn - Northern Hemisphere'], + '28' => ['mmm' => 'Win', 'mmmm' => 'Winter - Northern Hemisphere'], + '29' => ['mmm' => 'Spr', 'mmmm' => 'Spring - Southern Hemisphere'], + '30' => ['mmm' => 'Sum', 'mmmm' => 'Summer - Southern Hemisphere'], + '31' => ['mmm' => 'Aut', 'mmmm' => 'Autumn - Southern Hemisphere'], + '32' => ['mmm' => 'Win', 'mmmm' => 'Winter - Southern Hemisphere'], + '33' => ['mmm' => 'Q1', 'mmmm' => 'Quarter 1'], + '34' => ['mmm' => 'Q2', 'mmmm' => 'Quarter 2'], + '35' => ['mmm' => 'Q3', 'mmmm' => 'Quarter 3'], + '36' => ['mmm' => 'Q4', 'mmmm' => 'Quarter 4'], + // I'm making up the rest of these abbreviations + // because I can't find standardized ones. + '37' => ['mmm' => 'Quad1', 'mmmm' => 'Quadrimester 1'], + '38' => ['mmm' => 'Quad2', 'mmmm' => 'Quadrimester 2'], + '39' => ['mmm' => 'Quad3', 'mmmm' => 'Quadrimester 3'], + '40' => ['mmm' => 'Sem1', 'mmmm' => 'Semestral 1'], + '41' => ['mmm' => 'Sem2', 'mmmm' => 'Semestral 2'], + ]; + + + /** + * Validate an EDTF expression. + * + * @param string $edtf_text + * The datetime string. + * + * @return array + * Array of error messages. Valid if empty. + */ + public static function validate($edtf_text, $intervals = TRUE, $sets = TRUE, $strict = FALSE) { + $msgs = []; + // Sets. + if ($sets) { + if (strpos($edtf_text, '[') !== FALSE || strpos($edtf_text, '{') !== FALSE) { + // Test for valid enclosing characters and valid characters inside. + $match = preg_match('/^([\[,\{])[\d,\-,X,Y,E,S,.]*([\],\}])$/', $edtf_text); + if (!$match || $match[1] !== $match[2]) { + $msgs[] = "The set is improperly encoded."; + } + // Test each date in set. + foreach (preg_split('/(,|\.\.)/', trim($edtf_text, '{}[]')) as $date) { + $msgs = array_merge($msgs, self::validate_date($date, $strict)); + } + return $msgs; + } + } + // Intervals. + if ($intervals) { + if (strpos($edtf_text, 'T') !== FALSE) { + $msgs[] = "Date intervals cannot include times."; + } + foreach (explode('/', $$edtf_text) as $date) { + if (!empty($date) && !$date === '..') { + $msgs = array_merge($msgs, self::validate_date($date, $strict)); + } + } + return $msgs; + } + // Single date (we assume at this point). + return self::validate_date($edtf_text, $strict); + } + + /** + * Validate a single date. + * + * @param string $datetime_str + * The datetime string. + * + * @return array + * Array of error messages. Valid if empty. + */ + public static function validate_date($datetime_str, $strict = FALSE) { + $msgs = []; + + list($date, $time) = explode('T', $datetime_str); + + preg_match(self::DATE_PARSE_REGEX, $date, $parsed_date); + + // "Something" is wrong with the provided date if it doesn't round-trip. + // Includes (non-exhaustive): + // - no invalid characters present, + // - two-digit months and days, and + // - capturing group qualifiers. + if ($date !== $parsed_date[self::FULL_MATCH]) { + $msgs[] = ["Could not parse the date '$date'",]; + } + + // Year. + if ((strpos($parsed_date[self::YEAR_FULL], 'Y') === 0) { + if ($strict){ + $msgs[] = ["Extended years are not supported with the 'strict dates' option enabled."]; + } + // Expand exponents. + if (!empty($parsed_date[self::YEAR_EXPONENT])) { + $exponent = intval(substr($parsed_date[self::YEAR_EXPONENT], 1)); + $parsed_date[self::YEAR_BASE] = strval((10 ** $exponent) * intval($parsed_date[self::YEAR_BASE])); + $parsed_date[self::YEAR_BASE] = self::expand_year($parsed_date[self::YEAR_FULL], $parsed_date[self::YEAR_BASE], $parsed_date[self::YEAR_EXPONENT]); + } + } elseif (length($parsed_date[self::YEAR_BASE]) > 4) { + $msgs[] = ["Years longer than 4 digits must be prefixed with a 'Y'."]; + } elseif (length($parsed_date[self::YEAR_BASE]) < 4) { + $msgs[] = ["Years must be at least 4 characters long."]; + } + $strict_pattern = 'Y' + + // Month. + if (!array_key_exists(self::MONTH, $parsed_date) && !empty($parsed_date[self::MONTH])) { + // Valid month values? + if (!array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) && + strpos($parsed_date[self::MONTH], 'X') === FALSE ) { + $msgs[] = ["Provided month value '$parsed_date[self::MONTH]' is not valid."]; + } + $strict_pattern = 'Y-m'; + } + + // Day. + if (!array_key_exists(self::DAY) && !empty($parsed_date[self::DAY])) { + // Valid day values? + if (strpos($parsed_date[self::DAY], 'X') === FALSE && + !in_array(intval($parsed_date[self::DAY]), range(1, 31))) { + $msgs[] = ["Provided day value '$parsed_date[self::DAY]' is not valid."]; + } + $strict_pattern = 'Y-m-d'; + } + // Time. + if (strpos($datetime_str, 'T') !== FALSE && empty($time)) { + $msgs[] = "Time not provided with time seperator (T)."; + } + + if ($time) { + if (!preg_match('/^-?(\d{4})(-\d{2}){2}T\d{2}(:\d{2}){2}(Z|(\+|-)\d{2}:\d{2})?$/', $datetime_str, $matches)) { + $msgs[] = "The date/time '$datetime_str' is invalid."; + } + $strict_pattern = 'Y-m-d\TH:i:s'; + if (count($matches) > 4) { + if ($matches[4] === 'Z') { + $strict_pattern .= '\Z'; + } + else { + $strict_pattern .= 'P'; + } + } + } + + if ($strict) { + // Assemble the parts again. + if ($time) { + $cleaned_datetime = $datetime_str; + } else { + $cleaned_datetime = implode('-', [ + $parsed_date[self::YEAR_BASE], + $parsed_date[self::MONTH], + $parsed_date[self::DAY], + ]); + } + $datetime_obj = DateTime::createFromFormat('!' . $strict_pattern, $cleaned_datetime); + $errors = DateTime::getLastErrors(); + if (!$datetime_obj || + !empty($errors['warning_count']) || + // DateTime will create valid dates from Y-m without warning, + // so validate we still have what it was given. + !($cleaned_datetime === $datetime_obj->format($strict_pattern)) + ) { + $msgs[] = "Strictly speaking, the date (and/or time) '$datetime_str' is invalid."; + } + } + + return $msgs; + } + + public static function expand_year($year_full, $year_base, $year_exponent){ + $year = ''; + // Apply negative to base. + // Note that the minus sign can be before or after the 'Y' + // in the full date field; thus, simply check not FALSE. + if (strpos($year_full,'-') !== FALSE) { + $year = '-'; + } + // Expand exponents. + $exponent = intval(substr($year_exponent, 1)); + $year .= strval((10 ** $exponent) * intval($year_base)); + } + +} diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php index 364eda1..d6344cc 100644 --- a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -5,6 +5,7 @@ use Drupal\Core\Field\FormatterBase; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\controlled_access_terms\EDTFUtils; /** * Plugin implementation of the 'TextEDTFFormatter'. @@ -21,49 +22,6 @@ */ class EDTFFormatter extends FormatterBase { - /** - * Month/Season to text map. - * - * @var array - */ - private $MONTHS = [ - '01' => ['mmm' => 'Jan', 'mmmm' => 'January'], - '02' => ['mmm' => 'Feb', 'mmmm' => 'February'], - '03' => ['mmm' => 'Mar', 'mmmm' => 'March'], - '04' => ['mmm' => 'Apr', 'mmmm' => 'April'], - '05' => ['mmm' => 'May', 'mmmm' => 'May'], - '06' => ['mmm' => 'Jun', 'mmmm' => 'June'], - '07' => ['mmm' => 'Jul', 'mmmm' => 'July'], - '08' => ['mmm' => 'Aug', 'mmmm' => 'August'], - '09' => ['mmm' => 'Sep', 'mmmm' => 'September'], - '10' => ['mmm' => 'Oct', 'mmmm' => 'October'], - '11' => ['mmm' => 'Nov', 'mmmm' => 'November'], - '12' => ['mmm' => 'Dec', 'mmmm' => 'December'], - '21' => ['mmm' => 'Spr', 'mmmm' => 'Spring'], - '22' => ['mmm' => 'Sum', 'mmmm' => 'Summer'], - '23' => ['mmm' => 'Aut', 'mmmm' => 'Autumn'], - '24' => ['mmm' => 'Win', 'mmmm' => 'Winter'], - '25' => ['mmm' => 'Spr', 'mmmm' => 'Spring - Northern Hemisphere'], - '26' => ['mmm' => 'Sum', 'mmmm' => 'Summer - Northern Hemisphere'], - '27' => ['mmm' => 'Aut', 'mmmm' => 'Autumn - Northern Hemisphere'], - '28' => ['mmm' => 'Win', 'mmmm' => 'Winter - Northern Hemisphere'], - '29' => ['mmm' => 'Spr', 'mmmm' => 'Spring - Southern Hemisphere'], - '30' => ['mmm' => 'Sum', 'mmmm' => 'Summer - Southern Hemisphere'], - '31' => ['mmm' => 'Aut', 'mmmm' => 'Autumn - Southern Hemisphere'], - '32' => ['mmm' => 'Win', 'mmmm' => 'Winter - Southern Hemisphere'], - '33' => ['mmm' => 'Q1', 'mmmm' => 'Quarter 1'], - '34' => ['mmm' => 'Q2', 'mmmm' => 'Quarter 2'], - '35' => ['mmm' => 'Q3', 'mmmm' => 'Quarter 3'], - '36' => ['mmm' => 'Q4', 'mmmm' => 'Quarter 4'], - // I'm making up the rest of these abbreviations - // because I can't find standardized ones. - '37' => ['mmm' => 'Quad1', 'mmmm' => 'Quadrimester 1'], - '38' => ['mmm' => 'Quad2', 'mmmm' => 'Quadrimester 2'], - '39' => ['mmm' => 'Quad3', 'mmmm' => 'Quadrimester 3'], - '40' => ['mmm' => 'Sem1', 'mmmm' => 'Semestral 1'], - '41' => ['mmm' => 'Sem2', 'mmmm' => 'Semestral 2'], - ]; - /** * Various delimiters. * @@ -214,94 +172,78 @@ public function viewElements(FieldItemListInterface $items, $langcode) { * The date in EDTF format. */ protected function formatDate($edtf_text) { - $settings = $this->getSettings(); - $cleaned_datetime = $edtf_text; - // TODO: Time? - $qualifiers_format = '%s'; - // Uncertainty. - // TODO: Group Qualification - if (!(strpos($edtf_text, '~') === FALSE)) { - $qualifiers_format = t('approximately'); - $qualifiers_format .= ' %s'; - } - if (!(strpos($edtf_text, '?') === FALSE)) { - $qualifiers_format = '%s '; - $qualifiers_format .= t('(uncertain)'); - } - if (!(strpos($edtf_text, '%') === FALSE)) { - $qualifiers_format = '%s '; - $qualifiers_format .= t('(approximate and uncertain)'); - } - $cleaned_datetime = str_replace(['?', '~', '%'], '', $cleaned_datetime); - list($year, $month, $day) = explode('-', $cleaned_datetime, 3); + list($date, $time) = explode('T', $datetime_str); - // Which unspecified, if any? - $which_unspecified = ''; - if (!(strpos($year, 'XX') === FALSE)) { - $which_unspecified = t('decade'); - } - if (!(strpos($year, 'X') === FALSE)) { - $which_unspecified = t('year'); + // Formatted versions of the date elements. + $year = ''; + $month = ''; + $day = ''; + + preg_match(EDTFUtils::DATE_PARSE_REGEX, $date, $parsed_date); + $parsed_date[EDTFUtils::YEAR_BASE] = EDTFUtils::expand_year($parsed_date[EDTFUtils::YEAR_FULL], $parsed_date[EDTFUtils::YEAR_BASE], $parsed_date[EDTFUtils::YEAR_EXPONENT]); + $settings = $this->getSettings(); + + // Unspecified. + $unspecified = []; + if (strpos($parsed_date[EDTFUtils::YEAR_BASE], 'XXXX') !== FALSE) { + $unspecified[] = t('year'); } - if (!(strpos($month, 'X') === FALSE)) { - $which_unspecified = t('month'); - // No partial months. - $month = ''; + elseif (strpos($parsed_date[EDTFUtils::YEAR_BASE], 'XXX') !== FALSE) { + $unspecified[] = t('century'); } - if (!(strpos($day, 'X') === FALSE)) { - $which_unspecified = t('day'); - // No partial days. - $day = ''; + elseif (strpos($parsed_date[EDTFUtils::YEAR_BASE], 'XX') !== FALSE) { + $unspecified[] = t('decade'); } - // Add unspecified formatting if needed. - if (!empty($which_unspecified)) { - $qualifiers_format = t('an unspecified @part in', ['@part' => $which_unspecified]) . ' ' . $qualifiers_format; + elseif (strpos($parsed_date[EDTFUtils::YEAR_BASE], 'XXXX') !== FALSE) { + $unspecified[] = t('year'); } - // Clean-up unspecified year/decade. - if (!(strpos($year, 'X') === FALSE)) { - $year = str_replace('X', '0', $year); - $year = t("the @year's", ['@year' => $year]); - } + $year = str_replace('X', '0', $parsed_date[EDTFUtils::YEAR_BASE]); - // Format the month. - if (!empty($month)) { + if (array_key_exists(self::MONTH, $parsed_date) && strpos($parsed_date[EDTFUtils::MONTH], 'X') !== FALSE) { + $unspecified[] = t('month'); // IF 'mm', do nothing, it is already in this format. if ($settings['month_format'] === 'mmm' || $settings['month_format'] === 'mmmm') { - $month = $this->MONTHS[$month][$settings['month_format']]; + $month = EDTFUtils::MONTHS_MAP[$parsed_date[EDTFUtils::MONTH]][$settings['month_format']]; } if ($settings['month_format'] === 'm') { - $month = ltrim($month, ' 0'); + $month = ltrim($parsed_date[EDTFUtils::MONTH], ' 0'); } } - - // Format the day. - if (!empty($day)) { + if (array_key_exists(self::DAY, $parsed_date) && strpos($parsed_date[EDTFUtils::DAY], 'X') !== FALSE) { + $unspecified[] = t('day'); if ($settings['day_format'] === 'd') { - $day = ltrim($day, ' 0'); + $day = ltrim($parsed_date[EDTFUtils::DAY], ' 0'); + } + else { + $day = $parsed_date[EDTFUtils::DAY]; } } - // Put the parts back together - // Big Endian by default. - $parts_in_order = [$year, $month, $day]; - + // TODO: Qualified. + // Put the parts back together. if ($settings['date_order'] === 'little_endian') { $parts_in_order = [$day, $month, $year]; } elseif ($settings['date_order'] === 'middle_endian') { $parts_in_order = [$month, $day, $year]; - } // Big Endian by default + } + else { + // Big Endian by default. + $parts_in_order = [$year, $month, $day]; + } if ($settings['date_order'] === 'middle_endian' && !preg_match('/\d/', $month) && !empty(array_filter([$month, $day]))) { - $cleaned_datetime = "$month $day, $year"; + $formatted_date = "$month $day, $year"; } else { - $cleaned_datetime = implode($this->DELIMITERS[$settings['date_separator']], array_filter($parts_in_order)); + $formatted_date = implode($this->DELIMITERS[$settings['date_separator']], array_filter($parts_in_order)); } - return sprintf($qualifiers_format, $cleaned_datetime); + // TODO: Time. + // Return sprintf($qualifiers_format, $formatted_date);. + return $formatted_date; } } diff --git a/src/Plugin/Field/FieldWidget/EDTFWidget.php b/src/Plugin/Field/FieldWidget/EDTFWidget.php index 30369e0..2b6b52a 100644 --- a/src/Plugin/Field/FieldWidget/EDTFWidget.php +++ b/src/Plugin/Field/FieldWidget/EDTFWidget.php @@ -2,10 +2,10 @@ namespace Drupal\controlled_access_terms\Plugin\Field\FieldWidget; -use Datetime; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\controlled_access_terms\EDTFUtils; /** * Plugin implementation of the 'edtf' widget. @@ -123,164 +123,12 @@ public function validate($element, FormStateInterface $form_state) { $form_state->setValueForElement($element, ''); return; } - // No whitespace. - if (preg_match('/\s/', $value) !== FALSE) { - $form_state->setError($element, t("Dates cannot include spaces.")); - return; - } - // Sets. - if ($this->getSetting('sets')) { - if (strpos($value, '[') !== FALSE || strpos($value, '{') !== FALSE) { - // Test for valid enclosing characters and valid characters inside. - $match = preg_match('/^([\[,\{])[\d,\-,X,Y,E,S,.]*([\],\}])$/', $value); - if (!$match || $match[1] !== $match[2]) { - $form_state->setError($element, t("The set is improperly encoded.")) - } - // Test each date in set. - foreach (preg_split('/(,|\.\.)/', trim($value, '{}[]')) as $date) { - $error_message = $this->dateValidation($date); - if ($error_message) { - $form_state->setError($element, $error_message); - } - } - return; - } - } - // Intervals. - if ($this->getSetting('intervals')) { - if (strpos($value, 'T') !== FALSE) { - $form_state->setError($element, t("Date intervals cannot include times.")); + $errors = EDTFUtils::validate($value, $this->getSetting('intervals'), $this->getSetting('sets'), $this->getSetting('strict_dates')); + if (!empty($errors)) { + foreach ($errors as $err) { + $form_state->setError($element, $err); } - foreach (explode('/', $$value) as $date) { - if (!empty($date) && !$date === '..') { - $error_message = $this->dateValidation($begin); - if ($error_message) { - $form_state->setError($element, $error_message); - } - } - } - return; - } - // Single date (we assume at this point). - $error_message = $this->dateValidation($value); - if ($error_message) { - $form_state->setError($element, $error_message); - } - } - - /** - * Validate a date. - * - * @param string $datetime_str - * The datetime string. - * - * @return bool|string - * False if valid or a string explaining the reason for invalidation. - */ - protected function dateValidation($datetime_str) { - // TODO: Level 2 Unspecified Digits - list($date, $time) = explode('T', $datetime_str); - - $date = trim($date); - $extended_year = (strpos($date, 'Y') === 0 ? TRUE : FALSE); - if (&& $this->getSetting('strict_dates')) { - return "Extended years are not supported with the 'strict dates' option enabled."; - } - // Uncertainty characters on the end are valid Level 1 features. - // But only one should be used. - if (preg_match_all('/[~?%]/', $date) > 1) { - return "Only one uncertainty indicator ('~', '?', and '%') may be used per date." - } - - // Negative year? That is fine, but remove it - // and the extended year indicator before exploding the date. - $date = ltrim($date, 'Y-'); - - // Now to check the parts. - list($year, $month, $day) = explode('-', $date, 3); - - // Year. - // Pull off uncertainty characters to make checking the rest easier. - $year = trim($year, '?~%'); - // Trim significant digits. - $year = substr($year, 0, strpos($year, 'S')); - // Expand exponents. - if (strps($year, 'E') > 0) { - list($base, $exponent) = explode('E', $year); - $year = strval((10 ** intval($exponent)) * intval($base)); - } - if (!$extended_year && !preg_match('/^\d\d(\d\d|\dX|XX)$/', $year)) { - return "The year '$year' is invalid. Please enter a four-digit year."; - } - elseif ($extended_year && !preg_match('/^\d{5,}$/', $year)) { - return "Invalid extended year. Please enter at least a four-digit year."; } - $strict_pattern = 'Y'; - - // Month. - $month = trim($month, '?~%'); - if (!empty($month) && !preg_match('/^(\d\d|\dX|XX)$/', $month)) { - return "The month '$month' is invalid. Please enter a two-digit month."; - } - if (!empty($month)) { - if (strpos($year, 'X') !== FALSE && strpos($month, 'X') === FALSE) { - return "The month must either be blank or unspecified when the year is unspecified."; - } - if (strpos($month, 'X') === FALSE && !in_array(intval($month), array_merge(range(1, 12), range(21, 41)))) { - return "The specified month '$month' in '$datetime_str' is invalid."; - } - $strict_pattern = 'Y-m'; - } - - // Day. - $day = trim($day, '?~%'); - if (!empty($day) && !preg_match('/^(\d\d|\dX|XX)$/', $day)) { - return "The day '$day' is invalid. Please enter a two-digit day."; - } - if (!empty($day)) { - if (strpos($month, 'X') !== FALSE && strpos($day, 'X') === FALSE) { - return "The day must either be blank or unspecified when the month is unspecified."; - } - $strict_pattern = 'Y-m-d'; - } - - // Time. - if (strpos($datetime_str, 'T') !== FALSE && empty($time)) { - return "Time not provided with time seperator (T)."; - } - - if ($time) { - if (!preg_match('/^-?(\d{4})(-\d{2}){2}T\d{2}(:\d{2}){2}(Z|(\+|-)\d{2}:\d{2})?$/', $datetime_str, $matches)) { - return "The date/time '$datetime_str' is invalid."; - } - $strict_pattern = 'Y-m-d\TH:i:s'; - if (count($matches) > 4) { - if ($matches[4] === 'Z') { - $strict_pattern .= '\Z'; - } - else { - $strict_pattern .= 'P'; - } - } - } - - if ($this->getSetting('strict_dates')) { - // Clean the date/time string to ensure it parses correctly. - $cleaned_datetime = str_replace('X', '1', $datetime_str); - $datetime_obj = DateTime::createFromFormat('!' . $strict_pattern, $cleaned_datetime); - $errors = DateTime::getLastErrors(); - if (!$datetime_obj || - !empty($errors['warning_count']) || - // DateTime will create valid dates from Y-m without warning, - // so validate we still have what it was given. - !($cleaned_datetime === $datetime_obj->format($strict_pattern)) - ) { - return "Strictly speaking, the date (and/or time) '$datetime_str' is invalid."; - } - - } - - return FALSE; } } From 12ff890e7186e36b4bc4b2be804aa533774147c4 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Tue, 5 Feb 2019 12:52:38 -0800 Subject: [PATCH 03/12] edtf 2018 level 2 support --- src/EDTFUtils.php | 108 ++++++---- .../Field/FieldFormatter/EDTFFormatter.php | 195 ++++++++++++++++-- src/Plugin/Field/FieldWidget/EDTFWidget.php | 4 +- 3 files changed, 248 insertions(+), 59 deletions(-) diff --git a/src/EDTFUtils.php b/src/EDTFUtils.php index 0d0d13f..1ba2a22 100644 --- a/src/EDTFUtils.php +++ b/src/EDTFUtils.php @@ -1,4 +1,4 @@ - ['mmm' => 'Sem2', 'mmmm' => 'Semestral 2'], ]; - /** * Validate an EDTF expression. * * @param string $edtf_text * The datetime string. + * @param bool $intervals + * Are interval expressions permitted? + * @param bool $sets + * Are set expressions permitted? + * @param bool $strict + * Are only valid calendar dates permitted? * * @return array * Array of error messages. Valid if empty. @@ -90,7 +95,7 @@ public static function validate($edtf_text, $intervals = TRUE, $sets = TRUE, $st } // Test each date in set. foreach (preg_split('/(,|\.\.)/', trim($edtf_text, '{}[]')) as $date) { - $msgs = array_merge($msgs, self::validate_date($date, $strict)); + $msgs = array_merge($msgs, self::validateDate($date, $strict)); } return $msgs; } @@ -102,13 +107,13 @@ public static function validate($edtf_text, $intervals = TRUE, $sets = TRUE, $st } foreach (explode('/', $$edtf_text) as $date) { if (!empty($date) && !$date === '..') { - $msgs = array_merge($msgs, self::validate_date($date, $strict)); + $msgs = array_merge($msgs, self::validateDate($date, $strict)); } } return $msgs; } // Single date (we assume at this point). - return self::validate_date($edtf_text, $strict); + return self::validateDate($edtf_text, $strict); } /** @@ -116,11 +121,13 @@ public static function validate($edtf_text, $intervals = TRUE, $sets = TRUE, $st * * @param string $datetime_str * The datetime string. + * @param bool $strict + * Are only valid calendar dates permitted? * * @return array * Array of error messages. Valid if empty. */ - public static function validate_date($datetime_str, $strict = FALSE) { + public static function validateDate($datetime_str, $strict = FALSE) { $msgs = []; list($date, $time) = explode('T', $datetime_str); @@ -129,37 +136,39 @@ public static function validate_date($datetime_str, $strict = FALSE) { // "Something" is wrong with the provided date if it doesn't round-trip. // Includes (non-exhaustive): - // - no invalid characters present, - // - two-digit months and days, and - // - capturing group qualifiers. + // - no invalid characters present, + // - two-digit months and days, and + // - capturing group qualifiers. if ($date !== $parsed_date[self::FULL_MATCH]) { - $msgs[] = ["Could not parse the date '$date'",]; + $msgs[] = "Could not parse the date '$date'"; } // Year. - if ((strpos($parsed_date[self::YEAR_FULL], 'Y') === 0) { - if ($strict){ - $msgs[] = ["Extended years are not supported with the 'strict dates' option enabled."]; + if (strpos($parsed_date[self::YEAR_FULL], 'Y') === 0) { + if ($strict) { + $msgs[] = "Extended years are not supported with the 'strict dates' option enabled."; } // Expand exponents. if (!empty($parsed_date[self::YEAR_EXPONENT])) { $exponent = intval(substr($parsed_date[self::YEAR_EXPONENT], 1)); $parsed_date[self::YEAR_BASE] = strval((10 ** $exponent) * intval($parsed_date[self::YEAR_BASE])); - $parsed_date[self::YEAR_BASE] = self::expand_year($parsed_date[self::YEAR_FULL], $parsed_date[self::YEAR_BASE], $parsed_date[self::YEAR_EXPONENT]); + $parsed_date[self::YEAR_BASE] = self::expandYear($parsed_date[self::YEAR_FULL], $parsed_date[self::YEAR_BASE], $parsed_date[self::YEAR_EXPONENT]); } - } elseif (length($parsed_date[self::YEAR_BASE]) > 4) { - $msgs[] = ["Years longer than 4 digits must be prefixed with a 'Y'."]; - } elseif (length($parsed_date[self::YEAR_BASE]) < 4) { - $msgs[] = ["Years must be at least 4 characters long."]; } - $strict_pattern = 'Y' + elseif (strlen(ltrim($parsed_date[self::YEAR_BASE], '-')) > 4) { + $msgs[] = "Years longer than 4 digits must be prefixed with a 'Y'."; + } + elseif (strlen($parsed_date[self::YEAR_BASE]) < 4) { + $msgs[] = "Years must be at least 4 characters long."; + } + $strict_pattern = 'Y'; // Month. if (!array_key_exists(self::MONTH, $parsed_date) && !empty($parsed_date[self::MONTH])) { // Valid month values? if (!array_key_exists($parsed_date[self::MONTH], self::MONTHS_MAP) && - strpos($parsed_date[self::MONTH], 'X') === FALSE ) { - $msgs[] = ["Provided month value '$parsed_date[self::MONTH]' is not valid."]; + strpos($parsed_date[self::MONTH], 'X') === FALSE) { + $msgs[] = "Provided month value '" . $parsed_date[self::MONTH] . "' is not valid."; } $strict_pattern = 'Y-m'; } @@ -169,7 +178,7 @@ public static function validate_date($datetime_str, $strict = FALSE) { // Valid day values? if (strpos($parsed_date[self::DAY], 'X') === FALSE && !in_array(intval($parsed_date[self::DAY]), range(1, 31))) { - $msgs[] = ["Provided day value '$parsed_date[self::DAY]' is not valid."]; + $msgs[] = "Provided day value '" . $parsed_date[self::DAY] . "' is not valid."; } $strict_pattern = 'Y-m-d'; } @@ -197,7 +206,8 @@ public static function validate_date($datetime_str, $strict = FALSE) { // Assemble the parts again. if ($time) { $cleaned_datetime = $datetime_str; - } else { + } + else { $cleaned_datetime = implode('-', [ $parsed_date[self::YEAR_BASE], $parsed_date[self::MONTH], @@ -219,17 +229,27 @@ public static function validate_date($datetime_str, $strict = FALSE) { return $msgs; } - public static function expand_year($year_full, $year_base, $year_exponent){ - $year = ''; - // Apply negative to base. - // Note that the minus sign can be before or after the 'Y' - // in the full date field; thus, simply check not FALSE. - if (strpos($year_full,'-') !== FALSE) { - $year = '-'; + /** + * Expand an exponent year. + * + * @param string $year_full + * The full year expression from the EDTF string. + * @param string $year_base + * The base expression from the EDTF string. + * @param string $year_exponent + * The exponent expression from the EDTF string. + * + * @return string + * The expanded year value. + */ + public static function expandYear($year_full, $year_base, $year_exponent) { + if (!empty($year_exponent)) { + $exponent = intval(substr($year_exponent, 1)); + return strval((10 ** $exponent) * intval($year_base)); + } + else { + return $year_base; } - // Expand exponents. - $exponent = intval(substr($year_exponent, 1)); - $year .= strval((10 ** $exponent) * intval($year_base)); } } diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php index d6344cc..634ccbe 100644 --- a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -173,7 +173,7 @@ public function viewElements(FieldItemListInterface $items, $langcode) { */ protected function formatDate($edtf_text) { - list($date, $time) = explode('T', $datetime_str); + list($date, $time) = explode('T', $edtf_text); // Formatted versions of the date elements. $year = ''; @@ -181,7 +181,8 @@ protected function formatDate($edtf_text) { $day = ''; preg_match(EDTFUtils::DATE_PARSE_REGEX, $date, $parsed_date); - $parsed_date[EDTFUtils::YEAR_BASE] = EDTFUtils::expand_year($parsed_date[EDTFUtils::YEAR_FULL], $parsed_date[EDTFUtils::YEAR_BASE], $parsed_date[EDTFUtils::YEAR_EXPONENT]); + + $parsed_date[EDTFUtils::YEAR_BASE] = EDTFUtils::expandYear($parsed_date[EDTFUtils::YEAR_FULL], $parsed_date[EDTFUtils::YEAR_BASE], $parsed_date[EDTFUtils::YEAR_EXPONENT]); $settings = $this->getSettings(); // Unspecified. @@ -195,25 +196,33 @@ protected function formatDate($edtf_text) { elseif (strpos($parsed_date[EDTFUtils::YEAR_BASE], 'XX') !== FALSE) { $unspecified[] = t('decade'); } - elseif (strpos($parsed_date[EDTFUtils::YEAR_BASE], 'XXXX') !== FALSE) { + elseif (strpos($parsed_date[EDTFUtils::YEAR_BASE], 'X') !== FALSE) { $unspecified[] = t('year'); } // Clean-up unspecified year/decade. $year = str_replace('X', '0', $parsed_date[EDTFUtils::YEAR_BASE]); - if (array_key_exists(self::MONTH, $parsed_date) && strpos($parsed_date[EDTFUtils::MONTH], 'X') !== FALSE) { - $unspecified[] = t('month'); + if (array_key_exists(EDTFUtils::MONTH, $parsed_date)) { + if (strpos($parsed_date[EDTFUtils::MONTH], 'X') !== FALSE) { + $unspecified[] = t('month'); + } // IF 'mm', do nothing, it is already in this format. if ($settings['month_format'] === 'mmm' || $settings['month_format'] === 'mmmm') { $month = EDTFUtils::MONTHS_MAP[$parsed_date[EDTFUtils::MONTH]][$settings['month_format']]; } - if ($settings['month_format'] === 'm') { + elseif ($settings['month_format'] === 'm') { $month = ltrim($parsed_date[EDTFUtils::MONTH], ' 0'); } + else { + $month = $parsed_date[EDTFUtils::MONTH]; + } } - if (array_key_exists(self::DAY, $parsed_date) && strpos($parsed_date[EDTFUtils::DAY], 'X') !== FALSE) { - $unspecified[] = t('day'); - if ($settings['day_format'] === 'd') { + + if (array_key_exists(EDTFUtils::DAY, $parsed_date)) { + if (strpos($parsed_date[EDTFUtils::DAY], 'X') !== FALSE) { + $unspecified[] = t('day'); + } + elseif ($settings['day_format'] === 'd') { $day = ltrim($parsed_date[EDTFUtils::DAY], ' 0'); } else { @@ -221,7 +230,6 @@ protected function formatDate($edtf_text) { } } - // TODO: Qualified. // Put the parts back together. if ($settings['date_order'] === 'little_endian') { $parts_in_order = [$day, $month, $year]; @@ -241,8 +249,171 @@ protected function formatDate($edtf_text) { $formatted_date = implode($this->DELIMITERS[$settings['date_separator']], array_filter($parts_in_order)); } - // TODO: Time. - // Return sprintf($qualifiers_format, $formatted_date);. + // Time. + // TODO: Add time formatting options. + if (isset($time) && !empty($time)) { + $formatted_date .= ' ' . $time; + } + + // Unspecified. + // Year = 1, Month = 2, Day = 4. + switch (count($unspecified)) { + case 1: + $formatted_date = t('unspecified @time_unit in @date', [ + '@time_unit' => $unspecified[0], + '@date' => $formatted_date, + ]); + break; + + case 2: + $formatted_date = t('unspecified @time_unit1 and @time_unit2 in @date', [ + '@time_unit1' => $unspecified[0], + '@time_unit2' => $unspecified[1], + '@date' => $formatted_date, + ]); + break; + + case 3: + $formatted_date = t('unspecified @time_unit1, @time_unit2, and @time_unit3 in @date', [ + '@time_unit1' => $unspecified[0], + '@time_unit2' => $unspecified[1], + '@time_unit2' => $unspecified[2], + '@date' => $formatted_date, + ]); + break; + } + + // Qualified. + // This is ugly and terrible, but I'm out of ideas for simplifying it. + $qualifiers = [ + 'uncertain' => [], + 'approximate' => [], + ]; + if (array_key_exists(EDTFUtils::QUALIFIER_YEAR, $parsed_date) && !empty($parsed_date[EDTFUtils::QUALIFIER_YEAR])) { + switch ($parsed_date[EDTFUtils::QUALIFIER_YEAR]) { + case '?': + $qualifiers['uncertain']['year'] = TRUE; + break; + + case '~': + $qualifiers['approximate']['year'] = TRUE; + break; + + case '%': + $qualifiers['uncertain']['year'] = TRUE; + $qualifiers['approximate']['year'] = TRUE; + break; + } + } + if (array_key_exists(EDTFUtils::QUALIFIER_YEAR_ONLY, $parsed_date) && !empty($parsed_date[EDTFUtils::QUALIFIER_YEAR_ONLY])) { + switch ($parsed_date[EDTFUtils::QUALIFIER_YEAR_ONLY]) { + case '?': + $qualifiers['uncertain']['year'] = TRUE; + break; + + case '~': + $qualifiers['approximate']['year'] = TRUE; + break; + + case '%': + $qualifiers['uncertain']['year'] = TRUE; + $qualifiers['approximate']['year'] = TRUE; + break; + } + } + if (array_key_exists(EDTFUtils::QUALIFIER_MONTH, $parsed_date) && !empty($parsed_date[EDTFUtils::QUALIFIER_MONTH])) { + switch ($parsed_date[EDTFUtils::QUALIFIER_MONTH]) { + case '?': + $qualifiers['uncertain']['year'] = TRUE; + $qualifiers['uncertain']['month'] = TRUE; + break; + + case '~': + $qualifiers['approximate']['year'] = TRUE; + $qualifiers['approximate']['month'] = TRUE; + break; + + case '%': + $qualifiers['uncertain']['year'] = TRUE; + $qualifiers['uncertain']['month'] = TRUE; + $qualifiers['approximate']['year'] = TRUE; + $qualifiers['approximate']['month'] = TRUE; + break; + } + } + if (array_key_exists(EDTFUtils::QUALIFIER_MONTH_ONLY, $parsed_date) && !empty($parsed_date[EDTFUtils::QUALIFIER_MONTH_ONLY])) { + switch ($parsed_date[EDTFUtils::QUALIFIER_MONTH_ONLY]) { + case '?': + $qualifiers['uncertain']['month'] = TRUE; + break; + + case '~': + $qualifiers['approximate']['month'] = TRUE; + break; + + case '%': + $qualifiers['uncertain']['month'] = TRUE; + $qualifiers['approximate']['month'] = TRUE; + break; + } + } + if (array_key_exists(EDTFUtils::QUALIFIER_DAY, $parsed_date) && !empty($parsed_date[EDTFUtils::QUALIFIER_DAY])) { + switch ($parsed_date[EDTFUtils::QUALIFIER_DAY]) { + case '?': + $qualifiers['uncertain']['year'] = TRUE; + $qualifiers['uncertain']['month'] = TRUE; + $qualifiers['uncertain']['day'] = TRUE; + break; + + case '~': + $qualifiers['approximate']['year'] = TRUE; + $qualifiers['approximate']['month'] = TRUE; + $qualifiers['approximate']['day'] = TRUE; + break; + + case '%': + $qualifiers['uncertain']['year'] = TRUE; + $qualifiers['uncertain']['month'] = TRUE; + $qualifiers['uncertain']['day'] = TRUE; + $qualifiers['approximate']['year'] = TRUE; + $qualifiers['approximate']['month'] = TRUE; + $qualifiers['approximate']['day'] = TRUE; + break; + } + } + if (array_key_exists(EDTFUtils::QUALIFIER_DAY_ONLY, $parsed_date) && !empty($parsed_date[EDTFUtils::QUALIFIER_DAY_ONLY])) { + switch ($parsed_date[EDTFUtils::QUALIFIER_DAY_ONLY]) { + case '?': + $qualifiers['uncertain']['day'] = TRUE; + break; + + case '~': + $qualifiers['approximate']['day'] = TRUE; + break; + + case '%': + $qualifiers['uncertain']['day'] = TRUE; + $qualifiers['approximate']['day'] = TRUE; + break; + } + } + $qualifier_parts = []; + foreach ($qualifiers as $qualifier => $parts) { + $keys = array_keys($parts); + switch (count($keys)) { + case 1: + case 2: + $qualifier_parts[] = implode(' ' . t('and') . ' ', $keys) . ' ' . $qualifier; + break; + + case 3: + $qualifier_parts[] = $qualifier; + break; + } + } + if (count($qualifier_parts) > 0) { + return $formatted_date . ' (' . implode('; ', $qualifier_parts) . ')'; + } return $formatted_date; } diff --git a/src/Plugin/Field/FieldWidget/EDTFWidget.php b/src/Plugin/Field/FieldWidget/EDTFWidget.php index 2b6b52a..2d5d914 100644 --- a/src/Plugin/Field/FieldWidget/EDTFWidget.php +++ b/src/Plugin/Field/FieldWidget/EDTFWidget.php @@ -125,9 +125,7 @@ public function validate($element, FormStateInterface $form_state) { } $errors = EDTFUtils::validate($value, $this->getSetting('intervals'), $this->getSetting('sets'), $this->getSetting('strict_dates')); if (!empty($errors)) { - foreach ($errors as $err) { - $form_state->setError($element, $err); - } + $form_state->setError($element, implode("\n", $errors)); } } From 6fcc81c436f311f4ad802719168a007abfd7207a Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Tue, 5 Feb 2019 14:03:02 -0800 Subject: [PATCH 04/12] removed unused config --- ...entity_view_display.taxonomy_term.corporate_body.default.yml | 2 -- .../core.entity_view_display.taxonomy_term.family.default.yml | 2 -- .../core.entity_view_display.taxonomy_term.person.default.yml | 2 -- 3 files changed, 6 deletions(-) diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.corporate_body.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.corporate_body.default.yml index 3133cd5..ae89e1a 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.corporate_body.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.corporate_body.default.yml @@ -45,7 +45,6 @@ content: date_order: big_endian month_format: mm day_format: dd - season_hemisphere: north third_party_settings: { } type: edtf_default region: content @@ -57,7 +56,6 @@ content: date_order: big_endian month_format: mm day_format: dd - season_hemisphere: north third_party_settings: { } type: edtf_default region: content diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.family.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.family.default.yml index 05d4f07..5d90208 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.family.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.family.default.yml @@ -42,7 +42,6 @@ content: date_order: big_endian month_format: mm day_format: dd - season_hemisphere: north third_party_settings: { } type: edtf_default region: content @@ -54,7 +53,6 @@ content: date_order: big_endian month_format: mm day_format: dd - season_hemisphere: north third_party_settings: { } type: edtf_default region: content diff --git a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.person.default.yml b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.person.default.yml index 5f7245f..c71da8d 100644 --- a/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.person.default.yml +++ b/modules/controlled_access_terms_default_configuration/config/install/core.entity_view_display.taxonomy_term.person.default.yml @@ -45,7 +45,6 @@ content: date_order: little_endian month_format: mmm day_format: dd - season_hemisphere: north third_party_settings: { } type: edtf_default region: content @@ -57,7 +56,6 @@ content: date_order: little_endian month_format: mmm day_format: dd - season_hemisphere: north third_party_settings: { } type: edtf_default region: content From ade46b6bbc80b3fca35b3cde548b94e3041786af Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Tue, 5 Feb 2019 14:04:15 -0800 Subject: [PATCH 05/12] fix edtf sets --- src/EDTFUtils.php | 4 ++- .../Field/FieldFormatter/EDTFFormatter.php | 26 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/EDTFUtils.php b/src/EDTFUtils.php index 1ba2a22..4f96dbc 100644 --- a/src/EDTFUtils.php +++ b/src/EDTFUtils.php @@ -95,7 +95,9 @@ public static function validate($edtf_text, $intervals = TRUE, $sets = TRUE, $st } // Test each date in set. foreach (preg_split('/(,|\.\.)/', trim($edtf_text, '{}[]')) as $date) { - $msgs = array_merge($msgs, self::validateDate($date, $strict)); + if (!empty($date)) { + $msgs = array_merge($msgs, self::validateDate($date, $strict)); + } } return $msgs; } diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php index 634ccbe..28b3704 100644 --- a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -143,8 +143,30 @@ public function viewElements(FieldItemListInterface $items, $langcode) { // Sets. if (strpos($item->value, '[') !== FALSE || strpos($item->value, '{') !== FALSE) { $set_qualifier = (strpos($item->value, '[') !== FALSE) ? t('one of the dates:') : t('all of the dates:'); - foreach (preg_split('/(,|\.\.)/', trim($item->value, '{}[]')) as $date) { - $formatted_dates[] = $this->formatDate($date); + foreach (explode(',', trim($item->value, '{}[] ')) as $date) { + $date_range = explode('..', $date); + switch (count($date_range)) { + case 1: + $formatted_dates[] = $this->formatDate($date); + break; + + case 2: + if (empty($date_range[0])) { + $formatted_dates[] = t('@date or some earlier date', [ + '@date' => $this->formatDate($date_range[1]) + ]); + } elseif (empty($date_range[1])) { + $formatted_dates[] = t('@date or some later date', [ + '@date' => $this->formatDate($date_range[0]) + ]); + } else { + $formatted_dates[] = t('@date_begin until @date_end', [ + '@date_begin' => $this->formatDate($date_range[0]), + '@date_end' => $this->formatDate($date_range[1]), + ]); + } + break; + } } $element[$delta] = [ '#markup' => t('@qualifier @list', [ From bff0159cbb2b286aef16edbd2090f6853bc8851c Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Wed, 6 Feb 2019 08:26:10 -0800 Subject: [PATCH 06/12] coding standards --- src/Plugin/Field/FieldFormatter/EDTFFormatter.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php index 28b3704..afbab76 100644 --- a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -153,13 +153,15 @@ public function viewElements(FieldItemListInterface $items, $langcode) { case 2: if (empty($date_range[0])) { $formatted_dates[] = t('@date or some earlier date', [ - '@date' => $this->formatDate($date_range[1]) + '@date' => $this->formatDate($date_range[1]), ]); - } elseif (empty($date_range[1])) { + } + elseif (empty($date_range[1])) { $formatted_dates[] = t('@date or some later date', [ - '@date' => $this->formatDate($date_range[0]) + '@date' => $this->formatDate($date_range[0]), ]); - } else { + } + else { $formatted_dates[] = t('@date_begin until @date_end', [ '@date_begin' => $this->formatDate($date_range[0]), '@date_end' => $this->formatDate($date_range[1]), From 3bfb6c8c4bbd89fc3f6aac6154304bf062fbc212 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Wed, 6 Feb 2019 09:46:44 -0800 Subject: [PATCH 07/12] fix unspecified month bug --- src/Plugin/Field/FieldFormatter/EDTFFormatter.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php index afbab76..8f407d8 100644 --- a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -229,14 +229,15 @@ protected function formatDate($edtf_text) { if (array_key_exists(EDTFUtils::MONTH, $parsed_date)) { if (strpos($parsed_date[EDTFUtils::MONTH], 'X') !== FALSE) { $unspecified[] = t('month'); + // Month remains blank for output. } - // IF 'mm', do nothing, it is already in this format. - if ($settings['month_format'] === 'mmm' || $settings['month_format'] === 'mmmm') { + elseif ($settings['month_format'] === 'mmm' || $settings['month_format'] === 'mmmm') { $month = EDTFUtils::MONTHS_MAP[$parsed_date[EDTFUtils::MONTH]][$settings['month_format']]; } elseif ($settings['month_format'] === 'm') { $month = ltrim($parsed_date[EDTFUtils::MONTH], ' 0'); } + // IF 'mm', do nothing, it is already in this format. else { $month = $parsed_date[EDTFUtils::MONTH]; } From a4482690d0d2870ce4d0c8111b7f9881409bc915 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Wed, 6 Feb 2019 09:47:02 -0800 Subject: [PATCH 08/12] add existing EDTF dates update --- controlled_access_terms.module | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/controlled_access_terms.module b/controlled_access_terms.module index a5fb060..4f6b8a5 100644 --- a/controlled_access_terms.module +++ b/controlled_access_terms.module @@ -73,6 +73,40 @@ function controlled_access_terms_jsonld_alter_normalized_array(EntityInterface $ } } + +/** + * Update EDTF fields from the 2012 draft to match the 2018 spec. + */ +function controlled_access_terms_update_8003() { + $db = \Drupal::database(); + + // Find all the fields using edtf. + $config_factory = \Drupal::configFactory(); + foreach ($config_factory->listAll('field.storage.') as $field_storage_config_name) { + $field_storage_config = $config_factory->get($field_storage_config_name); + if ($field_storage_config->get('type') === 'edtf'){ + + // Run through each update. Make sure 'unknown' is updated before 'u'. + $updates = [ + 'open' => '..', + 'unknown' => '', + 'y' => 'Y', + 'u' => 'X', + '?~' => '%', + '~?' => '%', + ]; + foreach ($updates as $old => $new) { + $db->update($field_storage_config->get('entity_type') . '__' . $field_storage_config->get('field_name')) + ->expression($field_storage_config->get('field_name') . '_value', 'replace('. $field_storage_config->get('field_name') . '_value, :old, :new)', [ + ':old' => $old, + ':new' => $new + ]) + ->execute(); + } + } + } +} + /** * Change fields using the EDTF Widget to the new EDTF Field Type. */ From 491b91f9d1cf182b8f357ca2122b413f478d7827 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Wed, 6 Feb 2019 09:49:08 -0800 Subject: [PATCH 09/12] update EDTF field type label --- src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php b/src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php index 900e4d2..643021a 100644 --- a/src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php +++ b/src/Plugin/Field/FieldType/ExtendedDateTimeFormat.php @@ -9,7 +9,7 @@ * * @FieldType( * id = "edtf", - * label = @Translation("EDTF, level 1"), + * label = @Translation("EDTF"), * module = "controlled_access_terms", * description = @Translation("Extended Date Time Format field"), * default_formatter = "edtf_default", From ce2bd1bd68ce77981c82a322165399ee5b1066c9 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Wed, 6 Feb 2019 10:06:08 -0800 Subject: [PATCH 10/12] coding standards --- controlled_access_terms.module | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/controlled_access_terms.module b/controlled_access_terms.module index 4f6b8a5..50372ef 100644 --- a/controlled_access_terms.module +++ b/controlled_access_terms.module @@ -73,7 +73,6 @@ function controlled_access_terms_jsonld_alter_normalized_array(EntityInterface $ } } - /** * Update EDTF fields from the 2012 draft to match the 2018 spec. */ @@ -84,7 +83,7 @@ function controlled_access_terms_update_8003() { $config_factory = \Drupal::configFactory(); foreach ($config_factory->listAll('field.storage.') as $field_storage_config_name) { $field_storage_config = $config_factory->get($field_storage_config_name); - if ($field_storage_config->get('type') === 'edtf'){ + if ($field_storage_config->get('type') === 'edtf') { // Run through each update. Make sure 'unknown' is updated before 'u'. $updates = [ @@ -97,11 +96,11 @@ function controlled_access_terms_update_8003() { ]; foreach ($updates as $old => $new) { $db->update($field_storage_config->get('entity_type') . '__' . $field_storage_config->get('field_name')) - ->expression($field_storage_config->get('field_name') . '_value', 'replace('. $field_storage_config->get('field_name') . '_value, :old, :new)', [ - ':old' => $old, - ':new' => $new - ]) - ->execute(); + ->expression($field_storage_config->get('field_name') . '_value', 'replace(' . $field_storage_config->get('field_name') . '_value, :old, :new)', [ + ':old' => $old, + ':new' => $new, + ]) + ->execute(); } } } From 93804841c21d923a7f91288d700ee4c4d9c67cf4 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Fri, 8 Feb 2019 16:01:01 -0800 Subject: [PATCH 11/12] fix undefined offset notice --- src/Plugin/Field/FieldFormatter/EDTFFormatter.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php index 8f407d8..c46912b 100644 --- a/src/Plugin/Field/FieldFormatter/EDTFFormatter.php +++ b/src/Plugin/Field/FieldFormatter/EDTFFormatter.php @@ -197,14 +197,14 @@ public function viewElements(FieldItemListInterface $items, $langcode) { */ protected function formatDate($edtf_text) { - list($date, $time) = explode('T', $edtf_text); + $date_time = explode('T', $edtf_text); // Formatted versions of the date elements. $year = ''; $month = ''; $day = ''; - preg_match(EDTFUtils::DATE_PARSE_REGEX, $date, $parsed_date); + preg_match(EDTFUtils::DATE_PARSE_REGEX, $date_time[0], $parsed_date); $parsed_date[EDTFUtils::YEAR_BASE] = EDTFUtils::expandYear($parsed_date[EDTFUtils::YEAR_FULL], $parsed_date[EDTFUtils::YEAR_BASE], $parsed_date[EDTFUtils::YEAR_EXPONENT]); $settings = $this->getSettings(); @@ -276,8 +276,8 @@ protected function formatDate($edtf_text) { // Time. // TODO: Add time formatting options. - if (isset($time) && !empty($time)) { - $formatted_date .= ' ' . $time; + if (array_key_exists(1, $date_time) && !empty($date_time[1])) { + $formatted_date .= ' ' . $date_time[1]; } // Unspecified. From a19173cf86373063c0d84e75c985013ba8ece270 Mon Sep 17 00:00:00 2001 From: Seth Shaw Date: Thu, 21 Feb 2019 13:32:34 -0800 Subject: [PATCH 12/12] update dateIso8601Value for v2018 --- src/EDTFConverter.php | 85 ++--------------------------------- src/EDTFUtils.php | 101 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 81 deletions(-) diff --git a/src/EDTFConverter.php b/src/EDTFConverter.php index 85a278e..11db6d2 100644 --- a/src/EDTFConverter.php +++ b/src/EDTFConverter.php @@ -9,40 +9,6 @@ */ class EDTFConverter extends CommonDataConverter { - /** - * Northern hemisphere season map. - * - * @var array - */ - private $seasonMapNorth = [ - // Spring => March. - '21' => '03', - // Summer => June. - '22' => '06', - // Autumn => September. - '23' => '09', - // Winter => December. - '24' => '12', - ]; - - /** - * Southern hemisphere season map. - * - * (Currently unused until a config for this is established.) - * - * @var array - */ - private $seasonMapSouth = [ - // Spring => September. - '21' => '03', - // Summer => December. - '22' => '06', - // Autumn => March. - '23' => '09', - // Winter => June. - '24' => '12', - ]; - /** * Converts an EDTF text field into an ISO 8601 timestamp string. * @@ -55,19 +21,11 @@ class EDTFConverter extends CommonDataConverter { * Returns the ISO 8601 timestamp. */ public static function datetimeIso8601Value(array $data) { - $date = explode('/', $data['value'])[0]; - // Strip approximations/uncertainty. - $date = str_replace(['?', '~'], '', $date); + // Take first possible date. + $date = preg_split('/(,|\.\.|\/)/', trim($data['value'], '{}[]'))[0]; - // Replace unspecified. - // Month/day. - $date = str_replace('-uu', '-01', $date); - // Zero-Year in decade/century. - $date = str_replace('u', '0', $date); - - // Seasons map. - return EDTFConverter::seasonsMap($date) . 'T00:00:00'; + return EDTFUtils::iso8601Value($date); } @@ -83,43 +41,8 @@ public static function datetimeIso8601Value(array $data) { * Returns the ISO 8601 date. */ public static function dateIso8601Value(array $data) { - $date = explode('/', $data['value'])[0]; - - // Strip approximations/uncertainty. - $date = str_replace(['?', '~'], '', $date); - - // Remove unspecified. - // Month/day. - $date = str_replace('-uu', '', $date); - // Zero-Year in decade/century. - $date = str_replace('u', '0', $date); - - // Seasons map. - return EDTFConverter::seasonsMap($date); - - } - - /** - * Converts a numeric season into a numeric month. - * - * @param string $date - * The date string to convert. - * - * @return string - * Returns the ISO 8601 date with the correct month. - */ - protected static function seasonsMap(string $date) { - $date_parts[] = explode('-', $date, 3); - // Digit Seasons. - if ((count($date_parts) > 1) && - in_array($date_parts[1], ['21', '22', '23', '24'])) { - // TODO: Make hemisphere seasons configurable. - $season_mapping = $seasonMapNorth; - $date_parts[1] = $season_mapping[$date_parts[1]]; - $date = implode('-', array_filter($date_parts)); - } - return $date; + return explode('T', EDTFConverter::datetimeIso8601Value($data))[0]; } diff --git a/src/EDTFUtils.php b/src/EDTFUtils.php index 4f96dbc..b6113db 100644 --- a/src/EDTFUtils.php +++ b/src/EDTFUtils.php @@ -68,6 +68,54 @@ class EDTFUtils { '41' => ['mmm' => 'Sem2', 'mmmm' => 'Semestral 2'], ]; + const SEASONS_MAP = [ + // Northern Hemisphere bias for 21-24. + '21' => '03', + '22' => '06', + '23' => '09', + '24' => '12', + // Northern seasons. + '25' => '03', + '26' => '06', + '27' => '09', + '28' => '12', + // Southern seasons. + '29' => '09', + '30' => '12', + '31' => '03', + '32' => '06', + // Quarters. + '33' => '01', + '34' => '04', + '35' => '07', + '36' => '10', + // Quadrimesters. + '37' => '01', + '38' => '05', + '39' => '09', + // Semesters. + '40' => '01', + '41' => '07', + ]; + + /** + * Southern hemisphere season map. + * + * (Currently unused until a config for this is established.) + * + * @var array + */ + private $seasonMapSouth = [ + // Spring => September. + '21' => '03', + // Summer => December. + '22' => '06', + // Autumn => March. + '23' => '09', + // Winter => June. + '24' => '12', + ]; + /** * Validate an EDTF expression. * @@ -254,4 +302,57 @@ public static function expandYear($year_full, $year_base, $year_exponent) { } } + /** + * Converts an EDTF string into an ISO 8601 timestamp string. + * + * @param string $edtf + * The array containing the 'value' element. + * + * @return string + * Returns the ISO 8601 timestamp. + */ + public static function iso8601Value(string $edtf) { + + $date_time = explode('T', $edtf); + + preg_match(EDTFUtils::DATE_PARSE_REGEX, $date_time[0], $parsed_date); + + $year = ''; + $month = ''; + $day = ''; + + $parsed_date[EDTFUtils::YEAR_BASE] = EDTFUtils::expandYear($parsed_date[EDTFUtils::YEAR_FULL], $parsed_date[EDTFUtils::YEAR_BASE], $parsed_date[EDTFUtils::YEAR_EXPONENT]); + + // Clean-up unspecified year/decade. + $year = str_replace('X', '0', $parsed_date[EDTFUtils::YEAR_BASE]); + + if (array_key_exists(EDTFUtils::MONTH, $parsed_date)) { + $month = str_replace('XX', '01', $parsed_date[EDTFUtils::MONTH]); + $month = str_replace('X', '0', $month); + + // ISO 8601 doesn't support seasonal notation yet. Swap them out. + if (array_key_exists($month, EDTFUtils::SEASONS_MAP)) { + $month = EDTFUtils::SEASONS_MAP[$month]; + } + } + + if (array_key_exists(EDTFUtils::DAY, $parsed_date)) { + $day = str_replace('XX', '01', $parsed_date[EDTFUtils::DAY]); + $day = str_replace('X', '0', $day); + } + + $formatted_date = implode('-', array_filter([$year, $month, $day])); + + // Time. + if (array_key_exists(1, $date_time) && !empty($date_time[1])) { + $formatted_date .= 'T' . $date_time[1]; + } + else { + $formatted_date .= 'T00:00:00'; + } + + return $formatted_date; + + } + }