Get a weather report at the terminal with Perl

Getting a weather forecast can be a chore; you have to navigate to the right website, close the banner ad, type in your location, click the right link, and maybe then you can see a forecast. I wanted a more convenient way and found one using WWW::Wunderground::API. As the name suggests, the module provides a Perl interface to the Wunderground.com API. In this article I’ll show you how to use it.

Setup

You’ll need an API key for Wunderground.com (sign up here it’s free). You’ll also need to install WWW::Wunderground.::API. The CPAN Testers results show that it runs on most platforms, including Windows. You can install the module at the command line:

$ cpan WWW::Wunderground::API

The Code

Using WWW::Wunderground::API, I created a script that would pull an hourly forecast for my local city:

#!/usr/bin/env perl
use strict;
use warnings;
use WWW::Wunderground::API;

binmode STDOUT, ':utf8'; # for degrees symbol

my $w = new WWW::Wunderground::API(
    location => 'New York City, NY',
    api_key  => '123456789012345',
    auto_api => 1,
);

# print header
printf "%-10s%-4s%-4s%-8s%-20s\n",
       'Time',
       "\x{2109}",
       "\x{2103}",
       'Rain %',
       'Conditions';

# print hourly
for (@{ $w->hourly })
{
    printf "%8s%4i%4i%8i  %-30s\n",
           $_->{FCTTIME}{civil},
           $_->{temp}{english},
           $_->{temp}{metric},
           $_->{pop},
           $_->{condition};
}

In the script I use code>binmode to switch the standard output to UTF8 mode. This lets me print some cool degrees symbols later on. I then connect to the Wunderground API, passing my API key and location (location can be a city name or a zip code). Finally I print out the weather forecast using printf to format the output nicely. I saved the script as weather and ran it at the command line:

$ weather
Time            Rain %  Conditions
11:00 PM  69  21       3  Partly Cloudy
12:00 AM  69  21       3  Partly Cloudy
 1:00 AM  69  21       8  Partly Cloudy
 2:00 AM  69  21       9  Mostly Cloudy
 3:00 AM  69  21       8  Mostly Cloudy
 4:00 AM  69  21       5  Mostly Cloudy
 5:00 AM  69  21       5  Overcast
 6:00 AM  69  21       4  Overcast
 7:00 AM  69  21       4  Mostly Cloudy
 8:00 AM  70  21       4  Mostly Cloudy
 9:00 AM  72  22       3  Mostly Cloudy
10:00 AM  74  23       2  Mostly Cloudy
11:00 AM  77  25       2  Mostly Cloudy
12:00 PM  80  27       2  Mostly Cloudy
 1:00 PM  82  28       1  Mostly Cloudy
 2:00 PM  84  29       7  Overcast
 3:00 PM  84  29      46  Chance of a Thunderstorm
 4:00 PM  84  29      52  Chance of a Thunderstorm
 5:00 PM  82  28      56  Chance of a Thunderstorm
 6:00 PM  82  28      45  Chance of a Thunderstorm
 7:00 PM  81  27      50  Chance of a Thunderstorm
 8:00 PM  80  27      39  Chance of a Thunderstorm
 9:00 PM  78  26      32  Chance of a Thunderstorm
10:00 PM  77  25      38  Chance of a Thunderstorm
11:00 PM  74  23       6  Partly Cloudy
12:00 AM  71  22       3  Clear
 1:00 AM  69  21       3  Clear
 2:00 AM  67  19       2  Partly Cloudy
 3:00 AM  65  18       2  Clear
 4:00 AM  64  18       2  Clear
 5:00 AM  62  17       2  Clear
 6:00 AM  61  16       2  Clear
 7:00 AM  60  16       2  Clear
 8:00 AM  60  16       2  Clear
 9:00 AM  62  17       1  Clear
10:00 AM  64  18       0  Clear

The results show an hourly forecast with the temperature in Fahrenheit and Celsius, the probability of rain and an overall description. As I do most of my work from the terminal, this is much more convenient than using the browser and there are no ads!

Multiple Locations

