将缺少月份和日期的日期转换为Perl中的特定格式

时间:2018-12-22 06:13:59

标签: perl date

我只使用Perl一周,所以希望有人可以在这里提供帮助。

我在编写过程中获得了一些帮助的脚本将制表符分隔的文件导入到哈希中,其中一列包含以YYYYMMDD存储的日期。这被输出为日月年的文件(例如20180712打印为2018年7月12日)。我在How can I change the date formats in Perl?处找到了一种转换方法,如下所示:

my $date = '20111230';
my @months = ('January','February','March','April','May','June','July','August','September','October','November','December');

if($date =~ m/^(\d{4})(\d{2})(\d{2})$/){
    print $3 . ' ' . $months[$2-1] . ' ' . $1;
}               

但是,有时日期仅存储为一年和一个月,在极少数情况下,仅存储一年。这存储在散列中,用零代替日期(如果需要,可以替换为月)。因此,我要求将20180700打印为2018年7月,并将20180000打印为2018。

我可以修改代码以检查最后两个字符是否为00,然后仅打印月份和年份,并同样检查最后四个字符是否为0000等,但是还有一种更优雅的方法。 >

2 个答案:

答案 0 :(得分:3)

丢失日期/月份为00的格式定义得很好,但是它编码的特殊情况与yyyymmdd格式不一致。我看不出有什么方法可以避免对这些特殊情况进行显式测试,因为这些特殊情况下每天/每月都被排除在外。

我建议不要使用正则表达式来选择日期时间,因为有很好的模块可以完成这项工作。即使这个例子很简单,工作也趋于发展。同样,即使在简单的情况下,使用好的工具也没有错。

使用核心模块Time::Piece

use warnings;
use strict;
use feature 'say';

use Time::Piece;

my $d = shift || '20180712';

my $date = fmt_date($d);

say $date;

sub fmt_date {
    my ($date) = @_;         
    my ($yr, $mm, $dd) = grep { $_ != 0 } unpack "A4A2A2", $date;
    my $d_fmt;

    if ($yr and $mm and $dd) {
        $d_fmt = Time::Piece
            ->strptime($date, "%Y%m%d")
            ->strftime("%d %B %Y");
    }   
    elsif (not $dd and $mm) {
        $d_fmt = Time::Piece
            ->strptime($yr.$mm.'01', "%Y%m%d")
            ->strftime("%B %Y");
    }   
    elsif (not $mm) {
        $d_fmt = $yr 
    }   
    return $d_fmt;
}

我过滤了unpack返回的列表,以便不必处理00 strings ;这样,相应的变量将为undef,可以更简单地对其进行测试。

strptime返回一个Time::Piece对象,直接在其上调用strftime方法,并以所需格式返回一个字符串。如果这些日期还有更多工作要做,您当然可以将对象存储在变量中,然后从中形成字符串并返回两者。

但是,这引起了一个设计问题:如果不指定日期/星期一,应该是什么样的日期?处理日期时,通常的解决方案是将其设置为01,然后应用程序可以仅使用所需的部分。

这可以变得更紧凑,甚至可能更“小”,但是当您必须通过一系列测试时,我建议不要担心优雅。

用于日期时间处理的另一个更大,更全面的选项是DateTime模块。


例如

sub fmt_date {
    my ($date) = @_;
    my ($yr, $mm, $dd) = grep { $_ != 0 } unpack "A4A2A2", $date;

    my $dt_obj = Time::Piece->strptime(
        $yr . ($mm // '01') . ($dd // '01'), "%Y%m%d"  # legit format
    );

    my $d_fmt = do {
        if ($yr and $mm and $dd) { $dt_obj->strftime("%d %B %Y") }
        elsif (not $dd and $mm)  { $dt_obj->strftime("%B %Y")    }
        elsif (not $mm)          { $dt_obj->strftime("%Y")       }  # or, $yr 
    };  

    return wantarray ? ($d_fmt, $dt_obj) : $d_fmt;
}

其中wantarray知道呼叫方context,因此现在可以将其称为

my ($date, $obj) = fmt_date($d);

或为

my $date = fmt_date($d);

取决于调用者是否希望该对象进一步工作。

答案 1 :(得分:0)

zdim建议使用unpack(),在这种情况下,它并不比正则表达式好多少。所以我想说原始的解决方案已经可以了。您只需要添加一些代码即可完成它,例如:

my $date = '20111230';
my @months = ('January','February','March','April','May','June','July','August','September','October','November','December');

if ($date =~ m/^(\d{4})(\d{2})(\d{2})$/){
    print ($3 > 0 ? $3 . ' ' : '') . ($2 > 0 ? $months[$2-1] . ' ' : '') . $1;
} else {
    die "Invalid date: $date";
}