From f05938c8678dd2b785d088d2c7ae468008f10053 Mon Sep 17 00:00:00 2001 From: Joshua Ramon Enslin Date: Fri, 18 Sep 2020 18:48:40 +0200 Subject: [PATCH] Initial --- src/NodaTimeAutotranslater.php | 133 ++++++++++ src/NodaTimeSplitter.php | 434 +++++++++++++++++++++++++++++++++ src/inc/datesByCountry.php | 138 +++++++++++ 3 files changed, 705 insertions(+) create mode 100644 src/NodaTimeAutotranslater.php create mode 100644 src/NodaTimeSplitter.php create mode 100644 src/inc/datesByCountry.php diff --git a/src/NodaTimeAutotranslater.php b/src/NodaTimeAutotranslater.php new file mode 100644 index 0000000..ddc0ea2 --- /dev/null +++ b/src/NodaTimeAutotranslater.php @@ -0,0 +1,133 @@ + + */ +declare(strict_types = 1); +require_once __DIR__ . '/inc/datesByCountry.php'; + +/** + * Autotranslater class for times. + */ +class NodaTimeAutotranslater { + + const LANGS_TO_LOCALES = [ + 'ar' => 'ar_SY.utf8', + 'de' => 'de_DE.utf8', + 'en' => 'en_US.utf8', + 'es' => 'es_ES.utf8', + 'fa' => 'fa_IR.UTF-8', + 'fr' => 'fr_FR.utf8', + 'hu' => 'hu_HU.utf8', + 'id' => 'id_ID.utf8', + 'it' => 'it_IT.utf8', + 'ka' => 'ka_GE.UTF-8', + 'ko' => 'ko_KR.UTF-8', + 'pl' => 'pl_PL.utf8', + 'pt' => 'pt_BR.utf8', + 'ro' => 'ro_RO.UTF-8', + 'ru' => 'ru_RU.UTF-8', + 'ta' => 'ta_IN.UTF-8', + 'tl' => 'tl_PH.utf8', + 'tr' => 'tr_TR.utf8', + + // Languages that don't really need a specific locale + 'ja' => 'en_US.utf8', + 'zh' => 'en_US.utf8', + ]; + + /** @var MDMysqli */ + private MDMysqli $_mysqli_noda; + /** @var integer */ + private int $_znum; + /** @var MDMysqliStmt */ + private MDMysqliStmt $_insertStmt; + + /** + * Checks if a time is translatable. + * Translatable times have either a counting time day and month or at least a month. + * + * @param string $zeit_zaehlzeit_monat Counting time month. + * + * @return boolean + */ + public static function check_translatability(string $zeit_zaehlzeit_monat) { + + if (trim($zeit_zaehlzeit_monat, ", .0") === "") { + return false; + } + + return true; + + } + + /** + * Runs autotranslater. + * + * @param array $timeInfo Time information. + * + * @return void + */ + public function translate(array $timeInfo):void { + + if (!self::check_translatability($timeInfo['zeit_zaehlzeit_monat'])) { + throw new MDgenericInvalidInputsException("Non-translatable date"); + } + + if (trim($timeInfo['zeit_zaehlzeit_tag'], ", .0") === "") { + $dateStr = "{$timeInfo['zeit_zaehlzeit_jahr']}-{$timeInfo['zeit_zaehlzeit_monat']}-05 00:00:01"; + $usecase = "month"; + } + else { + $dateStr = "{$timeInfo['zeit_zaehlzeit_jahr']}-{$timeInfo['zeit_zaehlzeit_monat']}-{$timeInfo['zeit_zaehlzeit_tag']} 00:00:01"; + $usecase = "day"; + } + + $dateGeneral = strtotime($dateStr); + + foreach (self::LANGS_TO_LOCALES as $tLang => $locale) { + + setlocale(LC_TIME, $locale); + if ($locale !== setlocale(LC_TIME, "0")) continue; + if ($usecase === "month") $tLangValue = strftime(getMonthFormatByLang($tLang), $dateGeneral ?: 0); + else if ($usecase === "day") $tLangValue = strftime(getDateFormatByLang($tLang), $dateGeneral ?: 0); + $this->_insertStmt->bind_param("iss", $this->_znum, $tLang, $tLangValue); + $this->_insertStmt->execute(); + + } + + } + + /** + * Constructor. + * + * @param MDMysqli $mysqli_noda Database connection. + * @param integer $znum Time ID. + * + * @return void + */ + public function __construct(MDMysqli $mysqli_noda, int $znum) { + + $this->_mysqli_noda = $mysqli_noda; + $this->_znum = $znum; + + $this->_insertStmt = $mysqli_noda->do_prepare("INSERT INTO `zeit_translation` + (`zeit_id`, `trans_language`, `trans_name`) + VALUES + (?, ?, ?)"); + + } + + /** + * Destructor. + * + * @return void + */ + public function __destruct() { + + $this->_insertStmt->close(); + + } + +} diff --git a/src/NodaTimeSplitter.php b/src/NodaTimeSplitter.php new file mode 100644 index 0000000..e7138c8 --- /dev/null +++ b/src/NodaTimeSplitter.php @@ -0,0 +1,434 @@ + + */ +declare(strict_types = 1); + +/** + * Class for splitting times. + */ +final class NodaTimeSplitter { + + const MONTH_NAMES_GERMAN = [ + "01" => ['Januar', 'Jan.'], + "02" => ['Februar', 'Feb'], + "03" => ['März', 'Mrz.'], + "04" => ['April', 'Apr.'], + "05" => ['Mai'], + "06" => ['Juni', 'Jun.'], + "07" => ['Juli', 'Jul.'], + "08" => ['August', 'Aug.'], + "09" => ['September', 'Sep.', 'Sept.'], + "10" => ['Oktober', 'Okt.'], + "11" => ['November', 'Nov.'], + "12" => ['Dezember', 'Dez.'], + ]; + + const MONTH_NAMES_HUNGARIAN = [ + "01" => ['január', 'jan'], + "02" => ['február', 'feb'], + "03" => ['március', 'mar.'], + "04" => ['április', 'apr.'], + "05" => ['május', 'maj.'], + "06" => ['június', 'jun.'], + "07" => ['július', 'jul.'], + "08" => ['augusztus', 'aug.'], + "09" => ['szeptember', 'szp.'], + "10" => ['október', 'okt.'], + "11" => ['november', 'nov.'], + "12" => ['december', 'dec.'], + ]; + + const STOP_STRINGS_GERMAN = [ + "-", + ",", + ";", + ":", + "/", + "(", ")", + "[", "]", + ", ", + " und ", + "nach ", + "um ", + "ca.", + "ab ", + "seit ", + "bis ", + "vor ", + "anfang ", + "ende ", + ]; + + const STOP_STRINGS_HUNGARIAN = [ + "-", + ",", + ";", + ":", + "/", + "(", ")", + "[", "]", + "ca.", + ", ", + "-ig", + "és", + "eleje", + "között", + "töl", + "januárig", + "februárig", + "márciusig", + "vége", + "végén", + "áprilisig", + "májusig", + "júniusig", + "júliusig", + "augusztusig", + "szeptemberig", + "októberig", + "novemberig", + "decemberig", + ]; + + /** + * Cleans input strings by trimming obsolete stuff. + * + * @param string $input Input date name. + * + * @return string + */ + private static function clean_input(string $input):string { + + while (strpos($input, " -") !== false) $input = str_replace(" -", "-", $input); + while (strpos($input, "- ") !== false) $input = str_replace("- ", "-", $input); + + return trim($input, ", [](){}"); + + } + + /** + * Checks if a string is really numeric, not numeric + space, dot. + * + * @param string $input Input string. + * + * @return boolean + */ + private static function is_numeric(string $input):bool { + + if (is_numeric($input) + and strpos($input, " ") === false + and strpos($input, ".") === false + ) { + return true; + } + + return false; + + } + + /** + * Validates a time substr. + * + * @param string $datum Date. + * @param integer $start Start of substr. + * @param integer $end End of substr. + * + * @return string + */ + private static function validateDateSubstr(string $datum, int $start, int $end = 10000):string { + + if ($start !== 0 + && !in_array(substr($datum, $start - 1, 1), ["-", " ", "."], true) + ) { + return ""; + } + + $output = substr($datum, $start, $end); + if (self::is_numeric($output)) return $output; + return ""; + + } + + /** + * Generates new time name based on moda. + * + * @param array $moda Date strings. + * + * @return string + */ + public static function timePartsToTimeName(array $moda):string { + + if ($moda[0] !== $moda[1]) { + return "{$moda[0]}-{$moda[1]}"; + } + else if (intval($moda[2]) !== 0 and intval($moda[3]) !== 0) { + return "{$moda[3]}.{$moda[2]}.{$moda[0]}"; + } + else if ($moda[0] === $moda[1] && trim($moda[2], " 0") === "" && trim($moda[3], " 0") === "") { + return "{$moda[0]}"; + } + + return ""; + + } + + /** + * Generates counting year - the middle between start and end year. + * + * @param array $moda Date strings. + * + * @return integer + */ + public static function timePartsToCountingYear(array $moda):int { + + return (int)ceil(intval($moda[1]) - ((intval($moda[1]) - intval($moda[0])) / 2)); + + } + + /** + * Generates HTML for linking disassembly of times for a single day. + * + * @param integer $znum Time ID. + * @param array $moda Date strings. + * @param MDTlLoader $tlLoader Translation loader. + * + * @return string + */ + public static function generateDisassemblyForDay(int $znum, array $moda, MDTlLoader $tlLoader):string { + + $zaehlzeit_jahr = self::timePartsToCountingYear($moda); + + // Wenn Datum in Form von tt.mm.jjjj, dann biete zerlegen an + $output = '
'; + $output .= ''; + $output .= ''; + $output .= ''; + $output .= ''; + $output .= '
' . $tlLoader->tl("tempi", "tempi", "time_tool") . '+'; + if (!empty(trim($moda[3], " 0")) and !empty(trim($moda[2], " 0"))) $output .= $moda[3] . '.' . $moda[2] . '.' . $moda[0]; + else if ($moda[0] !== $moda[1]) $output .= $moda[0] . "-" . $moda[1]; + else if (!empty(trim($moda[2], " 0"))) $output .= "{$moda[2]}.{$moda[0]}"; + else $output .= $moda[0]; + $output .= ' - ' . $tlLoader->tl("tempi", "tempi", "time_disassemble") . '
'; + + return $output; + + } + + /** + * Checks if any string of a list occurs in the haystack input string. + * + * @param string $haystack Haystack. + * @param array $needles Needles. + * + * @return boolean + */ + private static function stri_occurs(string $haystack, array $needles):bool { + + foreach ($needles as $needle) { + if (stripos($haystack, $needle) !== false) return true; + } + + return false; + + } + + /** + * Translate German month to two digits number. + * + * @param string $datum Date. + * + * @return array + */ + public static function is_valid_date(string $datum):array { + + $datum = self::clean_input($datum); + + if (self::stri_occurs($datum, self::STOP_STRINGS_GERMAN)) { + return []; + } + + if (strlen($datum) <= 9) $use_day = false; + else $use_day = true; + + foreach (self::MONTH_NAMES_GERMAN as $monthVal => $monthValidNames) { + if (self::stri_occurs($datum, $monthValidNames)) { + if (!empty($monat)) return []; + $monat = (string)$monthVal; + } + } + + if (empty($monat) and self::is_numeric((string)substr($datum, 3, 2))) $monat = substr($datum, 3, 2); + + if (self::is_numeric((string)substr($datum, 0, 2))) $day = substr($datum, 0, 2); + else if (substr($datum, 1, 1) === "." and self::is_numeric((string)substr($datum, 0, 1))) $day = "0" . substr($datum, 0, 1); + + if (self::is_numeric((string)substr($datum, -4))) $year = substr($datum, -4); + + if (!empty($year) and !empty($monat) and !empty($day) and $use_day) { + return [$year, $year, $monat, $day]; + } + else if (!empty($year) and !empty($monat)) { + return [$year, $year, $monat, "00"]; + } + return []; + + } + + /** + * Translate Hungarian month to two digits number. + * + * @param string $datum Date. + * + * @return array + */ + public static function is_valid_date_hungarian(string $datum):array { + + $datum = self::clean_input($datum); + + if (self::stri_occurs($datum, self::STOP_STRINGS_HUNGARIAN)) { + return []; + } + + if (strlen($datum) <= 9) return []; + + foreach (self::MONTH_NAMES_HUNGARIAN as $monthVal => $monthValidNames) { + if (self::stri_occurs($datum, $monthValidNames)) { + if (!empty($monat)) return []; + $monat = (string)$monthVal; + } + } + + if (empty($monat) and self::is_numeric((string)substr($datum, 5, 2))) $monat = substr($datum, 5, 2); + else if (empty($monat) and self::is_numeric((string)substr($datum, 6, 2))) $monat = substr($datum, 6, 2); + + $day = self::validateDateSubstr($datum, -2); + if (empty($day)) $day = self::validateDateSubstr($datum, -3, 2); + if (empty($day)) $day = self::validateDateSubstr($datum, -4, 2); + if (empty($day)) $day = self::validateDateSubstr($datum, -5, 2); + if (empty($day)) $day = self::validateDateSubstr($datum, -6, 2); + + if (substr($datum, -2, 1) === " " and self::is_numeric((string)substr($datum, -1, 1))) { + $day = "0" . substr($datum, -1, 1); + } + else if ((empty($day)) and substr($datum, -3, 1) === " " and self::is_numeric((string)substr($datum, -2, 1))) { + $day = "0" . substr($datum, -2, 1); + } + + if (self::is_numeric((string)substr($datum, 0, 4))) $year = substr($datum, 0, 4); + + if (!empty($year) and !empty($monat) and !empty($day)) { + return [$year, $year, $monat, $day]; + } + else if (!empty($year) and !empty($monat)) { + return [$year, $year, $monat, "00"]; + } + return []; + + } + + /** + * Translate German month to two digits number. + * + * @param string $datum Date. + * + * @return array + */ + public static function is_valid_date_by_php(string $datum):array { + + $datum = self::clean_input($datum); + + if (!($timeInt = strtotime($datum))) { + return []; + } + + return [date("Y", $timeInt), date("m", $timeInt), date("d", $timeInt)]; + + } + + /** + * Checks if an input date is a timespan. + * + * @param string $datum Input date. + * + * @return array + */ + public static function is_timespan(string $datum):array { + + $datum = self::clean_input($datum); + + if (preg_match("/^[0-9][0-9][0-9][0-9](-|\/)[0-9][0-9][0-9][0-9]$/", $datum)) { + + $start = substr($datum, 0, 4); + $end = substr($datum, -4); + return [$start, $end, "00", "00"]; + + } + if (preg_match("/^[0-9][0-9]\.[0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German T.MM.JJJJ + $start = substr($datum, 5, 4); + $month = "0" . substr($datum, 3, 1); + $day = substr($datum, 0, 2); + return [$start, $start, $month, $day]; + } + if (preg_match("/^[0-9]\.[0-9][0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German T.MM.JJJJ + $start = substr($datum, 5, 4); + $month = substr($datum, 2, 2); + $day = "0" . substr($datum, 0, 1); + return [$start, $start, $month, $day]; + } + if (preg_match("/^[0-9]\.[0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German T.M.JJJJ + $start = substr($datum, 4, 4); + $month = "0" . substr($datum, 2, 1); + $day = "0" . substr($datum, 0, 1); + return [$start, $start, $month, $day]; + } + if (preg_match("/^[0-9][0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German Y-m + $start = substr($datum, 3, 4); + $month = substr($datum, 0, 2); + return [$start, $start, $month, "00"]; + } + if (preg_match("/^[0-9]\.[0-9][0-9][0-9][0-9]$/", $datum)) { // German Y-m + $start = substr($datum, 2, 4); + $month = "0" . substr($datum, 0, 1); + return [$start, $start, $month, "00"]; + } + if (preg_match("/^[0-9][0-9][0-9][0-9]\.[0-9][0-9]$/", $datum)) { // Hungarian Y-m + $start = substr($datum, 0, 4); + $month = substr($datum, 5, 2); + return [$start, $start, $month, "00"]; + } + if (preg_match("/^[0-9][0-9][0-9][0-9]$/", $datum)) { + + $start = substr($datum, 0, 4); + return [$start, $start, "00", "00"]; + + } + return []; + + } + + /** + * Wrapper to check if any splitting command works. + * + * @param string $datum Input date. + * + * @return array + */ + public static function attempt_splitting(string $datum):array { + + $moda = NodaTimeSplitter::is_timespan($datum); + if (!$moda) $moda = NodaTimeSplitter::is_valid_date($datum); + if (!$moda) $moda = NodaTimeSplitter::is_valid_date_hungarian($datum); + + return $moda; + + } + +} diff --git a/src/inc/datesByCountry.php b/src/inc/datesByCountry.php new file mode 100644 index 0000000..517a5de --- /dev/null +++ b/src/inc/datesByCountry.php @@ -0,0 +1,138 @@ + + */ +declare(strict_types = 1); + +/** + * Function getDateFormatByLang returns the common date format in a given + * country / language. + * + * @param string $lang Language code to check. + * + * @thanks Michael Connor (https://gist.github.com/mlconnor/1887156) for + * compiling the underlying array. + * + * @return string + */ +function getDateFormatByLang(string $lang):string { + + $datesByCountry = [ + 'ar' => '%d/%m/%Y', + 'be' => '%d.%B.%Y', + 'bg' => '%Y-%B-%d', + 'ca' => '%d/%m/%Y', + 'cs' => '%d.%B.%Y', + 'da' => '%d-%m-%Y', + 'de' => '%d.%m.%Y', + 'el' => '%d/%B/%Y', + 'en' => '%B %d, %Y', + 'es' => '%d/%m/%Y', + 'et' => '%d.%m.%Y', + 'fa' => '%d/%m/%Y', + 'fi' => '%d.%B.%Y', + 'fr' => '%d/%m/%Y', + 'ga' => '%d/%m/%Y', + 'hr' => '%d.%m.%Y', + 'hu' => '%Y. %B %d.', + 'id' => '%d %B %Y', + 'in' => '%d/%m/%Y', + 'is' => '%d.%B.%Y', + 'it' => '%d/%m/%Y', + 'iw' => '%d/%m/%Y', + 'ja' => '%Y年%m月%d日', + 'ka' => '%d.%m.%Y', + 'ko' => '%Y년 %m월 %d일', + 'lt' => '%Y.%B.%d', + 'lv' => '%Y.%d.%B', + 'mk' => '%d.%B.%Y', + 'ms' => '%d/%m/%Y', + 'mt' => '%d/%m/%Y', + 'nl' => '%d-%B-%Y', + 'no' => '%d.%m.%Y', + 'pl' => '%d.%m.%Y', + 'pt' => '%d/%m/%Y', + 'ro' => '%d.%m.%Y', + 'ru' => '%d.%m.%Y', + 'sk' => '%d.%B.%Y', + 'sl' => '%d.%B.%Y', + 'sq' => '%Y-%m-%d', + 'sr' => '%d.%B.%Y.', + 'sv' => '%Y-%m-%d', + 'ta' => '%d-%m-%Y', + 'tr' => '%d.%m.%Y', + 'uk' => '%d.%m.%Y', + 'vi' => '%d/%m/%Y', + 'zh' => '%Y年%m月%d日', + ]; + if (!isset($datesByCountry[$lang])) return $datesByCountry["en"]; + + return $datesByCountry[$lang]; + +} + +/** + * Function getDateFormatByLang returns the common date format in a given + * country / language. + * + * @param string $lang Language code to check. + * + * @thanks Michael Connor (https://gist.github.com/mlconnor/1887156) for + * compiling the underlying array. + * + * @return string + */ +function getMonthFormatByLang(string $lang):string { + + $datesByCountry = [ + 'ar' => '%m/%Y', + 'be' => '%B %Y', + 'bg' => '%Y-%B', + 'ca' => '%m/%Y', + 'cs' => '%B.%Y', + 'da' => '%m-%Y', + 'de' => '%B %Y', + 'el' => '%B %Y', + 'en' => '%B %Y', + 'es' => '%B %Y', + 'fa' => '%B %Y', + 'fr' => '%B %Y', + 'hu' => '%Y. %B', + 'in' => '%m/%Y', + 'is' => '%B %Y', + 'it' => '%B %Y', + 'iw' => '%m %Y', + 'ja' => '%Y年%m月', + 'ka' => '%B %Y', + 'ko' => '%Y년 %m월', + 'lt' => '%Y. %B.', + 'lv' => '%Y. %B', + 'mk' => '%B %Y', + 'ms' => '%m %Y', + 'mt' => '%m %Y', + 'nl' => '%B %Y', + 'no' => '%B %Y', + 'pl' => '%B %Y', + 'pt' => '%B %Y', + 'ro' => '%B %Y', + 'ru' => '%B %Y', + 'sk' => '%B %Y', + 'sl' => '%B %Y', + 'sq' => '%B %Y', + 'sr' => '%B %Y', + 'sv' => '%Y-%m', + 'ta' => '%B %Y', + 'tr' => '%B %Y', + 'tl' => '%B %Y', + 'uk' => '%m %Y', + 'vi' => '%m %Y', + 'zh' => '%Y年%m月', + ]; + if (!isset($datesByCountry[$lang])) return $datesByCountry["en"]; + + return $datesByCountry[$lang]; + +}