Aleksander Machniak
2013-10-28 b1f3c3bee814ee9fadd4145ade9d9542211d2ee4
Fixed saving contact birthday/anniversary dates before 01-01-1970
3 files modified
109 ■■■■ changed files
program/lib/Roundcube/rcube_utils.php 79 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/save.inc 4 ●●●● patch | view | raw | blame | history
tests/Framework/Utils.php 26 ●●●●● patch | view | raw | blame | history
program/lib/Roundcube/rcube_utils.php
@@ -747,39 +747,12 @@
     */
    public static function strtotime($date)
    {
        $date = trim($date);
        // check for MS Outlook vCard date format YYYYMMDD
        if (preg_match('/^([12][90]\d\d)([01]\d)([0123]\d)$/', $date, $m)) {
            return mktime(0,0,0, intval($m[2]), intval($m[3]), intval($m[1]));
        }
        // common little-endian formats, e.g. dd/mm/yyyy (not all are supported by strtotime)
        if (preg_match('/^(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{4})$/', $date, $m)
            && $m[1] > 0 && $m[1] <= 31 && $m[2] > 0 && $m[2] <= 12 && $m[3] >= 1970
        ) {
            return mktime(0,0,0, intval($m[2]), intval($m[1]), intval($m[3]));
        }
        $date = self::clean_datestr($date);
        // unix timestamp
        if (is_numeric($date)) {
            return (int) $date;
        }
        // Clean malformed data
        $date = preg_replace(
            array(
                '/GMT\s*([+-][0-9]+)/',                     // support non-standard "GMTXXXX" literal
                '/[^a-z0-9\x20\x09:+-]/i',                  // remove any invalid characters
                '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i',   // remove weekday names
            ),
            array(
                '\\1',
                '',
                '',
            ), $date);
        $date = trim($date);
        // if date parsing fails, we have a date in non-rfc format.
        // remove token from the end and try again
@@ -808,8 +781,8 @@
            return $date;
        }
        $dt = false;
        $date = trim($date);
        $dt   = false;
        $date = self::clean_datestr($date);
        // try to parse string with DateTime first
        if (!empty($date)) {
@@ -834,6 +807,52 @@
        return $dt;
    }
    /**
     * Clean up date string for strtotime() input
     *
     * @param string $date Date string
     *
     * @return string Date string
     */
    public static function clean_datestr($date)
    {
        $date = trim($date);
        // check for MS Outlook vCard date format YYYYMMDD
        if (preg_match('/^([12][90]\d\d)([01]\d)([0123]\d)$/', $date, $m)) {
            return sprintf('%04d-%02d-%02d 00:00:00', intval($m[1]), intval($m[2]), intval($m[3]));
        }
        // Clean malformed data
        $date = preg_replace(
            array(
                '/GMT\s*([+-][0-9]+)/',                     // support non-standard "GMTXXXX" literal
                '/[^a-z0-9\x20\x09:+-\/]/i',                  // remove any invalid characters
                '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i',   // remove weekday names
            ),
            array(
                '\\1',
                '',
                '',
            ), $date);
        $date = trim($date);
        // try to fix dd/mm vs. mm/dd discrepancy, we can't do more here
        if (preg_match('/^(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{4})$/', $date, $m)) {
            $mdy   = $m[2] > 12 && $m[1] <= 12;
            $day   = $mdy ? $m[2] : $m[1];
            $month = $mdy ? $m[1] : $m[2];
            $date  = sprintf('%04d-%02d-%02d 00:00:00', intval($m[3]), $month, $day);
        }
        // I've found that YYYY.MM.DD is recognized wrong, so here's a fix
        else if (preg_match('/^(\d{4})\.(\d{1,2})\.(\d{1,2})$/', $date)) {
            $date = str_replace('.', '-', $date) . ' 00:00:00';
        }
        return $date;
    }
    /*
     * Idn_to_ascii wrapper.
     * Intl/Idn modules version of this function doesn't work with e-mail address
program/steps/addressbook/save.inc
@@ -80,8 +80,8 @@
    // normalize the submitted date strings
    if ($colprop['type'] == 'date') {
        if ($timestamp = rcube_utils::strtotime($a_record[$col])) {
            $a_record[$col] = date('Y-m-d', $timestamp);
        if ($a_record[$col] && ($dt = rcube_utils::anytodatetime($a_record[$col]))) {
            $a_record[$col] = $dt->format('Y-m-d');
        }
        else {
            unset($a_record[$col]);
tests/Framework/Utils.php
@@ -294,6 +294,32 @@
    }
    /**
     * rcube:utils::anytodatetime()
     */
    function test_anytodatetime()
    {
        $test = array(
            '2013-04-22' => '2013-04-22',
            '2013/04/22' => '2013-04-22',
            '2013.04.22' => '2013-04-22',
            '22-04-2013' => '2013-04-22',
            '22/04/2013' => '2013-04-22',
            '22.04.2013' => '2013-04-22',
            '04/22/2013' => '2013-04-22',
            '22.4.2013'  => '2013-04-22',
            '20130422'   => '2013-04-22',
            '1900-10-10' => '1900-10-10',
            '01-01-1900' => '1900-01-01',
            '01/30/1960' => '1960-01-30'
        );
        foreach ($test as $datetime => $ts) {
            $result = rcube_utils::anytodatetime($datetime);
            $this->assertSame($ts, $result ? $result->format('Y-m-d') : '', "Error parsing date: $datetime");
        }
    }
    /**
     * rcube:utils::normalize _string()
     */
    function test_normalize_string()