Perl如何与MySQL比较日期?

3

我很难想出如何使用perl比较MySQL日期和当前系统时间。

我有一个在cron作业中运行的脚本,如果当前系统日期/时间超过返回记录的日期/时间,它将发送通知:

一个应用程序显示一个表格视图:

EventId    Device        Location

CC: 123    something     BFE
TT: 456    anotherthing  BFE

脚本的工作方式是查找字段EventID中的值,从ID(数字部分)中解析类型(CC:,TT:等)。 ID是另一个数据库/表中的唯一ID,其中包含结束时间字段。 EventID本身不是唯一的,并且在表中可能有重复项。每个“类型”都有子例程,因为每种类型都有不同的数据库和/或表。
目标是脚本每分钟运行一次并切换Expired的值。某些内容可能会过期,进行更改,然后取消过期状态。
脚本运行良好,但存在一个问题,与时区相关,根据我迄今收到的反馈。如果我没有显式设置时区为'America/New_York'($now)以获取当前系统时间,则会出现错误($notz)。因此,我需要找到一种方法使MySQL返回的日期能够准确地与当前系统时间进行比较。 $now->set_time_zone('America/New_York')似乎也不起作用。
我不确定如何做到这一点,甚至不确定我迄今为止编写的代码是否是最佳方法(对Perl仍然相当新)。
#!/usr/bin/perl
use DBI;
use DateTime;
use DateTime::Format::MySQL;
use Switch;
my $now         = DateTime->now(time_zone => 'America/New_York');
my $notz        = DateTime->now();
my $maindb      = DBI=>connect(database);
my $seteventsql = qq { select * from view where EventId like 'IE:' or EventId like 'TT:' or EventId like 'CC:';};
my $commit      = $livedb->prepare($seteventsql);
$commit->execute() || die "could not set event: $DBI::errstr";

while(@events = $commit->fetchrow_array()) {
                (my $type, my $id) = split (/ /,$events[0]);
                $id =~ s|\D||g;
                switch ($type) {
                        case ('CC:') {check_expired_case($id);}
                        case ('TT:') {check_expired_task($id);}
                        case ('IE:') {check_expired_event($id);}
                }
}

sub check_expired_case {  
        my $id = shift; #id = 123
        my $sql = qq { select id, status_id, item_enddate from item where id = ?; };
        my $exec = $itemdb->prepare($sql);
        $exec->execute($id);
        while(my @row = $exec->fetchrow_array()) {
                my $status = $row[1];
                my $end = DateTime::Format::MySQL->parse_datetime($row[2]);
                if ($now > $end || $status ne 3 || $status ne 6) {
                        $sql = qq { update item set Expired = 1 where EventId = '$eventid';};
                        $maindb->do($sql)
                }else{
                        $sql = qq { update item set Expired = 0 where EventId = '$eventid';};
                        $maindb->do($sql)
                }
        }
        $exec->finish();
}


NoTZ: 2010-09-10T01:27:19 
Now:  2010-09-09T21:27:19
End:  2010-09-10T17:00:00

提前感谢您。我希望我已经很好地解释了这个问题,但很难解释所有事情的关系。


所以澄清一下,你的代码走的是“做某事”的路径,而不是“做其他事情”的路径? - Ether
我实际使用的生产脚本会将一个值设置到数据库中,该值为1或0,具体取决于当前时间是否在结束时间之前或之后。 - Mose
@mose:我正在尝试确认您的代码是否错误地表示2010-09-08T20:03:38 > 2010-09-10T17:00:00? - Ether
抱歉出现了重复发布的情况,按回车键会提交而不是插入新行。 两者都会执行某些操作,并调用单独的SQL语句来更新记录集,其中值为1或0,用于标识当前记录是否过期。 if语句中的代码片段本身与日期无关。 - Mose
6个回答

1
你需要转储对象以确认,不过这似乎是一个时区问题。DateTime::Format::*->parse_datetime(或任何其他构造函数)将使用一个浮动时区(大致相当于UTC),如果您没有指定时区,那么您的比较结果将会相差5个小时。

使用Data::Dumper;打印$dt1和$dt2,以查看对象内容。年份、月份和日期的值是否正确? - Ether
我运行了以下代码:print Dumper($now, $end);这导致$VAR1打印了大量的时区信息,输出大约有113行左右,显然太多信息无法粘贴在注释中。$VAR2则干净得多。 - Mose
现在时间:DateTime::TimeZone::America::New_York 结束时间如何正确设置TZ?:DateTime::TimeZone::Floating - Mose
@mose:请查看DateTime的文档 -- 它是$dt->set_timezone(...); - Ether
终于搞定了,对于Dumper的指令以及如何使用它的说明确实帮了大忙。我成功地将时区更改为$end->set_time_zone('America/New_York')并使其正常运行起来了。 - Mose
显示剩余3条评论

