【问题标题】:Perl or Python: Convert date from dd/mm/yyyy to yyyy-mm-ddPerl 或 Python:将日期从 dd/mm/yyyy 转换为 yyyy-mm-dd
【发布时间】:2011-05-03 22:27:57
【问题描述】:

我在 CSV 文件的列中有很多日期,我需要将这些日期从 dd/mm/yyyy 转换为 yyyy-mm-dd 格式。例如 17/01/2010 应转换为 2010-01-17。

如何在 Perl 或 Python 中做到这一点?

【问题讨论】:

  • 解决孤立的、格式良好的 ASCII 字符串问题与解决字符串问题之间存在很大差异,因为它们在可能是任意文本文件中作为日期出现 - 尤其是 如果它是正确的 Unicode 数据,而不仅仅是传统的 ASCII。

标签: python perl date text-processing


【解决方案1】:

如果您保证有格式良好的数据,只包含 DD-MM-YYYY 格式的单例日期,那么这样可以:

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

这适用于持有“07/04/1776”的$date,但在“这个 17/01/2010 和那个 01/17/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

现在让我们假设您确实确实想要匹配非 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 中正确处理这样的事情实在是太麻烦了,更不用说 both 正确和干净了。这要求比它所能产生的要多。

相比之下,Perl 的正则表达式在这两个方面都表现出色。

【讨论】:

  • ++ 用于教育价值。
  • @Zaid,@Frost:不客气。这些天来,Unicode 正则表达式在我的脑海中占据了很大的位置,我正试图教人们你真的可以编写干净的正则表达式,同时又是可移植的、易读的和可维护的。我试图放下“正则表达式难以理解”的神话。当然,如果你不能使用 cmets、空格、字母标识符,或者将你的声明与它们的执行解耦,那是完全没有希望的。所以不要这样做:在正则表达式中使用所有这些技术,就像在任何其他编程语言中一样。
  • 我想知道在这里使用(?(DEFINE)...) 是否真的可以通过良好的字符串插值来完成任何事情:$slash = qr(/); $start = qr/\A/; 等,然后将它们插入替换而不是写(?&amp;slash)等
  • -1 引起你的注意,如果你能回答我的问题,当然会回复! :)
【解决方案2】:
>>> 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'

【讨论】:

  • 这不适用于真实数据。您假设字符串中没有其他内容。
  • 日期是我从 CSV 记录中提取的字段。该领域没有其他任何东西。它非常适合我的要求。
  • @tchrist:问题非常明确:我需要从dd/mm/yyyy 转换为yyyy-mm-dd 格式。
  • 我和silentghost在这个...不要解决不存在的问题。为什么假设数据未以其原始形式进行验证?
  • @FrankC,我解决了问题。它并没有真正完全指定,所以我给出了多个答案,显示哪些在哪里有效,哪些在哪里失败。我尽量不去假设原始问题中没有的东西,特别是如果它可能被用作货物崇拜编程。所有split() 解决方案,包括我的解决方案,都在文本文件中中断日期;它们仅适用于孤立的实例,这不是问题所问的。这可能是他想要的,但他没有要求。因此,具有不同附带条件和方法的多个答案。
【解决方案3】:

使用 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
$ 

【讨论】:

  • 我一直担心这类解决方案,因为它们只转换有效日期的数据,不会处理脏数据。如果所有数据都是干净的就好了,但我发现它们通常不是。
  • 您在考虑哪些无效日期?大多数会导致 strptime 抛出错误。
【解决方案4】:

使用 Perl:datetime Python 包刚刚损坏。您可以使用正则表达式来交换日期部分,例如

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

如果您确实需要解析这些日期(例如计算它们的星期几或其他日历类型的操作),请查看DateTimeX::Easy(您可以在 Ubuntu 下使用apt-get 安装它):

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

【讨论】:

  • python的日期时间有什么问题? SilentGhost 的回答是我自己会怎么做,除非你知道有限制。
  • DateTime 模块太过分了,Time::Piece 在 CPAN 上可用,并且自 5.9.5 / 5.10 以来一直是核心。
【解决方案5】:

Perl:

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

然后你只需要运行:

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

【讨论】:

  • 不需要MyScript.pl 或新文件。 perl -pi -e 's{(\d{2})/(\d{2})/(\d{4})}{$3-$2-$1}g' file.txt 将执行内联替换。
  • @Zaid,这不会在像“123/45/67890”这样的字符串上做正确的事情;这将使它们成为“16789-56-230”。我认为它应该让他们独自一人。你需要设置一些界限。
  • 界限来了!
【解决方案6】:

Perl:

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

【讨论】:

  • 有 LTS(倾斜牙签综合症)的味道。
  • @tchrist:我们中的一些人喜欢我们的 LTS
  • @ysth:我不得不承认,有时将 LTS 用于美观地伪装成 Perl 代码的可爱的小 ASCII 艺术图片。 ☺
【解决方案7】:

在 Perl 中你可以这样做:

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

【讨论】:

  • 如果一行中只有一个日期并且没有其他内容,这将起作用
【解决方案8】:

以华丽的 perl-oneliner 形式:

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

但说真的,我会这样做:

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

这将适用于管道,每行转换和打印一个日期。

【讨论】:

  • 这仅适用于超级简单的输入字符串,不适用于任意文本文件。
  • @who 说它必须适用于任意文本?不要解决不存在的问题。数据可能已以现有格式验证。我们不知道一种或另一种方式,所以为什么要做出任何假设?
  • 事实上,作者在其他地方已经声明不需要任意文本,因为这些是 CSV 文件中众所周知的文本字符串。 tchrist 对其他地方的代码发表了同样的评论,但几乎没有任何理由。
  • 问题规范刚刚提到,这样的日期字符串出现在文本文件中。它从未声明要更改的字符串实际上并未出现在文本文件中;事实上,他说他们是。因此,假装字符串完全孤立存在的解决方案会出错。你不能让人们回答所提出的问题。如果提问者在他的规范中更加小心,人们就不必猜测了。
  • 我不能因为你的答案而责怪你,这是非常精确和正确的,但这不会使任何不太精确的答案不正确。您声称对“文本文件中”的临时引用意味着“在需要首先解析然后转换日期的任意文本流中”,而我和其他人将这个问题正确解释为关于转换字符串表示获取该字符串后的日期,而不是将其从文本文件中解析出来。你不能因为回答了所提出的问题而不是它所暗示的更广泛的问题而让人们感到震惊。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 2016-06-21
  • 1970-01-01
相关资源
最近更新 更多