2010-11-02 8 views
12

Ho molte date in una colonna in un file CSV che devo convertire da gg/mm/aaaa aa aaaa-mm-gg formato. Ad esempio il 17/01/2010 dovrebbe essere convertito al 2010-01-17.Perl o Python: Convertire la data da gg/mm/aaaa-mm-gg

Come posso farlo in Perl o Python?

+6

C'è una grande differenza tra la soluzione del problema per isolati, stringhe ASCII ben formate con nient'altro in loro, e risolvendolo per le stringhe ovunque si presentino come date in file di testo presumibilmente arbitrari - ** specialmente ** se sono dati Unicode corretti, non solo ASCII legacy. – tchrist

risposta

17
>>> from datetime import datetime 
>>> datetime.strptime('02/11/2010', '%d/%m/%Y').strftime('%Y-%m-%d') 
'2010-11-02' 

o più hacker modo (che non verifica la validità dei valori):

>>> '-'.join('02/11/2010'.split('/')[::-1]) 
'2010-11-02' 
>>> '-'.join(reversed('02/11/2010'.split('/'))) 
'2010-11-02' 
+1

Ciò funziona perfettamente. Grazie! – FunLovinCoder

+4

Non funziona su dati reali. Stai assumendo che non ci sia nient'altro nella stringa. – tchrist

+5

La data è un campo che estraggo da un record CSV. Non c'è nient'altro nel campo. Funziona perfettamente per le mie esigenze. – FunLovinCoder

1

Perl:

my $date =~ s/(\d+)\/(\d+)\/(\d+)/$3-$2-$1/; 
+5

Che odori di LTS (sindrome degli stuzzicadenti pendente). – tchrist

+1

@tchrist: alcuni di noi adorano il nostro LTS – ysth

+1

@ysth: Devo confessare a volte l'utilizzo di LTS per adorabili piccole immagini artistiche ASCII esteticamente camuffate come codice Perl. ☺ – tchrist

0

In Perl si può fare:

use strict; 
while(<>) { 
    chomp; 
    my($d,$m,$y) = split/\//; 
    my $newDate = $y.'-'.$m.'-'.$d; 
} 
+1

funzionerà se c'è solo una data e nient'altro nella riga – Toto

5

Perl:

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

Poi basta eseguire:

perl MyScript.pl <oldfile.txt> newfile.txt 
+0

Non c'è bisogno di 'MyScript.pl' o del nuovo file. 'perl -pi -e 's {(\ d {2})/(\ d {2})/(\ d {4})} {$ 3- $ 2- $ 1} g' file.txt' eseguirà un inline sostituire. – Zaid

+0

@Zaid, che non farà la cosa giusta su stringhe come "123/45/67890"; li renderà "16789-56-230". Dovrebbe lasciarli soli, penso. È necessario impostare alcuni limiti. – tchrist

+0

E qui arrivano i confini! –

6

Go con Perl: la datetime package Python è solo rotto. Si potrebbe semplicemente farlo con espressioni regolari per scambiare le parti della data in giro, ad esempio

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

Se si ha bisogno di analizzare queste date (ad esempio, per calcolare il loro giorno della settimana o altre operazioni di calendario-tipo), guardare in DateTimeX::Easy (è possibile installarlo con apt-get sotto Ubuntu):

perl -MDateTimeX::Easy -e 'print DateTimeX::Easy->parse("17/01/2010")->ymd("-")' 
+4

Cosa c'è di sbagliato con il datetime di python? La risposta di SilentGhost è come farei io stesso, a meno che tu non conosca una limitazione. – AndrewF

+2

i moduli DateTime sono eccessivi, Time :: Piece è disponibile su CPAN ed è stato core da 5.9.5/5.10. – MkV

-2

In gloriosa forma perl-oneliner:

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

Ma sul serio vorrei fare in questo modo:

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

Che funzionerà su una pipe, convertendo e stampando una data per riga.

+1

Questo funziona solo per stringhe di input super semplici, non per file di testo arbitrari. – tchrist

+2

@ chi dice che deve funzionare per il testo arbitrario? Non risolvere problemi che non esistono. I dati potrebbero essere stati convalidati nel formato esistente. Non sappiamo in un modo o nell'altro, quindi perché formulare ipotesi? – frankc

+0