1

试试这个

if (DateTime->compare( $now, $end ) == 1) {  
  # do something
} else {  
  # do something else
}  

实现方式与 $dt1 > $dt2 相同。 - Ether
据我所知,它与$dt1 > $dt2不完全相同:DateTime的比较委托到_approx中的_compare(),在5.10.0(64b)中约为50行代码......您能展示一下如何将其减少到使用'>'运算符的身份识别吗? - Rondo
@Rondo:在 DateTime 对象上进行了重载 >。 - ysth

1
我同意Ether的观点,因为DateTime文档建议立即将所有内容转换为UTC以避免时区问题。
如果这不是时区问题,我会想知道在使用DateTimes的>运算符时是否存在一些歧义。您是否尝试过检查DateTime->compare($now, $end)的结果,如文档中所建议的那样?

我现在正在实施DateTime->compare($now, $end),目前还没有变化,但我也会尝试使用Dumper建议,并看看那里能找到什么。 - Mose
所以我一直在按照之前建议的方式进行Dumper打印。如果我没有显式地将时区设置为美国/纽约,那么系统时间会比实际时间晚5个小时 - 然而即使设置了时区,从MySQL检索到的日期/时间中似乎仍存在“浮动”时区的问题。尝试将时区设置为美国/纽约甚至添加... - Mose
$ENV{TZ} = 'America/New_York'; #tzset; 到目前为止尚未有所帮助。 - Mose

1
你的方法不对。关系型数据库不是一个键值存储,你不需要一次读写一个值。你可以使用原子操作来更快地完成这个任务,而且代码量更少,依赖项也更少(如果'id'列不是唯一的话,还能避免错误)。试试这个方法:
sub set_expired {
  my $id = shift;
  my $dbh = DBI->connect(database);
  my $sql = qq{UPDATE table SET expired=IF(NOW() > date, 1, 0) WHERE id = ?};
  my $sth = $dbh->prepare($sql);
  $sth->execute($id);
  my $rows_affected = $sth->rows();
  # if no matches, $rows_affected will be 0; on error, -1
  $sth->finish();
}

希望您的mysqld将存储与自身运行时相同时区的table.date时间值。看起来您的table.date值处于美国/纽约时区,因此如果幸运的话,您的mysqld已设置其时区,方式相同。在这种情况下,NOW()将是一个可以直接与table.date进行比较的时间戳,就像上面所示。

如果您一直在使用不同时区的值进行赋值,则可能需要调整比较,例如:

  ... SET expired=IF(NOW() > DATE_ADD(date, INTERVAL 4 HOUR), 1, 0) WHERE ...

如果您存储的表格日期值所在的时区变化(或曾经变化)与夏令时有关,那么情况会变得棘手:例如,每年秋季都会出现一个1小时的时间段,同一时间戳会出现两次,而通过查看保存的值无法确定您想要哪个。这就是为什么通常最好将时间戳存储在GMT/UTC中的原因之一。

现在,关于id的唯一性。如果WHERE子句保证只匹配1行,则上述代码和您的代码将执行相同的操作。但是,如果它可以匹配多个,并且这些匹配的行可以具有不同的table.date值,则您的代码可能存在错误。换句话说,它将遍历所有n个匹配的行,并为每个匹配的行设置所有n行的table.expired为1或0,具体取决于它所在的行。结果将是所有n行最终都将具有1或0,具体取决于返回的最后一行,这是未定义的,因此您的结果基本上是随机的。

上述UPDATE语句唯一不能提供的是匹配或更改的id列表,但看起来您也不需要该信息。如果确实需要,有一种聪明的方法可以获取它;回复我,我会分享给您 :)


我将在帖子中更新完整的代码块,我试图缩短代码以便简洁,并关注我发现有问题的代码部分。 - Mose

0

使用 ltgt Perl 运算符:

my $isLessThan = '0001-01-01' lt '2050-01-01'; # Returns 1.
my $isBiggerThan = '0001-01-01' gt '2050-01-01'; # Returns ''.

还可以查看一个循环的示例


-3

这很可能是因为它们返回字符串值。您需要做的是在每个日期上调用epoch()方法,然后进行比较。


DateTime对象重载了比较运算符。 - Ether
@Ether 实际上,从源代码来看,>和<没有被重载。但是Spaceship和cmp被重载了... - Glen Solsberry
这只是语义学问题。只要太空船运算符被重载,> 操作将使用它;不需要单独的重载(同样 gtgeltleneeq 都将使用 cmp)。 - Ether
@Ether 我不知道那个。有趣。 - Glen Solsberry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接