Re: How to Compute Hours In a Day?

2008-10-20 Thread Zefram
[EMAIL PROTECTED] wrote:
>    C:\>perl oddhours.pl 2007
>    2007-12-09  24.5  America/Caracas
>    2007-03-11  22    America/Indiana/Winamac
>    2007-03-25  24.5  Australia/Lord_Howe
>    2007-10-28  23.5  Australia/Lord_Howe

Interesting, Australia/Lord_Howe does a half-hour DST shift every year.

>    1961-01-01  23.74583  Africa/Dar_es_Salaam

That's the switch from an unaligned +02:44:45 to the regular +03:00.
Curiously, it went to +02:44:45 *from* +03:00 earlier, in 1948, and
before that (until 1931) it had used +02:37:08.  Strong odour of politics.

>    C:\>perl oddhours.pl 1967
>    Invalid local time for date in time zone: Africa/Casablanca

Wow, someone *does* switch at noon.  Or did, once.  A quick look at the
tzdata files shows a handful of other noon switches, all one-offs.

I think you're going to have to give up the idea of using a fixed local
time each day as your reference point.  You need a function that finds
the earliest point that lies within a given calendar date in local time,
which will usually (but not always) be labelled 00:00.  It's probably
safer to work in UTC and convert to local, rather than work with local
time-of-day directly, to avoid trouble with ambiguous times of day
(there might be two instances of local 00:00 in one day).

This is still assuming that the extent of each local calendar day
is contiguous.  If the clocks go back an hour at 00:30 local time,
it would break your model.  The Alaskan zones stretch the model to
the limit, having in 1867 a jump back by 24 hours at 24:00 local time,
yielding a 48-hour day.  (Subjectively it was treated as two consecutive
Fridays, which would have had the same calendar date were it not for the
simultaneous switch from the Julian calendar to the Gregorian calendar.)
There are also some Pacific islands that have jumped across the IDL in
the other direction, skipping a calendar date altogether.

Anyway, have a go with this function:

sub day_start() {
my($zone, $y, $m, $d) = @_;
my $p = DateTime->new(time_zone=>"UTC", year=>$y, month=>$m, day=>$d, 
hour=>12);
(my $rd) = $p->utc_rd_values;
while(do { (my $l = $p->clone)->set_time_zone($zone); 
[$l->local_rd_values]->[0] } >= $rd) {
$p->subtract(hours=>1);
}
while(do { (my $l = $p->clone)->set_time_zone($zone); 
[$l->local_rd_values]->[0] } < $rd) {
$p->add(hours=>1);
}
my $q;
while(1) {
($q = $p->clone)->subtract(minutes=>1);
last if do { (my $l = $q->clone)->set_time_zone($zone); 
[$l->local_rd_values]->[0] } < $rd;
$p = $q;
}
while(1) {
($q = $p->clone)->subtract(seconds=>1);
last if do { (my $l = $q->clone)->set_time_zone($zone); 
[$l->local_rd_values]->[0] } < $rd;
$p = $q;
}
return $p;
}

-zefram


Re: How to Compute Hours In a Day?

2008-10-20 Thread Eugene van der Pijll
[EMAIL PROTECTED] schreef:
> Here's my first shot at a script. It combines Zefram's suggestion to
> use $dt->epoch() and Eugene's recommendation to measure the intervals
> at noon instead of at midnight. Next, I'd like to refactor it
> using...um...I'm not sure, but something other than $dt->epoch().

I think the script in my first mail comes close; I'd start there.

> It's easy enough to find stranger and even more problematic dates.
> 
>     C:\>perl oddhours.pl 1961
>     1961-01-01  23.74583  Africa/Dar_es_Salaam

In the 19th century, there were no time standards, and each city used
its own local time. At some point, times within a country were
standardized; often to an integer offset with GMT, sometimes first to
the local time of the capital.

Check out 1883, when the U.S. moved from local time to standard time
zones. That results in a lot of strange day lengths. Apparently,
Tanganyika moved to standard time in 1961.

>     C:\>perl oddhours.pl 1967
>     Invalid local time for date in time zone: Africa/Casablanca

And so there are time zones that change at noon... Those wacky
politicians...

In Morocco in 1967, and only in that year, DST started on June 3, 12:00.

Eugene


Re: How to Compute Hours In a Day?

