Perl или Python: конвертировать дату из дд / мм / гггг в гггг-мм-дд - PullRequest
12 голосов
/ 02 ноября 2010

У меня есть много дат в столбце в файле CSV, который мне нужно конвертировать из формата дд / мм / гггг в гггг-мм-ддНапример, 17.01.2010 следует преобразовать в 2010-01-17.

Как я могу сделать это на Perl или Python?

Ответы [ 8 ]

30 голосов
/ 02 ноября 2010

Если вы гарантированно имеете правильно сформированные данные, состоящие только из однотонной даты в формате DD-MM-YYYY, то это работает:

# FIRST METHOD
my $ndate = join("-" => reverse split(m[/], $date));

Это работает на $date, держащем "07/04/1776", но не работает "это 17.01.2010 и 17.01.2010 там". Вместо этого используйте:

# SECOND METHOD
($ndate = $date) =~ s{
    \b
      ( \d \d   )
    / ( \d \d   )
    / ( \d {4}  )
    \b
}{$3-$2-$1}gx;

Если вы предпочитаете более «грамматическое» регулярное выражение, чтобы его было проще поддерживать и обновлять, вы можете вместо этого использовать:

# THIRD METHOD
($ndate = $date) =~ s{
    (?&break)

              (?<DAY>    (?&day)    )
    (?&slash) (?<MONTH>  (?&month)  )
    (?&slash) (?<YEAR>   (?&year)   )

    (?&break)

    (?(DEFINE)
        (?<break> \b     )
        (?<slash> /      )
        (?<year>  \d {4} )
        (?<month> \d {2} )
        (?<day>   \d {2} )
    )
}{
    join "-" => @+{qw<YEAR MONTH DAY>}
}gxe;

Наконец, если у вас есть данные Unicode, вы можете быть немного осторожнее.

# FOURTH METHOD
($ndate = $date) =~ s{
    (?&break_before)
              (?<DAY>    (?&day)    )
    (?&slash) (?<MONTH>  (?&month)  )
    (?&slash) (?<YEAR>   (?&year)   )
    (?&break_after)

    (?(DEFINE)
        (?<slash>     /                  )
        (?<start>     \A                 )
        (?<finish>    \z                 )

        # don't really want to use \D or [^0-9] here:
        (?<break_before>
           (?<= [\pC\pP\pS\p{Space}] )
         | (?<= \A                )
        )
        (?<break_after>
            (?= [\pC\pP\pS\p{Space}]
              | \z
            )
        )
        (?<digit> \d            )
        (?<year>  (?&digit) {4} )
        (?<month> (?&digit) {2} )
        (?<day>   (?&digit) {2} )
    )
}{
    join "-" => @+{qw<YEAR MONTH DAY>}
}gxe;

Вы можете увидеть, как работает каждый из этих четырех подходов, когда сталкиваетесь с примерами входных строк, подобных этим:

my $sample  = q(17/01/2010);
my @strings =  (
    $sample,  # trivial case

    # multiple case
    "this $sample and that $sample there",

    # multiple case with non-ASCII BMP code points
    # U+201C and U+201D are LEFT and RIGHT DOUBLE QUOTATION MARK
    "from \x{201c}$sample\x{201d} through\xA0$sample",

    # multiple case with non-ASCII code points
    #   from both the BMP and the SMP 
    # code point U+02013 is EN DASH, props \pP \p{Pd}
    # code point U+10179 is GREEK YEAR SIGN, props \pS \p{So}
    # code point U+110BD is KAITHI NUMBER SIGN, props \pC \p{Cf}
    "\x{10179}$sample\x{2013}\x{110BD}$sample",
);

Теперь, разрешив $date быть итератором foreach в этом массиве, мы получим следующий вывод:

Original is:   17/01/2010
First method:  2010-01-17
Second method: 2010-01-17
Third method:  2010-01-17
Fourth method: 2010-01-17

Original is:   this 17/01/2010 and that 17/01/2010 there
First method:  2010 there-01-2010 and that 17-01-this 17
Second method: this 2010-01-17 and that 2010-01-17 there
Third method:  this 2010-01-17 and that 2010-01-17 there
Fourth method: this 2010-01-17 and that 2010-01-17 there

Original is:   from “17/01/2010” through 17/01/2010
First method:  2010-01-2010” through 17-01-from “17
Second method: from “2010-01-17” through 2010-01-17
Third method:  from “2010-01-17” through 2010-01-17
Fourth method: from “2010-01-17” through 2010-01-17

Original is:   ?17/01/2010–?17/01/2010
First method:  2010-01-2010–?17-01-?17
Second method: ?2010-01-17–?2010-01-17
Third method:  ?2010-01-17–?2010-01-17
Fourth method: ?2010-01-17–?2010-01-17

Теперь давайте предположим, что вы на самом деле do хотите сопоставить не-ASCII цифры. Например:

   U+660  ARABIC-INDIC DIGIT ZERO
   U+661  ARABIC-INDIC DIGIT ONE
   U+662  ARABIC-INDIC DIGIT TWO
   U+663  ARABIC-INDIC DIGIT THREE
   U+664  ARABIC-INDIC DIGIT FOUR
   U+665  ARABIC-INDIC DIGIT FIVE
   U+666  ARABIC-INDIC DIGIT SIX
   U+667  ARABIC-INDIC DIGIT SEVEN
   U+668  ARABIC-INDIC DIGIT EIGHT
   U+669  ARABIC-INDIC DIGIT NINE