So the script is nice, but how can we make it better? Well, I’m rarely in the same place all the time, and I expect most people mover around too, so it would good to be more flexible and let the user type in the location, rather than using the same location every time:

#!/usr/bin/env perl
use strict;
use warnings;
use WWW::Wunderground::API;

my $home_location = 'New York City, NY';

# capture location
print "Enter city or zip code ($home_location): ";
my $location = <>;
chomp $location;

binmode STDOUT, ':utf8'; # for degrees symbol
my $w = new WWW::Wunderground::API(
    location => $location || $home_location,
    api_key  => '123456789012345',
    auto_api => 1,
);

# print header
printf "%-10s%-4s%-4s%-8s%-20s\n",
       'Time',
       "\x{2109}",
       "\x{2103}",
       'Rain %',
       'Conditions';

# print hourly
for (@{ $w->hourly })
{
    printf "%8s%4i%4i%8i  %-30s\n",
           $_->{FCTTIME}{civil},
           $_->{temp}{english},
           $_->{temp}{metric},
           $_->{pop},
           $_->{condition};
}

I’ve updated the code to store a default location called $home_location. I then ask the user to enter a City or zip code, making sure to chomp the result. Later in the API call, the code: $location || $home_location will submit the home location unless the user has entered a location. Running the script now, I can get the weather for London easily:

$ weather
Enter city or zip code (New York City, NY): London, UK
Time            Rain %  Conditions
 4:00 AM  50  10       4  Clear
 5:00 AM  50  10       4  Clear
 6:00 AM  49   9       4  Clear
 7:00 AM  49   9       4  Clear
 8:00 AM  52  11       4  Clear
 9:00 AM  55  13       4  Clear
10:00 AM  59  15       4  Clear
11:00 AM  62  17       3  Clear
...

Caching

The WWW::Wunderground::API documentation shows how to use Cache::FileCache to cache the weather results locally. When you setup the cache, you can specify an expiry parameter - until the cache expires the WWW::Wunderground::API will use the cached results instead of the Wunderground API. This prevents unnecessary API calls and makes the script faster:

#!/usr/bin/env perl
use strict;
use warnings;
use WWW::Wunderground::API;
use Cache::FileCache;

my $home_location = 'New York City, NY';

#capture location
print "Enter city or zip code ($home_location): ";
my $location = <>;
chomp $location;

binmode STDOUT, ':utf8'; # for degrees symbol
my $w = new WWW::Wunderground::API(
    location => $location || $home_location,
    api_key  => '123456789012345',
    auto_api => 1,
    cache    => Cache::FileCache->new({
                    namespace          => 'wundercache',
                    default_expires_in => 2400 }),
);

# print header
printf "%-10s%-4s%-4s%-8s%-20s\n",
       'Time',
       "\x{2109}",
       "\x{2103}",
       'Rain %',
       'Conditions';

# print hourly
for (@{ $w->hourly })
{
    printf "%8s%4i%4i%8i  %-30s\n",
           $_->{FCTTIME}{civil},
           $_->{temp}{english},
           $_->{temp}{metric},
           $_->{pop},
           $_->{condition};
}

Not much has changed in the code. The line use Cache::FileCache; imports the module and a cache parameter has been added to the Wunderground API call. WWW::Wunderground::API is smart enough to not return cached results for different locations.

Conclusion

That’s probably enough to get started, however there is more that could be done with this script. I could make the script more portable by using environment variables instead of the hard coded values for my API key and home location. Exception handling could better - checking for an internet connection before running the script, handling failed API calls more gracefully (for unknown locations for example). Finally, why have the user type in a location at all? We could use get the user’s IP address and then geolocate them using the Geo::IP module.

The Wunderground API provides a lot more than just a 24 hour forecast. Check out their API documentation.

Cover image © NASA Goddard Space Flight Center


This article was originally posted on PerlTricks.com.

Tags

David Farrell

David is a professional programmer who regularly tweets and blogs about code and the art of programming.

Browse their articles

Feedback

Something wrong with this article? Help us out by opening an issue or pull request on GitHub