Skip to content

Commit

Permalink
Use a location's time zone to get the local time
Browse files Browse the repository at this point in the history
Use Geo::Location::TimeZoneFinder to map coordinates to time zone names.

Simplify the TimeStep class by splitting longer periods into single
hours.
  • Loading branch information
voegelas committed Jan 11, 2023
1 parent 31092b7 commit 8a22790
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 186 deletions.
10 changes: 8 additions & 2 deletions META.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"Andreas V\u00f6gele <[email protected]>"
],
"dynamic_config" : 0,
"generated_by" : "Dist::Zilla version 6.025, CPAN::Meta::Converter version 2.150010",
"generated_by" : "Dist::Zilla version 6.029, CPAN::Meta::Converter version 2.150010",
"license" : [
"agpl_3"
],
Expand Down Expand Up @@ -35,7 +35,9 @@
"requires" : {
"Exporter" : "0",
"File::Spec" : "0",
"Geo::Location::TimeZoneFinder" : "0",
"Mojo::Base" : "0",
"Mojo::Cache" : "0",
"Mojo::Collection" : "0",
"Mojo::Date" : "0",
"Mojo::File" : "0",
Expand Down Expand Up @@ -91,6 +93,10 @@
"file" : "lib/MyApp/Weather/Model/LocationForecast/TimeStep.pm",
"version" : "0.005"
},
"MyApp::Weather::Model::TimeZoneFinder" : {
"file" : "lib/MyApp/Weather/Model/TimeZoneFinder.pm",
"version" : "0.005"
},
"MyApp::Weather::Model::UserAgent" : {
"file" : "lib/MyApp/Weather/Model/UserAgent.pm",
"version" : "0.005"
Expand Down Expand Up @@ -121,7 +127,7 @@
}
},
"version" : "0.005",
"x_generated_by_perl" : "v5.34.1",
"x_generated_by_perl" : "v5.36.0",
"x_serialization_backend" : "Cpanel::JSON::XS version 4.32",
"x_spdx_expression" : "AGPL-3.0"
}
Expand Down
6 changes: 5 additions & 1 deletion Makefile.PL
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.025.
# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.029.
use strict;
use warnings;

