2009-11-24 90 views
2

有些程序/脚本需要在系统时区的不同时区不同的的特定时间运行。在不同时区安排脚本

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

这里是用例:我将创建Excel工作表,在列B中的PT时间和相应的程序/ Perl脚本列C中运行特定关于这个信息在Excel中是

没什么表单 - 也可以是纯文本文件/“crontab”条目。

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

需要注意的是,Perl脚本应该正确运行,而不管运行系统的时区是什么。

无论脚本是在NY或IL或CA的Box上运行,它应该在文件条目中提到的时间根据太平洋标准时间产生脚本,并且在DST的基础上。

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

你会建议什么?

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

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

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

为了记录在案,大量的好的建议在http://www.perlmonks.org/index.pl?node_id=772934走过来,但是,他们,在典型的在/ cron的方式,工作的根据系统配置的时区。

回答

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以五场时间规范,并从格林尼治标准时间转换为本地时间。请注意,一个完全通用的实现将支持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"; 
    } 
} 

对于这个八九不离十,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) 
+0

出色的工作! – PoorLuzer 2009-12-23 11:42:25

1

虽然我当然认为有可能“更清洁”的解决方案,下面的工作会是什么?

  • 设置cron来运行脚本几个小时提前可能的范围内,你真正想要倍的脚本运行

  • 处理脚本的时区检测,并把它睡适量的时间

再次,我知道这有点kludgey,但我想我会把它放在那里。

+0

我喜欢这样。对我来说,这似乎没有那么糟糕。人们甚至可以每小时运行一次cron脚本,然后让他们决定是否正确的时间(如果按小时计算的话)。 – innaM 2009-11-24 13:20:27

+0

我正在考虑在PT中创建一个具有时间细节的DateTime对象,然后使用其中一个Perl调度程序模块来执行任务。不过,我想知道如何确保在DST规则更改/禁用时这不会导致问题。这是我需要帮助的决定。 – PoorLuzer 2009-11-24 13:42:27

+0

为什么需要在PT?你可以只用UTC吗? – malonso 2009-12-02 01:42:37

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 

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

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

因此,解决方案似乎是避免在本地时区调度过程中不存在的时候,这些事件。这并不需要复杂的逻辑:只要包裹DateTime构造函数调用在eval和处理特殊的时间。

+1

@Sinan:除了来自时代的秒数每年会改变两次,因为他的剧本必须在夏令时相同的太平洋时间运行。在秋季,他将需要增加一小时,并在春季再次减去它。 – 2009-11-24 15:18:15

+0

卓越的理解亚当!这是我担心的问题。编写代码不是问题 - 问题是它将成为一个自定义代码片段,而不是作为CPAN模块的一部分。我想知道的是 - 是否已经没有模块/标准化的方式来完成这项工作,而无需编写自定义代码(我需要编写文档,测试用例和测试数据以确保我的自定义代码是正确的,因为这肯定会投入生产代码)? – PoorLuzer 2009-11-25 09:24:13

0

使用datetime模块来计算时间。

所以,如果您的设置表示在凌晨2:30,每天运行脚本,你将需要逻辑:

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

  • 如果没有对象添加5分钟的时间,然后再试一次。 2小时抵消后放弃。

  • 一旦你有一个DateTime对象,你可以做的比较与DateTime->now或从对象中提取一个划时代的时间和比较,与time结果。

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