Como converter esse formato de registro de data e hora em outro formato em Perl?

1

Estou tentando projetar uma abordagem Perl / ... que converta meu formato de registro de data e hora ( ddMMyyyy-HHmm+0300 ) no formato timestamp / time / ... ( yyyy-MM-dd'T'HH:mm:00 ) usado pelo sistema de análise de dados WEKA. Estou fazendo inicialmente o arquivo de dados WEKA do comando paste e a remoção da primeira coluna com AWK . Não deve haver nenhuma limitação que torne o problema mais difícil do que realmente é, mas possivelmente as cotações na primeira variável. Eu acho que a abordagem (3) pode ser mais viável, ou seja, use diretamente POSIX::strftime function (Deathgrip)

  1. Problema difícil na Seção 1
  2. Abordagem mais fácil sem aspas nos dados da Seção 2
  3. abordagem de POSIX::strftime e segmento semelhante O formato de strptime do Perl difere do strftime

Exemplo da entrada

23072017-2200+0300
  • Saída esperada

    2017-07-23'T'22:00:00
    

Exemplo completo de linha CSV sem aspas, mas com sublinhados, pode ser mais difícil

 Ni, Aika, Aika_l, Un, Unen, Unen_kesto, Uniluokat_R, Uniluokat_k, Uniluokat_s, HRV_RMSSD_a, HRV_RMSSD_i, Kokonaisp, Palautumisen_k, Hermoston_t, Syke_ave_m, Syke_a, Syke_l, Hengitystiheys_ave_m, Hengitystiheys_a, Hengitystiheys_min_a, Liikeaktiivisuus_l, Liikeaktiivisuus_a, Paivamaara_l
 "Masi", 23072010-2200+0300, 24072010-0600+0300, 70, 7h40, 6h30, 1h40, 3h40, 1h10, 67.0, 43.0, 24.0, 430, 30, 70, 50, 40, 20, 10, 10, 150, 260, 24.10.2010
 "Masi", 23072010-2200+0300, 24072010-0600+0300, 70, 7h40, 6h30, 1h40, 3h40, 1h10, 67.0, 43.0, 24.0, 430, 30, 70, 50, 40, 20, 10, 10, 150, 260, 24.10.2010

Saída esperada

 Ni, Aika, Aika_l, Un, Unen, Unen_kesto, Uniluokat_R, Uniluokat_k, Uniluokat_s, HRV_RMSSD_a, HRV_RMSSD_i, Kokonaisp, Palautumisen_k, Hermoston_t, Syke_ave_m, Syke_a, Syke_l, Hengitystiheys_ave_m, Hengitystiheys_a, Hengitystiheys_min_a, Liikeaktiivisuus_l, Liikeaktiivisuus_a, Paivamaara_l
 "Masi", 2010-07-23'T‌​'22:00:00, 2010-07-24'T‌​'06:00:00, 70, 7h40, 6h30, 1h40, 3h40, 1h10, 67.0, 43.0, 24.0, 430, 30, 70, 50, 40, 20, 10, 10, 150, 260, 24.10.2010
 "Masi", 2010-07-23'T‌​'22:00:00, 2010-07-24'T‌​'06:00:00, 70, 7h40, 6h30, 1h40, 3h40, 1h10, 67.0, 43.0, 24.0, 430, 30, 70, 50, 40, 20, 10, 10, 150, 260, 24.10.2010

1. Tentativa de script que você pode chamar por script.pl filename

Acho que o uso do parser Text::CSV é muito complicado porque meu conjunto de dados é mais simples do que o caso de uso. Então eu acho que uma abordagem regex simples pode ser possível

#!/usr/bin/env perl
# https://stackoverflow.com/a/33995620/54964

## Data prepared like this for the script
# paste -d" " log.csv data.csv | awk '{$1=""; print $0}' > weka.data.csv
# cp $HOME/Data/weka.data.csv $HOME/Workspace/
#
# Maybe, this all could be integrated into Perl script

use strict;
use warnings;

use Text::CSV;

my $csv = Text::CSV->new( { binary => 1, eol => "\n" } );

while ( my $row = $csv->getline( \*ARGV ) ) {
    s/\n/ /g for @$row;
    $csv->print( \*STDOUT, $row );

    # TODO regex
    #convert ddMMyyyy-HHmm+0300 to yyyy-MM-dd'T'HH:mm:00    
}

2. Aproximação Perl Regex

Pseudocódigo em que a abordagem não pode funcionar porque não há substituições de variáveis, como transportar dd para o resultado

# TODO s/ddMMyyyy-HHmm+0300/$3-$2-$1'T'$4:$5:00/;
perl -pe s/([0-3][0-9])(([0-1][0-9]))(20[0-9]{2})([0-2][0-9])([0-5][0-9])+0300/$3-$2-$1'T'$4:$5:00/;

onde

  • dd por ([0-3][0-9]) / $3
  • da mesma forma para MM por ([0-1][0-9]) / $2
  • yyyy da mesma forma que (20[0-9]{2}) / $1
  • - literalmente
  • HH 24H tempo por ([0-5][0-9]) / $4
  • mm por ([0-5][0-9]) ) / $5
  • +0300 / remove simplesmente

