在不同的时区安排脚本

时间:2009-11-24 12:45:25

标签: perl timezone scheduled-tasks

某些程序/脚本需要在与系统时区不同的时区中的特定时间运行。

Perl中的la crontab,但是在与配置系统的区域不同的区域中遵守时区和DST规则。

以下是用例:我将创建一个excel表,其中PT中的时间为B列,相应的程序/ Perl脚本则在C列中运行。

Excel信息表中没有任何关于此信息的具体内容 - 也可以是纯文本文件/“crontab”条目。

Perl脚本将读取excel表中的数据并在正确的时间运行/生成这些脚本。

要记住的是,无论运行它的系统是什么时区,Perl脚本都应该正确运行。

无论脚本是在纽约还是IL或CA的Box上运行,它都应该在文件条目中提到的时间按照太平洋标准时间和DST生成脚本。

正如我之前所说,非常重要的是,它知道“自动”(没有我做任何明确的编程)最新的PT区域DST规则。

你会建议什么?

也许我可以访问某个显示该区域当前时间并从中扫描时间值的网站,并在正确的时间运行脚本?

任何这样的Perl屏幕刮刀友好网站?

或许我可以使用一些智能Perl模块,例如Schedule::Cron

为了记录,在http://www.perlmonks.org/index.pl?node_id=772934然而提出了大量好的建议,它们以典型的/ cron方式按照系统配置的时区工作。

4 个答案:

答案 0 :(得分:2)

在计划中,存储每次运行时的纪元数,而不是日期/时间字符串。

扩大一点:

#!/usr/bin/perl

use strict; use warnings;

use DateTime;

my $dt = DateTime->new(
    year   => 2010,
    month  => 3,
    day    => 14,
    hour   => 2,
    minute => 0,
    second => 0,
    time_zone => 'America/Chicago',
);

print $dt->epoch, "\n";

给了我

Invalid local time for date in time zone: America/Chicago

因为2010年3月14日凌晨2点是切换发生的时间。另一方面,使用hour => 3,我得到:1268553600。现在,在纽约,我使用:

C:\Temp> perl -e "print scalar localtime 1268553600"
Sun Mar 14 04:00:00 2010

因此,解决方案似乎是避免在 本地 时区中的不存在时间内安排这些事件。这不需要复杂的逻辑:只需将DateTime构造函数调用包装在eval中并处理异常时间。

答案 1 :(得分:2)

一般情况下,如果您关心时区,请在内部以某种通用格式表示时间,并将转换时间仅用于显示目的。

将此问题应用于您的问题,请编写一个crontab,其时间以GMT表示。在每台工作计算机上,转换为本地时间并安装crontab。

前面的事情:

#! /usr/bin/perl

use warnings;
use strict;

use feature qw/ switch /;

use Time::Local qw/ timegm /;

对于此程序支持的转换,请使用今天的日期并替换当前cronjob中的时间。返回调整后的小时和星期几的偏移量:

sub gmtoday {
  my($gmmin,$gmhr,$gmmday,$gmmon,$gmwday) = @_;

  my @gmtime = gmtime $^T;
  my(undef,undef,$hour,$mday,$mon,$year,$wday) = @gmtime;

  my @args = (
    0,  # sec
    $gmmin eq "*" ? "0" : $gmmin,
    $gmhr,
    $mday,                         
    $mon,
    $year,
  );

  my($lhour,$lwday) = (localtime timegm @args)[2,6];

  ($lhour, $lwday - $wday);
}

从当前的cronjob中取出五场时间规格,并将其从GMT转换为当地时间。请注意,完全通用的实现将支持32(,2 ** 5)个案。

sub localcron {
  my($gmmin,$gmhr,$gmmday,$gmmon,$gmwday) = @_;

  given ("$gmmin,$gmhr,$gmmday,$gmmon,$gmwday") {
    # trivial case: no adjustment necessary
    when (/^\d+,\*,\*,\*,\*$/) {
      return ($gmmin,$gmhr,$gmmday,$gmmon,$gmwday);
    }

    # hour and maybe minute
    when (/^(\d+|\*),\d+,\*,\*,\*$/) {
      my($lhour) = gmtoday @_;
      return ($gmmin,$lhour,$gmmday,$gmmon,$gmwday);
    }

    # day of week, hour, and maybe minute
    when (/^(\d+|\*),\d+,\*,\*,\d+$/) {
      my($lhour,$wdoff) = gmtoday @_;
      return ($gmmin,$lhour,$gmmday,$gmmon,$gmwday+$wdoff);
    }

    default {
      warn "$0: unhandled case: $gmmin $gmhr $gmmday $gmmon $gmwday";
      return;
    }
  }
}

最后,主循环从输入读取每一行并生成适当的输出。请注意,我们不会破坏未处理的时间:它们会在输出中显示为注释。

while (<>) {
  if (/^\s*(?:#.*)?$/) {
    print;
    next;
  }

  chomp;
  my @gmcron = split " ", $_, 6;

  my $cmd = pop @gmcron;
  my @localcron = localcron @gmcron;

  if (@localcron) {
    print join(" " => @localcron), "\t", $cmd, "\n"
  }
  else {
    print "# ", $_, "\n";
  }
}

对于这种sorta-crontab

33  * * * * minute only
 0  0 * * * minute and hour
 0 10 * * 1 minute, hour, and wday (same day)
 0  2 * * 1 minute, hour, and wday (cross day)

在美国中部时区运行时输出如下:

33 * * * *  minute only
0 18 * * *  minute and hour
0 4 * * 1   minute, hour, and wday (same day)
0 20 * * 0  minute, hour, and wday (cross day)

答案 2 :(得分:1)

虽然我当然认为可能存在“更清洁”的解决方案,但以下工作会如何?

  • 将cron设置为在您希望脚本运行的可能范围之前几小时运行脚本

  • 处理脚本中的时区检测并让它在适当的时间内休眠

同样,我知道这有点像kludgey但我想我会把它放在那里。

答案 3 :(得分:0)

使用DateTime模块计算时间。

因此,如果您的设置说每天凌晨2:30运行脚本,则需要逻辑:

  • 尝试在时区America \ Los_Angeles上午2:30创建一个DateTime对象。

  • 如果没有任何对象添加5分钟,请重试。放弃2小时后放弃。

  • 获得DateTime个对象后,您可以与DateTime->now进行比较,或从对象中提取一个纪元时间,并将其与time的结果进行比较。

请注意,我选择了凌晨2:30,因为那个时间每年至少不会存在1天。这就是为什么你需要一个增加偏移量的循环。