Infatti, l'autore ha dichiarato altrove che il testo arbitrario non è richiesto in quanto sono stringhe di testo ben note da un file CSV. Tchrist ha fatto lo stesso commento su quel codice altrove con altrettanto poca giustificazione. – Sorpigal

29

Se si sono garantiti per avere i dati ben formati, comprensivi di nient'altro che una data Singleton nel formato-AAAA GG-MM, allora questo funziona:

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

che funziona su una $date azienda "07/04/1776 "ma fallisce" questo 17/01/2010 e quel 17/01/2010 lì ". Invece, l'uso:

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

Se si preferisce un'espressione regolare più "grammaticale", in modo che sia più facile da mantenere e aggiornamento, è possibile invece usare questo:

# 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; 

Infine, se si dispone di dati Unicode , potresti voler essere un po 'più attento.

# 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; 

Si può vedere come ciascuno di questi quattro approcci compie quando di fronte a stringhe di input campione come questi:

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", 
); 

Ora lasciarsi $date essere un iteratore foreach attraverso tale matrice, otteniamo questo output:

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 

Ora supponiamo che in realtà fai vogliono abbinare no cifre n-ASCII. Per esempio:

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 

o anche

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 

Quindi immaginate di avere una data in cifre spaziatura fissa matematiche, in questo modo:

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

Il codice Perl funziona bene su che:

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

I th l'inchiostro scoprirà che Python ha un modello Unicode piuttosto danneggiato dal cervello, la cui mancanza di supporto per caratteri e stringhe astratte a prescindere dal contenuto rende ridicola la difficoltà di scrivere cose come questa.

È anche difficile scrivere espressioni regolari leggibili in Python in cui si disaccoppia la dichiarazione delle sottoespressioni dalla loro esecuzione, poiché i blocchi (?(DEFINE)...) non sono supportati lì. Heck, Python non supporta nemmeno le proprietà Unicode. A causa di ciò, non è adatto per il lavoro regex Unicode.

Ma hey, se si pensa che è male in Python rispetto a Perl (e certamente è), basta provare qualsiasi altra lingua. Non ne ho trovato uno che non sia ancora peggio per questo tipo di lavoro.

Come potete vedere, si verificano problemi reali quando si richiedono soluzioni regex da più lingue. Prima di tutto, le soluzioni sono difficili da confrontare a causa dei diversi sapori regex. Ma anche perché nessun altro linguaggio può essere paragonato a Perl per potenza, espressività e manutenibilità nelle sue espressioni regolari. Questo può diventare ancora più ovvio una volta che l'Unicode arbitrario entra nell'immagine.

Quindi, se volessi solo Python, avresti dovuto chiedere solo quello. Altrimenti è una gara terribilmente ingiusta che Python perderà quasi sempre; è troppo disordinato per ottenere cose come questa corrette in Python, per non parlare di sia corrette e pulite. Questo sta chiedendo più di quello che può produrre.

Al contrario, le regex di Perl eccellono in entrambe.

+0

thats ... awesome –

+6

++ per il valore di educazione. – Zaid

+2

@ Zaid, @ Frost: sei il benvenuto. Le regex Unicode sono molto nella mia testa in questi giorni, e sto cercando di insegnare alle persone che si può davvero scrivere regex pulite che sono contemporaneamente portatili, leggibili e mantenibili. Sto cercando di mettere giù il mito delle "regexes sono imperscrutabili". Ovviamente, se non è possibile utilizzare commenti, spazi bianchi, identificativi alfabetici o disaccoppiare le dichiarazioni dalla loro esecuzione, è completamente senza speranza. Quindi non farlo: usa tutte quelle tecniche nelle regex, proprio come in qualsiasi altro linguaggio di programmazione. – tchrist

11

Use Time :: Piece (nel core dal 5.9.5), molto simile alla soluzione Python accettato, in quanto fornisce le strptime e strftime funzioni:

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

o

$ perl -MTime::Piece 
print Time::Piece->strptime('13/10/1979', '%d/%m/%Y')->strftime('%Y-%m-%d'); 
1979-10-13 
$ 
+0

Sono sempre preoccupato per questo tipo di soluzioni perché convertono solo i dati che sono date valide e non gestiranno i dati sporchi. Sarebbe bello se tutti i dati fossero puliti, ma trovo che spesso non lo siano. –

+0

Quali date non valide stai pensando? La maggior parte causerebbe un errore nel generare un errore. – MkV

+0

Sì, quindi cosa mettere in atto quando si verifica un errore? –