【问题标题】:Perl Compare dates with MySQLPerl 与 MySQL 比较日期
【发布时间】:2011-04-10 00:51:33
【问题描述】:

我正在努力弄清楚如何使用 perl 将 MySQL 日期与当前系统时间进行比较。

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

应用程序显示表格视图:

EventId    Device        Location

CC: 123    something     BFE
TT: 456    anotherthing  BFE

脚本的工作原理是在 EventID 字段中查找值,从 ID(数字部分)解析类型(CC:、TT: 等)。 ID 是另一个包含结束时间字段的数据库/表中的唯一 ID。 EventID 本身不是唯一的,并且可能在表中具有重复项。每个“类型”都有子例程,因为每种类型都有不同的数据库和/或表。

目标是脚本每分钟运行一次并切换 Expired 的值。某些内容可能会过期、进行更改然后过期。

脚本工作正常,除了 1 个问题,根据我到目前为止收到的反馈,这似乎与时区有关。如果我没有将当前系统时间的时区隐式设置为 '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

提前致谢。我希望我已经解释得足够好,很难解释一切之间的关系。

【问题讨论】:

  • 所以澄清一下,您的代码将进入“做某事”路径,而不是“做其他事情?
  • 无论当前时间是在结束时间之前还是之后,我所拥有的实际生产脚本都会将值设置为 1 或 0 的数据库。
  • @mose:我正在尝试确认您的代码错误地表示 2010-09-08T20:03:38 > 2010-09-10T17:00:00?
  • 抱歉重复发布,点击回车提交而不是插入换行符。两者都做某事,并做其他事情调用单独的 SQL 语句,该语句将更新记录集,其值为 1 或 0,用于识别当前记录是否过期。 if 语句中的代码片段本身与日期无关。

标签: mysql perl datetime


【解决方案1】:

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

【讨论】:

  • DateTime 对象重载了比较运算符。
  • @Ether 实际上,从源头上看,> 和 cmp 是……
  • 这只是语义。只要 spaceship 运算符重载,> 操作就会使用它;不需要单独的重载(同样gtgeltleneeq 都将使用cmp)。
  • @Ether 我不知道这一点。有趣。
【解决方案2】:

您必须转储对象才能确认,但听起来您遇到了时区问题。 DateTime::Format::*->parse_datetime(或任何其他构造函数)将使用浮动时区(大致近似于 UTC),如果您未指定,则您的比较将延迟 5 小时。

【讨论】:

  • 您能否详细说明“转储对象以确认”?我对 Perl 比较陌生。
  • 我的 $end = DateTime::Format::MySQL->parse_datetime($row[2],time_zone=>"America/New_York");
  • @mose: use Data::Dumper; print Dumper($dt1, $dt2); 查看对象内容。年、月、日的值看起来正确吗?
  • 我运行了以下命令: print Dumper($now, $end);这导致 $VAR1 打印了大量的时区信息,其输出大约为 113 行,显然太多信息无法粘贴到评论中。 $VAR2 干净得多。
  • $now: DateTime::TimeZone::America::New_York' $end: DateTime::TimeZone::Floating 问题是,如何正确设置 $end 的 TZ?
【解决方案3】:

试试这个

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

【讨论】:

  • 其实现与$dt1 > $dt2相同。
  • AFAICS 它与 $dt1 不同 > $dt2:DateTime 的比较代表到 _compare() 大约是。 5.10.0 (64b) 中的 50 行代码......你能用 '>' 操作说明如何将其简化为一个身份吗?
  • @Rondo: > 在 DateTime 对象上重载
【解决方案4】:

我在这里使用 Ether,因为 DateTime 文档建议立即将所有内容转换为 UTC 以避免时区问题。

如果不是时区问题,我想知道将 > 运算符与 DateTimes 一起使用是否存在一些歧义。您是否尝试过按照in the documentation 的建议检查DateTime->compare($now, $end) 的结果?

【讨论】:

  • 我现在正在实施 DateTime->compare($now, $end),到目前为止没有任何变化,但我也会尝试 Dumper 的建议,看看那里能找到什么。
  • 所以我一直按照之前的建议进行 Dumper 打印。如果我没有将时区隐式设置为 America/New_York,则系统时间的时间会缩短 5 小时 - 但是即使设置它,从检索到的日期/时间中的“浮动”时区似乎也存在问题mysql。尝试将时区设置为 America/New_York 甚至添加
  • $ENV{TZ} = 'America/New_York'; #tzset;到目前为止没有帮助。
【解决方案5】:

你做错了。关系数据库不是您必须一次读取和写入一个值的键值存储。您可以用更少的代码和更少的依赖项以原子方式更快地执行此操作(并且,如果“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 时间值存储在 mysqld 本身运行的同一时区中。看起来您的 table.date 值在美国/纽约时区,所以运气好,您的 mysqld 以相同的方式运行 its timezone。在这种情况下,NOW() 将是一个时间戳,可以直接与 table.date 进行比较,如上所述。

如果您一直在为其提供不同时区的值,您可能需要调整比较,例如:

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

如果您存储 table.date 值的时区随夏令时而变化(或已经变化),事情就会变得棘手:例如,每年秋天有一个 1 小时的时间段,其中相同的时间戳出现两次并且没有办法通过查看您所指的已保存值来判断。这就是为什么如果可以的话,通常最好将时间戳存储在 GMT/UTC 中的部分原因。

现在,关于 id 的唯一性。如果 WHERE 子句保证只匹配 1 行,那么上面的代码和你的代码会做同样的事情。但是如果它可以匹配超过 1 个,并且如果这些匹配的行可以有不同的 table.date 值,那么你的代码可能有一个错误。也就是说,它将遍历所有 n 个匹配的行,并且对于每一行,它会将所有 n 行设置为 table.expired 为 1 或 0,具体取决于它所在的行。结果将是所有 n 行最终都会以 1 或 0 结束,具体取决于返回的最后一行(未定义),因此您的结果基本上是随机的。

您没有从上述更新中获得的一件事是匹配或更改的 id 列表,但看起来您并不需要这些信息。如果你确实需要它,有一个聪明的方法可以得到它;回复,我会分享:)

【讨论】:

  • 我将使用完整的代码块更新帖子,为了简洁起见,我试图最小化代码并专注于我发现有问题的代码部分。
【解决方案6】:

使用ltgt perl 运算符:

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

另见 example 循环。

【讨论】:

    猜你喜欢
    • 2019-09-18
    • 1970-01-01
    • 2013-11-06
    • 2012-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-28
    • 1970-01-01
    相关资源
    最近更新 更多