Expand All @@ -22,7 +22,9 @@ my %WriteMakefileArgs = (
"PREREQ_PM" => {
"Exporter" => 0,
"File::Spec" => 0,
"Geo::Location::TimeZoneFinder" => 0,
"Mojo::Base" => 0,
"Mojo::Cache" => 0,
"Mojo::Collection" => 0,
"Mojo::Date" => 0,
"Mojo::File" => 0,
Expand Down Expand Up @@ -54,7 +56,9 @@ my %WriteMakefileArgs = (
my %FallbackPrereqs = (
"Exporter" => 0,
"File::Spec" => 0,
"Geo::Location::TimeZoneFinder" => 0,
"Mojo::Base" => 0,
"Mojo::Cache" => 0,
"Mojo::Collection" => 0,
"Mojo::Date" => 0,
"Mojo::File" => 0,
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ well as a longer description of the weather conditions.

## DEPENDENCIES

Requires Perl 5.20, Mojolicious 9 and Role::Tiny.
Requires Perl 5.20, Mojolicious 9, Role::Tiny and
Geo::Location::TimeZoneFinder.

## QUERY PARAMETERS

Expand Down Expand Up @@ -69,6 +70,12 @@ The base path in the frontend proxy, e.g. /weather. Empty by default.

The default temperature unit.

#### TIME_ZONE_DATABASE

The file base of a time zone database from
https://github.com/evansiroky/timezone-boundary-builder, e.g.
/usr/local/share/timezones/combined-shapefile.

#### WEATHER_USER_AGENT

An identifying user agent string. See https://api.met.no/doc/TermsOfService
Expand Down
4 changes: 3 additions & 1 deletion cpanfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# This file is generated by Dist::Zilla::Plugin::CPANFile v6.025
# This file is generated by Dist::Zilla::Plugin::CPANFile v6.029
# Do not edit this file directly. To change prereqs, edit the `dist.ini` file.

requires "Exporter" => "0";
requires "File::Spec" => "0";
requires "Geo::Location::TimeZoneFinder" => "0";
requires "Mojo::Base" => "0";
requires "Mojo::Cache" => "0";
requires "Mojo::Collection" => "0";
requires "Mojo::Date" => "0";
requires "Mojo::File" => "0";
Expand Down
4 changes: 4 additions & 0 deletions dist.ini
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ dir = script

[Test::Kwalitee]

[Encoding]
encoding = bytes
match = ^t/data/

[FakeRelease]

[AutoPrereqs]
Expand Down
6 changes: 6 additions & 0 deletions lib/MyApp/Weather.pm
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ The base path in the frontend proxy, e.g. /weather. Empty by default.
The default temperature unit. Valid values are "C" for Celsius and "F" for
Fahrenheit.
=head3 TIME_ZONE_DATABASE
The file base of a time zone database from
L<https://github.com/evansiroky/timezone-boundary-builder>, e.g.
F</usr/local/share/timezones/combined-shapefile>.
=head3 WEATHER_USER_AGENT
An identifying user agent string. See
Expand Down
80 changes: 63 additions & 17 deletions lib/MyApp/Weather/Model/LocationForecast.pm
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,69 @@ our $VERSION = '0.005';
use Mojo::Collection;
use MyApp::Weather::Model::LocationForecast::Day;
use MyApp::Weather::Model::LocationForecast::TimeStep;
use MyApp::Weather::Model::TimeZoneFinder;
use Time::Piece;
use Time::Seconds;

has 'hash';
has finder =>
sub { state $finder = MyApp::Weather::Model::TimeZoneFinder->new };

sub _get_hours ($self, $forecast_object) {
my @hours;

my $time = $forecast_object->{time} // '1970-01-01T00:00:00Z';
my $data = $forecast_object->{data} // {};

my $utc = Time::Piece->strptime($time, '%FT%TZ%z');
my $epoch = $utc->epoch;
my $from = localtime $epoch;

# Look for "next_1_hours", "next_6_hours" and "next_12_hours".
my $duration = ~0;
my $next_hours_key;
for my $key (keys %{$data}) {
if ($key =~ m{\A next_(\d+)_hours \z}xms) {
if ($duration > $1) {
$duration = $1;
$next_hours_key = $key;
}
}
}

if (defined $next_hours_key) {
my $instant = $data->{instant};
my $period = $data->{$next_hours_key};
# Split long periods into single hours.
while ($duration > 0) {
my $to = $from + ONE_HOUR;
my $timestep = MyApp::Weather::Model::LocationForecast::TimeStep->new(
from => $from,
to => $to,
instant => $instant,
period => $period,
);
push @hours, $timestep;
$from = $to;
--$duration;
}
}
return @hours;
}

sub timeseries ($self) {
my $hash = $self->hash // {};
my $properties = $hash->{properties} // {};
my $timeseries = $properties->{timeseries} // [];
my $hash = $self->hash // {};
my $geometry = $hash->{geometry} // {};
my $coordinates = $geometry->{coordinates} // [];
my $lat = $coordinates->[1] // 0.0;
my $lon = $coordinates->[0] // 0.0;
my $properties = $hash->{properties} // {};
my $timeseries = $properties->{timeseries} // [];

my @timesteps
= map { MyApp::Weather::Model::LocationForecast::TimeStep->new(hash => $_) }
@{$timeseries};
my $tz = $self->finder->time_zone_at(lat => $lat, lon => $lon);
local $ENV{TZ} = $tz if defined $tz;

my @timesteps = map { $self->_get_hours($_) } @{$timeseries};

return Mojo::Collection->with_roles(
'MyApp::Weather::Model::LocationForecast::Role::TimeSeries')
Expand All @@ -35,16 +87,8 @@ sub days ($self) {
my $from = $timestep->from;
my $from_ymd = $from->ymd;

my $to = $timestep->to - 1;
my $to_ymd = $to->ymd;

$date_for{$from_ymd} = $from->truncate(to => 'day');
push @{$timeseries_for{$from_ymd}}, $timestep->clip_to_day($from);

if ($from_ymd ne $to_ymd) {
$date_for{$to_ymd} = $to->truncate(to => 'day');
push @{$timeseries_for{$to_ymd}}, $timestep->clip_to_day($to);
}
push @{$timeseries_for{$from_ymd}}, $timestep;
}

# Get the weather conditions per day.
Expand All @@ -55,8 +99,10 @@ sub days ($self) {
);
} sort keys %timeseries_for;

# The last forecast doesn't span all day.
splice @days, -1;
# The last forecast may not span all day.
if (@days > 0 && @{$days[-1]->timeseries} < 24) {
splice @days, -1;
}

return Mojo::Collection->new(@days);
}
Expand Down
29 changes: 7 additions & 22 deletions lib/MyApp/Weather/Model/LocationForecast/Role/TimeSeries.pm
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ sub available_details ($self) {
sub _merge ($timesteps) {
# Average the timesteps' metrics.
my %average_for;
my $duration_sum = Time::Seconds->new(0);

for my $timestep (@{$timesteps}) {
my $duration = $timestep->duration;

my $details = $timestep->details;
if (defined $details) {
my $duration = $timestep->duration;
for my $metric (keys %{$details}) {
my $value = $details->{$metric};
if (exists $average_for{$metric}) {
Expand All @@ -43,8 +41,6 @@ sub _merge ($timesteps) {
}
}
}

$duration_sum += $duration;
}

my %details;
Expand All @@ -55,26 +51,15 @@ sub _merge ($timesteps) {
}
}

# Create a new datapoint.
# Create a new timestep.
my $first_timestep = $timesteps->[0];
my $from = $first_timestep->from;
my $next_hours = $first_timestep->next_hours;
my $next_hours_key = 'next_' . $duration_sum->hours . '_hours';

my $hash = {
data => {
instant => {details => \%details},
$next_hours_key => $next_hours,
},
time => $first_timestep->hash->{time},
};
my $last_timestep = $timesteps->[-1];

# Create a new timestep.
my $timestep = MyApp::Weather::Model::LocationForecast::TimeStep->new(
hash => $hash,
from => $from,
duration => $duration_sum,
next_hours => $next_hours,
from => $first_timestep->from,
to => $last_timestep->to,
instant => {details => \%details},
period => $first_timestep->period,
);

return $timestep;
Expand Down
Loading

0 comments on commit 8a22790

Please sign in to comment.