开发者

Transform any date (from anywhere) to DD-MM-YYYY

开发者 https://www.devze.com 2023-04-06 21:44 出处:网络
I\'m trying to create a function that could get a date in different formats and (languages) and transform it to DD-MM-YYYY.For example, this function could get 22 Fev 2011 (Portuguese) and also 22 Feb

I'm trying to create a function that could get a date in different formats and (languages) and transform it to DD-MM-YYYY. For example, this function could get 22 Fev 2011 (Portuguese) and also 22 Feb 2011 (English). For both it s开发者_运维知识库hould return 22-02-2011.

Let's assume that I have a limited amount of languages, so I can have some kind of data structure that carries the months and shortens of those. My question is: "Assuming that strtotime works for English strings, what is my best choice for creating a function that given a string of a date in a language X, returns a date with the format DD-MM-YYY?"


Date/time manipulation sure is a pain. :)

Proposed solution

1. Locale data

I just paid a visit to Yii's svn repository and shamelessly copied these:

$locales = array(
    'pt' => array(
        'monthNames' => array(
            'wide' => array (
              1 => 'janeiro',
              2 => 'fevereiro',
              3 => 'marco',
              4 => 'abril',
              5 => 'maio',
              6 => 'junho',
              7 => 'julho',
              8 => 'agosto',
              9 => 'setembro',
              10 => 'outubro',
              11 => 'novembro',
              12 => 'dezembro',
            ),
            'abbreviated' => array(
              1 => 'jan',
              2 => 'fev',
              3 => 'mar',
              4 => 'abr',
              5 => 'mai',
              6 => 'jun',
              7 => 'jul',
              8 => 'ago',
              9 => 'set',
              10 => 'out',
              11 => 'nov',
              12 => 'dez',
            ),
        ),
        'weekDayNames' => array(
            'wide' => array (
              0 => 'domingo',
              1 => 'segunda-feira',
              2 => 'terca-feira',
              3 => 'quarta-feira',
              4 => 'quinta-feira',
              5 => 'sexta-feira',
              6 => 'sabado',
            ),
            'abbreviated' => array(
              0 => 'dom',
              1 => 'seg',
              2 => 'ter',
              3 => 'qua',
              4 => 'qui',
              5 => 'sex',
              6 => 'sab',
            ),
        ),
     ),
    'en' => array(
        'monthNames' => array(
            'wide' => array (
              1 => 'January',
              2 => 'February',
              3 => 'March',
              4 => 'April',
              5 => 'May',
              6 => 'June',
              7 => 'July',
              8 => 'August',
              9 => 'September',
              10 => 'October',
              11 => 'November',
              12 => 'December',
            ),
            'abbreviated' => array(
              1 => 'Jan',
              2 => 'Feb',
              3 => 'Mar',
              4 => 'Apr',
              5 => 'May',
              6 => 'Jun',
              7 => 'Jul',
              8 => 'Aug',
              9 => 'Sep',
              10 => 'Oct',
              11 => 'Nov',
              12 => 'Dec',
            ),
        ),
        'weekDayNames' => array(
            'wide' => array (
              0 => 'Sunday',
              1 => 'Monday',
              2 => 'Tuesday',
              3 => 'Wednesday',
              4 => 'Thursday',
              5 => 'Friday',
              6 => 'Saturday',
            ),
            'abbreviated' => array(
              0 => 'Sun',
              1 => 'Mon',
              2 => 'Tue',
              3 => 'Wed',
              4 => 'Thu',
              5 => 'Fri',
              6 => 'Sat',
            ),
        ),
    ),
);

2. Brute forcing the problem

Assuming that your app isn't spending all its time converting human-readable dates, speed shouldn't really matter. Therefore I went for a shortish solution with good extensibility, at the cost of not trying to optimize and being slightly cryptic.

function strtotimeIntl($timeString, $locales, $normalizeCallback = 'strtolower') {
    // STEP 1 -- TRY ENGLISH
    $ts = strtotime($timeString);
    if ($ts !== false) {
        return $ts;
    }

    // STEP 2 -- BRUTE FORCE
    $english = $locales['en'];

    foreach($locales as $code => $localeInfo) {
        if($code == 'en') {
            continue; // don't try english again
        }

        $subject = $normalizeCallback($timeString); // reset

        // These reflect the structure of $localeInfo
        $replacementKeys = array(
            array('monthNames', 'wide'),
            array('monthNames', 'abbreviated'),
            array('weekDayNames', 'wide'),
            array('weekDayNames', 'abbreviated'),
        );

        // Replace everything present in the string with english equivalents
        foreach($replacementKeys as $keys) {
            $map = array_map($normalizeCallback, $localeInfo[$keys[0]][$keys[1]]);
            $flipped = array_flip($map);
            $subject = preg_replace('/\b('.implode('|', $map).')\b/e',
                                   '$english[$keys[0]][$keys[1]][$flipped[\'$1\']]',
                                   $subject);
        }

        // Does this look right?
        $ts = strtotime($subject);
        if ($ts !== false) {
            return $ts;
        }
    }

    // Give up, it's not like we didn't try
    return false;
}

That inner foreach does look smelly, but I think it's acceptable. What it does is try to replace any substring that looks like one of the items inside the sub-array of $localeInfo (current locale being tested) identified by the indexes $keys[0] and $keys[1]. To make the replacement as tersely as possible it uses an auxiliary array $flipped and preg_replace in evaluation mode; if you don't like this kind of code, it can certainly be replaced with a more familiar loop-based approach.

3. How to use it

$timeString = '22 Feb 2011';
echo strtotimeIntl($timeString, $locales);

$timeString = '22 Fev 2011';
echo strtotimeIntl($timeString, $locales);

4. What's with the third argument?

Well, it would be nice if the replacement worked in a case-insensitive manner. The problem with this is that you can't blindly use strtolower and the /i regex modifier, because at least the former will not work unless you change the LC_TEXT locale which is a painful requirement and not reliable to boot (locale names are OS-dependent). And the shotgun argument is that even if everything goes well that far, you still need to have your locale data saved in an ANSI encoding (which means you can't save them all in the same file).

Therefore the caller has the option of supplying their own normalization function if needed; mb_strtolower would be an excellent choice here if your data is saved in UTF-8.

5. Does that even work?

Sure it does.

6. And there are no caveats?

Well, apart from the normalization function there is one more I can think of: strtotime internally uses the local timezone to convert the parsed date to a timestamp. This means that a date in e.g. French will be parsed correctly given the appropriate locale data, but the timestamp will be produced for the local time zone, not CET/CEST (the timezone France uses). Depending on your requirements, you might also want to account for the timezone difference.


Other than using (i.e. paying for) translation API services, you could create database tables for languages, weekdays, abbreviated weekdays, months, abbreviated months. The four weekday/month tables will have a language_id foreign key. You can store the english equivalents in the rows or, better, normalize those out.

Then have the function grab the alpha tokens from the date string (preg_match) and query the tables for rows that match the token and language. Then, if the appropriate rows are returned, substitute the english tokens into the date string and pass to the date function.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号