Seria ótimo ter o regex em algum formato mais legível.

Testando a proposta de Sundeep no comentário

Código

#!/bin/bash
# https://stackoverflow.com/a/33995620/54964

s='"Masi", 23072010-2200+0300, 24072010-0600+0300 70, 7h40'

echo "$s" | perl -pe 's/\b(\d\d)(\d\d)(\d{4})-(\d\d)(\d\d)\+\d{4}\b/$3-$2-$1\x27T<200c><200b>\x27$4:$5:00/g' y $csv = Text::CSV->new( { binary => 1, eol => "\n" } );

A saída é a esperada para uma linha

"Masi", 2010-07-23'T‌​'22:00:00, 2010-07-24'T‌​'06:00:00, 70, 7h40

Aplicando na linha completa apenas substituindo a variável s content, output conforme esperado

"Masi", 2010-07-23'T‌​'22:00:00, 2010-07-24'T‌​'06:00:00, 70, 7h40, 6h30, 1h40, 3h40, 1h10, 67.0, 43.0, 24.0, 430, 30, 70, 50, 40, 20, 10, 10, 150, 260, 24.10.2010

TODO abordagem completa com abordagem multilinha com capacidade de pular o cabeçalho

Testando a proposta motivada de Deathgrip

Código

#!/usr/bin/env perl
# https://stackoverflow.com/a/33995620/54964

use strict;
use warnings;
# https://stackoverflow.com/a/20007784/54964
# http://perldoc.perl.org/POSIX.html
use Time::Piece;
use POSIX;

# TODO breaks because of false brackets
#my $input = '"Masi", 2010-07-23'T<200c><200b>'22:00:00, 2010-07-24'T<200c><200b>'06:00:00, 70, 7h40, 6h30, 1h40, 3h40, 1h10, 67.0, 43.0, 24.0, 430, 30, 70, 50, 40, 20, 10, 10, 150, 260, 24.10.2010'

my $str = '23072017-2200+0300';
my $f = '%d%m%dY-%H%M+0300';
#my $t = POSIX::strftime($str, $f); # fails!
my $t = strftime($str, $f); # fails!

print "$t\n";

Saída

Usage: POSIX::strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1) at prepare.data3.pl line 22.

OS: Debian 9

    
por Léo Léopold Hertz 준영 24.07.2017 / 17:28

2 respostas

2
$ perl -pe 's/\b(\d\d)(\d\d)(\d{4})-(\d\d)(\d\d)\+\d{4}\b/$3-$2-$1\x27T\x27$4:$5:00/g' ip.csv
 Ni, Aika, Aika_l Un, Unen, Unen_kesto, Uniluokat_R, Uniluokat_k, Uniluokat_s, HRV_RMSSD_a, HRV_RMSSD_i, Kokonaisp, Palautumisen_k, Hermoston_t, Syke_ave_m, Syke_a, Syke_l, Hengitystiheys_ave_m, Hengitystiheys_a, Hengitystiheys_min_a, Liikeaktiivisuus_l, Liikeaktiivisuus_a, Paivamaara_l
 "Masi", 2010-07-23'T'22:00:00, 2010-07-24'T'06:00:00 70, 7h40, 6h30, 1h40, 3h40, 1h10, 67.0, 43.0, 24.0, 430, 30, 70, 50, 40, 20, 10, 10, 150, 260, 24.10.2010
 "Masi", 2010-07-23'T'22:00:00, 2010-07-24'T'06:00:00 70, 7h40, 6h30, 1h40, 3h40, 1h10, 67.0, 43.0, 24.0, 430, 30, 70, 50, 40, 20, 10, 10, 150, 260, 24.10.2010
  • \b é limite de palavras
  • (\d\d) captura dois dígitos consecutivos, (\d{4}) captura quatro deles e assim por diante
  • \x27 é para aspas simples. Se houver dígitos não relacionados depois disso, talvez seja melhor usar representação octal 7
  • como a pesquisa e a substituição são apenas para o formato ddMMyyyy-HHmm+0300 específico, isso não afetará o cabeçalho. Ainda se necessário, basta adicionar if $.>1 após o comando substituto

Provavelmente, os comandos paste+awk usados para criar a entrada podem ser incorporados facilmente a esse comando, mas precisariam que essa informação fosse adicionada à pergunta

    
por 25.07.2017 / 17:25
1

Aqui está o que eu teria feito:

#!/usr/bin/env perl
# https://stackoverflow.com/a/33995620/54964

use strict;
use warnings;
# https://stackoverflow.com/a/20007784/54964
# http://perldoc.perl.org/POSIX.html
use POSIX qw(strftime);
use DateTime;
use DateTime::Format::Strptime qw(strptime);

my $str = '23072017-2200+0300';
my $dtime = strptime( '%d%m%Y-%H%M%z', $str );
my $f = '%Y-%m-%d\'T\'%H:%M:%S';
my $t = strftime( $f, 0, $dtime->minute, $dtime->hour, $dtime->day, $dtime->month-1, $dtime->year-1900, -1, -1, $dtime->time_zone );

print "$t\n";

Saída conforme esperado no campo de tempo

2017-07-23'T'22:00:00
    
por 25.07.2017 / 22:08