или даже

 U+1D7F6  MATHEMATICAL MONOSPACE DIGIT ZERO
 U+1D7F7  MATHEMATICAL MONOSPACE DIGIT ONE
 U+1D7F8  MATHEMATICAL MONOSPACE DIGIT TWO
 U+1D7F9  MATHEMATICAL MONOSPACE DIGIT THREE
 U+1D7FA  MATHEMATICAL MONOSPACE DIGIT FOUR
 U+1D7FB  MATHEMATICAL MONOSPACE DIGIT FIVE
 U+1D7FC  MATHEMATICAL MONOSPACE DIGIT SIX
 U+1D7FD  MATHEMATICAL MONOSPACE DIGIT SEVEN
 U+1D7FE  MATHEMATICAL MONOSPACE DIGIT EIGHT
 U+1D7FF  MATHEMATICAL MONOSPACE DIGIT NINE

Итак, представьте, что у вас есть дата в математических моно-космических цифрах, например:

$date = "\x{1D7F7}\x{1D7FD}/\x{1D7F7}\x{1D7F6}/\x{1D7F8}\x{1D7F6}\x{1D7F7}\x{1D7F6}";

Код Perl будет отлично работать на этом:

Original is:   ??/??/????
First method:  ????-??-??
Second method: ????-??-??
Third method:  ????-??-??
Fourth method: ????-??-??

Я думаю, вы обнаружите, что Python имеет довольно поврежденную мозговую модель Unicode, чья нехватка поддержки абстрактных символов и строк независимо от содержимого делает смешно трудным писать такие вещи.

Также сложно написать четкие регулярные выражения в Python, где вы отделяете объявление подвыражений от их выполнения, так как блоки (?(DEFINE)...) там не поддерживаются. Черт, Python даже не поддерживает свойства Unicode. Это просто не подходит для работы с регулярными выражениями Unicode из-за этого.

Но, эй, если вы считаете, что это плохо в Python по сравнению с Perl ( и, конечно, ), просто попробуйте любой другой язык. Я не нашел такого, который еще хуже для такой работы.

Как видите, вы сталкиваетесь с реальными проблемами, когда запрашиваете решения для регулярных выражений на нескольких языках. Прежде всего, решения сложно сравнивать из-за различных вкусовых выражений. Но также потому, что ни один другой язык не может сравниться с Perl по мощности, выразительности и удобству обслуживания в его регулярных выражениях. Это может стать еще более очевидным, когда произвольный Unicode входит в картину.

Так что, если вы просто хотели Python, вы должны были попросить только об этом. В противном случае это ужасно несправедливый конкурс, который Python почти всегда проигрывает; просто слишком грязно, чтобы в Python такие вещи были правильными, не говоря уже о и правильных и чистых . Это требует большего, чем может произвести.

Напротив, регулярные выражения Perl превосходны в обоих.

17 голосов
/ 02 ноября 2010
>>> from datetime import datetime
>>> datetime.strptime('02/11/2010', '%d/%m/%Y').strftime('%Y-%m-%d')
'2010-11-02'

или более хакерский способ (который не проверяет правильность значений):

>>> '-'.join('02/11/2010'.split('/')[::-1])
'2010-11-02'
>>> '-'.join(reversed('02/11/2010'.split('/')))
'2010-11-02'
11 голосов
/ 02 ноября 2010

Используйте Time :: Piece (в ядре начиная с 5.9.5), очень похоже на принятое решение Python, поскольку оно предоставляет функции strptime и strftime:

use Time::Piece;
my $dt_str = Time::Piece->strptime('13/10/1979', '%d/%m/%Y')->strftime('%Y-%m-%d');

или

$ perl -MTime::Piece
print Time::Piece->strptime('13/10/1979', '%d/%m/%Y')->strftime('%Y-%m-%d');
1979-10-13
$ 
6 голосов
/ 02 ноября 2010

Перейти с Perl: пакет datetime Python просто сломан. Вы можете просто сделать это с помощью регулярных выражений, чтобы поменять местами даты, например,

echo "17/01/2010" | perl -pe 's{(\d+)/(\d+)/(\d+)}{$3-$2-$1}g'

Если вам нужно проанализировать эти даты (например, чтобы вычислить их день недели или другие операции календарного типа), посмотрите DateTimeX :: Easy (вы можете установить его с помощью apt-get в Ubuntu ):

perl -MDateTimeX::Easy -e 'print DateTimeX::Easy->parse("17/01/2010")->ymd("-")'
5 голосов
/ 02 ноября 2010

Perl:

while (<>) {
  s/(^|[^\d])(\d\d)\/(\d\d)\/(\d{4})($|[^\d])/$4-$3-$2/g;
  print $_;
}

Тогда вам просто нужно запустить:

perl MyScript.pl < oldfile.txt > newfile.txt
1 голос
/ 02 ноября 2010

Perl:

my $date =~ s/(\d+)\/(\d+)\/(\d+)/$3-$2-$1/;
0 голосов
/ 02 ноября 2010

В великолепной форме perl-oneliner:

echo 17/01/2010 | perl -p -e "chomp;  join('-', reverse split /\//);"

А если серьезно, я бы сделал это так:

#!/usr/bin/env perl
while (<>) {
    chomp;
    print join('-', reverse split /\//), "\n";
}

, который будет работать на трубе, конвертируя и печатая одну дату залиния.

0 голосов
/ 02 ноября 2010

В Perl вы можете сделать:

use strict;
while(<>) {
    chomp;
    my($d,$m,$y) = split/\//;
    my $newDate = $y.'-'.$m.'-'.$d;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...