2008-10-20 Thread jim . monty
Eugene van der pijll wrote:
>Jim Monty schreef:
>> I want to iterate the time zones of the world to generate a report of
>> all days that aren't exactly 24 hours. I want to handle
>> America/Caracas and America/Sao_Paulo correctly. You mentioned
>> 2007-12-09 was 24.5 hours long in Venezuela. This is precisely the
>> kind of outlier I'm after.
>
> If you want to handle the Sao Paulo situation correctly, measure the
> intervals between two middays (12:00), instead of between midnights (0:00).
>
> It should show the same non-24 hour days, except when there are two DST
> changes in one day. Which is unlikely, but not impossible, given that
> DST is defined by politicians.

It seems there's no one-size-fits-all workaround for all time zones in all 
years.

Here's my first shot at a script. It combines Zefram's suggestion to use 
$dt->epoch() and Eugene's recommendation to measure the intervals at noon 
instead of at midnight. Next, I'd like to refactor it using...um...I'm not 
sure, but something other than $dt->epoch().

    #!perl
    
    use strict;
    use warnings;
    
    use DateTime;
    
    @ARGV or die "Usage: perl $0  ...\n";
    
    for my $year (@ARGV) {
    for my $time_zone (DateTime::TimeZone->all_names()) {
    my $today = DateTime->from_day_of_year(
    year    => $year,
    day_of_year => 1,
    hour    => 12,
    time_zone   => $time_zone,
    );
    
    (my $yesterday = $today->clone())->subtract( days => 1 );
    my $hours = ($today->epoch() - $yesterday->epoch()) / 3_600;
    my $year_today = $today->year();
    
    while ($year_today == $year) {
    (my $tomorrow = $today->clone())->add( days => 1 );
    
    my $date = $today->date();
    
    if ($hours != 23 && $hours != 24 && $hours != 25) {
    printf("%-10s  %-4s  %s\n", $date, $hours, $time_zone);
    }
    
    $hours = ($tomorrow->epoch() - $today->epoch()) / 3_600;
    $today = $tomorrow;
    $year_today = $today->year();
    }
    }
    }
    
    exit 0;
    
    __END__


    C:\>perl oddhours.pl 2007
    2007-12-09  24.5  America/Caracas
    2007-03-11  22    America/Indiana/Winamac
    2007-03-25  24.5  Australia/Lord_Howe
    2007-10-28  23.5  Australia/Lord_Howe
    
    C:\>

It's easy enough to find stranger and even more problematic dates.

    C:\>perl oddhours.pl 1961
    1961-01-01  23.74583  Africa/Dar_es_Salaam
    1961-08-10  23.5  Asia/Seoul
    
    C:\>perl oddhours.pl 1967
    Invalid local time for date in time zone: Africa/Casablanca
    
    C:\>

Since this is my first effort to do something quasi-useful with DateTime using 
DateTime math, all feedback, criticisms and suggestions are most welcome.

Jim Monty



Re: How to Compute Hours In a Day?

2008-10-19 Thread Dave Rolsky

On Mon, 20 Oct 2008, Eugene van der Pijll wrote:


DateTime cannot handle fractional leap hours.

See the source code of subtract_datetime(), section "This is a gross
hack", where the length of the leap hour is hardcoded in the lines

   $bigger_min -= 60
   $bigger_min += 60

This should probably be changed, but datetime math is scary.


If you write some failing tests, I might be able to fix it.


-dave

/*
http://VegGuide.org   http://blog.urth.org
Your guide to all that's veg  House Absolute(ly Pointless)
*/


Re: How to Compute Hours In a Day?

2008-10-19 Thread Eugene van der Pijll
[EMAIL PROTECTED] schreef:
> I want to iterate the time zones of the world to generate a report of
> all days that aren't exactly 24 hours. I want to handle
> America/Caracas and America/Sao_Paulo correctly. You mentioned
> 2007-12-09 was 24.5 hours long in Venezuela. This is precisely the
> kind of outliers I'm after.

If you want to handle the Sao Paulo situation correctly, measure the
intervals between two middays (12:00), instead of between midnights (0:00).

It should show the same non-24 hour days, except when there are two DST
changes in one day. Which is unlikely, but not impossible, given that
DST is defined by politicians.

Eugene


Re: How to Compute Hours In a Day?

2008-10-19 Thread jim . monty
Zefram wrote:
>[EMAIL PROTECTED] wrote:
> >How do I compute the number of hours in a day in a specific time zone?
> 
> Try this:

