如何在 Perl 中将指定年份的日历周转换为 Unix timestamp?
可以用 Perl 的 DateTime 模块,根据 ISO 周所属年份和周数算出该周周一零点,再通过 epoch 获取 Unix timestamp。原命令使用 %V,因此这里按 ISO 8601 周规则计算。
转换时要使用 ISO 周所属年份,并为周一零点指定时区。
先修正年份与周数的组合
原命令中的两个格式符使用了不同的年份口径:
%Y:日期所属的公历年份。%V:ISO 周数,周一为一周的开始。
ISO 第 1 周是包含 1 月 4 日的那一周。跨年附近的日期,其公历年份可能与 ISO 周所属年份不同。例如,2024-12-30 属于 2025-W01。
在支持 %G 的 strftime 实现中,应使用 %G-%V:
perl -E "use POSIX qw(strftime); say strftime('%G-%V', localtime)"
%G 表示 ISO 周所属年份。POSIX::strftime 对格式符的支持取决于运行平台;如果使用 DateTime,可以直接读取 week_year 和 week_number。
如果已有数据只保存了 %Y-%V,跨年附近的记录可能存在歧义,应尽量从原始日期重新计算。
根据 ISO 年份和周数生成 timestamp
如果尚未安装 DateTime,可以通过 CPAN 安装:
cpan DateTime
下面的函数返回指定 ISO 周的周一零点对应的 Unix timestamp。示例使用 UTC;实际使用时,应传入业务采用的时区。
use strict;
use warnings;
use DateTime;
sub iso_week_start_epoch {
my ($iso_year, $week, $time_zone) = @_;
$time_zone //= 'UTC';
die "Invalid ISO week number\n"
unless defined $week
&& $week =~ /\A[0-9]{1,2}\z/
&& $week >= 1
&& $week <= 53;
# 1 月 4 日始终位于 ISO 第 1 周。
my $start = DateTime->new(
year => $iso_year,
month => 1,
day => 4,
time_zone => $time_zone,
);
# 回到第 1 周的周一,再移动到目标周。
$start->subtract(days => $start->day_of_week - 1);
$start->add(weeks => $week - 1);
# 并非每个 ISO 年份都有第 53 周。
die "ISO week does not exist in the requested year\n"
unless $start->week_year == $iso_year
&& $start->week_number == $week;
return $start->epoch;
}
# 2025-W01 从 2024-12-30 开始。
print iso_week_start_epoch(2025, 1, 'UTC'), "\n";
函数先找到 ISO 第 1 周的周一,再增加 周数 − 1 周。最后检查计算结果,避免把不存在的第 53 周静默转换成下一年的第 1 周。
原始输入是日期时,可以直接计算所在周的起点
对于按 YYYY-MM-DD 分组的场景,可以直接从日期回退到本周周一,无须先格式化成周编号,再反向解析:
use strict;
use warnings;
use DateTime;
my $input = '2025-01-01';
my $time_zone = 'UTC';
my ($year, $month, $day) =
$input =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})\z/;
die "Expected YYYY-MM-DD\n" unless defined $day;
my $date = DateTime->new(
year => $year,
month => $month,
day => $day,
time_zone => $time_zone,
);
my $week_key = sprintf(
'%04d-%02d',
$date->week_year,
$date->week_number,
);
my $week_start = $date->clone;
$week_start->subtract(days => $week_start->day_of_week - 1);
print "$week_key\n";
print $week_start->epoch, "\n";
正则表达式检查日期格式,DateTime->new 检查具体日期是否合法。这里创建的日期默认时间为零点,因此回退后得到的是周一零点。
时区和夏令时需要统一处理
YYYY-MM-DD 只有日期信息,不能单独确定唯一的 Unix timestamp,必须先约定它表示哪个时区的日期。例如,业务按中国标准时间分周,可以将示例中的时区改为 Asia/Shanghai。
原命令使用 localtime,采用进程所在环境的本地时区。如果后续转换使用 UTC,接近午夜或周边界的记录可能被分到不同的周。日期解析、周分组和周起点计算应使用同一时区。
对于采用夏令时的地区,应按日历日期移动到周一,避免直接减去固定的 86400 秒来表示一天。在存在午夜跳时的时区,还需要明确遇到不存在或重复的本地零点时如何处理。
备注:内容仅供参考。
