说我想创建一个每日计划,我想把这一天分成15分钟的大块。
轻松吧?从午夜开始,…错误!在美国/圣保罗,由于夏令时变化,每年有一天从01:00开始。
给定时区和日期,如何找到一天开始的时代?
我的第一个想法是使用以下内容,但假设每一天都有23:59。假设每一天都有午夜,这可能不是一个假设。
perl -MDateTime -E' say DateTime->new( year => 2013,month => 10,day => 20 ) ->subtract( days => 1 ) ->set( hour => 23,minute => 59 ) ->set_time_zone("America/Sao_Paulo") ->add( minutes => 1 ) ->strftime("%H:%M"); ' 01:00
有更强大或更直接的替代方案吗?
解决方法
你会认为这是一般需要做的事情!我怀疑那里有很多错误代码
这是一个编码的解决方案,旨在将其纳入DateTime。
use strict; use warnings; use DateTime qw( ); use DateTime::TimeZone qw( ); # Assumption: # There is no dt to which one can add time # to obtain a dt with an earlier date. sub day_start { my $tz = shift; my $dt = shift; my $local_rd_days = ( $dt->local_rd_values() )[0]; my $seconds = $local_rd_days * 24*60*60; my $min_idx; if ( $seconds < $tz->max_span->[DateTime::TimeZone::LOCAL_END] ) { $min_idx = 0; } else { $min_idx = @{ $tz->{spans} }; $tz->_generate_spans_until_match( $dt->utc_year()+1,$seconds,'local' ); } my $max_idx = $#{ $tz->{spans} }; my $utc_rd_days; my $utc_rd_secs; while (1) { my $current_idx = int( ( $min_idx + $max_idx )/2 ); my $current = $tz->{spans}[$current_idx]; if ( $seconds < $current->[DateTime::TimeZone::LOCAL_START] ) { $max_idx = $current_idx - 1; } elsif ( $seconds >= $current->[DateTime::TimeZone::LOCAL_END] ) { $min_idx = $current_idx + 1; } else { my $offset = $current->[DateTime::TimeZone::OFFSET]; # In case of overlaps,always prefer earlier span. if ($current->[DateTime::TimeZone::IS_DST] && $current_idx) { my $prev = $tz->{spans}[$current_idx-1]; $offset = $prev->[DateTime::TimeZone::OFFSET] if $seconds >= $prev->[DateTime::TimeZone::LOCAL_START] && $seconds < $prev->[DateTime::TimeZone::LOCAL_END]; } $utc_rd_days = $local_rd_days; $utc_rd_secs = -$offset; DateTime->_normalize_tai_seconds($utc_rd_days,$utc_rd_secs); last; } if ($min_idx > $max_idx) { $current_idx = $min_idx; $current = $tz->{spans}[$current_idx]; if (int( $current->[DateTime::TimeZone::LOCAL_START] / (24*60*60) ) != $local_rd_days) { my $err = 'Invalid local time for date'; $err .= " in time zone: " . $tz->name; $err .= "\n"; die $err; } $utc_rd_secs = $current->[DateTime::TimeZone::UTC_START] % (24*60*60); $utc_rd_days = int( $current->[DateTime::TimeZone::UTC_START] / (24*60*60) ); last; } } my ($year,$month,$day) = DateTime->_rd2ymd($utc_rd_days); my ($hour,$minute,$second) = DateTime->_seconds_as_components($utc_rd_secs); return $dt ->_new_from_self( year => $year,month => $month,day => $day,hour => $hour,minute => $minute,second => $second,time_zone => 'UTC',) ->set_time_zone($tz); }
测试:
sub new_date { my $y = shift; my $m = shift; my $d = shift; return DateTime->new( year => $y,month => $m,day => $d,@_,hour => 0,minute => 0,second => 0,nanosecond => 0,time_zone => 'floating' ); } { # No midnight. my $tz = DateTime::TimeZone->new( name => 'America/Sao_Paulo' ); my $dt = day_start($tz,new_date(2013,10,20)); print($dt->iso8601(),"\n"); # 2013-10-20T01:00:00 $dt->subtract( seconds => 1 ); print($dt->iso8601(),"\n"); # 2013-10-19T23:59:59 } { # Two midnights. my $tz = DateTime::TimeZone->new( name => 'America/Havana' ); my $dt = day_start($tz,11,3)); print($dt->iso8601(),"\n"); # 2013-11-03T00:00:00 $dt->subtract( seconds => 1 ); print($dt->iso8601(),"\n"); # 2013-11-02T23:59:59 }
一个实际的例子,
sub today_as_floating { return DateTime ->now( @_ ) ->set_time_zone('floating') ->truncate( to => 'day' ); } { my $tz = DateTime::TimeZone->new( name => 'local' ); my $dt = today_as_floating( time_zone => $tz ); $dt = day_start($tz,$dt); print($dt->iso8601(),"\n"); }