Thanks for the working example. It's helpful.

> It ought to be possible to do the interval calculation using
> DateTime::Duration instead of ->epoch, but the behaviour of DT::D is
> unreasonably confusing and I couldn't get it to work.  (Strangely, I got
> a version that worked fine for America/New_York, where all the days are
> integer numbers of hours, but lost the fractional hour of 2007-12-09 in
> America/Caracas, which was 24.5 hours long.)
>
> Beware that the above code assumes that
> midnight exists every day.  We recently had a thread
>  about
> midnight not existing in some timezones, specifically America/Sao_Paulo.

It's funny you should mention that thread. It's what inspired my little 
exercise, which I thought was going to be easier than it is. Here's what I 
*really* want to do:

I want to iterate the time zones of the world to generate a report of all days 
that aren't exactly 24 hours. I want to handle America/Caracas and 
America/Sao_Paulo correctly. You mentioned 2007-12-09 was 24.5 hours long in 
Venezuela. This is precisely the kind of outliers I'm after.

So I'm looking for the best, most canonical, most 
true-to-the-spirit-of-DateTime way to accomplish this using, presumably, 
DateTime::Duration and possibly DateTime::Format::Duration.

Jim Monty



Re: How to Compute Hours In a Day?

2008-10-19 Thread Eugene van der Pijll
Zefram schreef:
> It ought to be possible to do the interval calculation using
> DateTime::Duration instead of ->epoch, but the behaviour of DT::D is
> unreasonably confusing and I couldn't get it to work.  (Strangely, I got
> a version that worked fine for America/New_York, where all the days are
> integer numbers of hours, but lost the fractional hour of 2007-12-09 in
> America/Caracas, which was 24.5 hours long.)

DateTime cannot handle fractional leap hours.

See the source code of subtract_datetime(), section "This is a gross
hack", where the length of the leap hour is hardcoded in the lines

$bigger_min -= 60
$bigger_min += 60

This should probably be changed, but datetime math is scary.

As a workaround, convert the time to UTC. (This does not work in all
cases, but for this purpose, it does.

use DateTime;

for my $doy (340..345) {

# The default time is 00:00:00
my $day = DateTime->from_day_of_year(
year => 2007, day_of_year => $doy,
time_zone => 'America/Caracas') or die;

# To find the end of the day, add 1 day to get 00:00:00 tomorrow
my $end_of_day = $day->clone->add( days => 1 );

# Workaround for Venezuela.
$day->set_time_zone('UTC');
$end_of_day->set_time_zone('UTC');

# Standard subtraction would result in a duration of 1 day, so we
# use delta_ms() which results in a duration without the "days"
# component. It is expressed entirely in minutes, which can be
# converted to hours.
my $hours = $end_of_day->delta_ms($day)->delta_minutes / 60;

print $day, " has $hours hours.\n";
}


Eugene


Re: How to Compute Hours In a Day?

2008-10-19 Thread Zefram
[EMAIL PROTECTED] wrote:
>How do I compute the number of hours in a day in a specific time zone?

Try this:

use DateTime;
for(my $today = DateTime->new(year=>2007, time_zone=>$ARGV[0]); $today->year == 
2007; ) {
(my $tomorrow = $today->clone)->add(days=>1);
print $today, "  ", ($tomorrow->epoch - $today->epoch)/3600, "\n";
$today = $tomorrow;
}

It ought to be possible to do the interval calculation using
DateTime::Duration instead of ->epoch, but the behaviour of DT::D is
unreasonably confusing and I couldn't get it to work.  (Strangely, I got
a version that worked fine for America/New_York, where all the days are
integer numbers of hours, but lost the fractional hour of 2007-12-09 in
America/Caracas, which was 24.5 hours long.)

Beware that the above code assumes that
midnight exists every day.  We recently had a thread
 about
midnight not existing in some timezones, specifically America/Sao_Paulo.

-zefram


How to Compute Hours In a Day?

2008-10-19 Thread jim . monty
How do I compute the number of hours in a day in a specific time zone?
 
Let's say I want to iterate the days of the year from Jan 1 thru Dec 31
and print the number of hours in each day in the America/New_York time
zone. I expect 363 days to have 24 hours, one day to have 23 hours, and
one day to have 25 hours. I want to know how best to compute these
numbers of hours in each day.
 
Jim Monty