http://www.developer.com/

Back to article

Building WML Gadgets: World Time Clock


January 10, 2003

Review

As mentioned in the last few articles, it is possible to add value to a mobile device by creating a small but ultimately useful application. In designing such an application, remember that the user will need to be online to use it, so the application's utility needs to be weighed against the potential cost of use.

Note: Most mobile service plans offer a base amount of online time dedicated to "Web" use. So the user generally isn't paying more for the occasional gadget use.

World Time Application

This article describes how to create another useful small application: a world time clock. This clock tells the time in prominent time zones and areas around the world. If you need to make a call to Sydney, Australia, for example, it would be nice to know if you are in danger of waking someone up, or are calling during the lunch hour.

Our world time clock should accomplish the following:

  • Allow the user to search for a given time zone or area
  • Display the accurate time for the zone(s) found
  • Be easy to use, but complete enough to be useful

More Help from CGI

We could utilize only mobile technologies (WML and WMLScript) to accomplish our goals. However, we'd have to do the following:

  • Find a comprehensive time zone database
  • Parse the database into an array in WMLScript
  • Do time and date calculations to determine the time in various zones from the resulting data

At first, this approach doesn't seem too bad. There are a finite number of time zones worldwide. However, you also have to take daylight saving time into account. For example, central Indiana (which includes Indianapolis) does not observe daylight savings. This means that Indiana, geographically in the Central time zone, seemingly bounces between Central and Eastern time zones. (In reality, central Indiana stays on Eastern Standard Time, and the zones around it shift.)

A comprehensive open source database exists that can be used for this purpose. The database, often referred to as the tz or zoneinfo database, is packaged with most implementation of the GNU C Library, primarily Linux installations. Several tools are available for reading the data from the database, including a Perl module, Time::Timezone (available from CPAN, www.cpan.org).

Note: As with previous articles, teaching Perl is out of the scope of this series. There are numerous sources on the Internet for learning Perl, including the tutorial at http://wdvl.internet.com/Authoring/Languages/Perl/PerlfortheWeb/toc.html.

Starting the Script -- Reading Time Zones and Telling Time

To start the script, we will simply output the time zones and the current time for each. The simple script shown below accomplishes this objective:

Listing: tztest.pl

#!/usr/bin/perl

use Time::ZoneInfo ':all';

my $zones = Time::ZoneInfo->new();

foreach my $zone ($zones->zones) {
  print $zone."\n";
}

This script simply creates a list of all the available zones and outputs each to the console. Next, we need to know what time it is in each zone. Fortunately, the operating system (Linux, in this case) can do the work for us.

Most Linux applications that need to tell time do so by referencing the environment variable TZ. This variable contains the current time zone for the system. The OS can use this variable to decode its internal time clock into the correct value for the zone. If you change this variable and request the time from an application that uses it, you can easily tell the time in another zone.

With help from the Date::Calc module, we can add the current time zone to our test:

Listing: tztest.pl

#!/usr/bin/perl

use Time::ZoneInfo ':all';
use Date::Calc ':all';

my $zones = Time::ZoneInfo->new();

foreach my $zone ($zones->zones) {
  $ENV{TZ} = $zone;
  ($year,$month,$day,$hour,$min,$sec, $doy,$dow,$dst) =
    System_Clock();
  print $zone.":";
  print $year."-".$month."-".$day."  ";
  print $hour.":".$min.":".$sec."\n";
}

Sample tztest.pl output:

. . .
America/New_York: 2002-12-29  23:35:22
America/Detroit: 2002-12-29  23:35:22
America/Louisville: 2002-12-29  23:35:22
America/Kentucky/Monticello: 2002-12-29  23:35:22
America/Indianapolis: 2002-12-29  23:35:22
America/Indiana/Marengo: 2002-12-29 23:35:22
America/Indiana/Knox: 2002-12-29  23:35:22
America/Indiana/Vevay: 2002-12-29  23:35:22
America/Chicago: 2002-12-29  22:35:22
America/Menominee: 2002-12-29  22:35:22
America/North_Dakota/Center: 2002-12-29  22:35:22
America/Denver: 2002-12-29  21:35:22
America/Boise: 2002-12-29  21:35:22
America/Shiprock: 2002-12-29  21:35:22
America/Phoenix: 2002-12-29  21:35:22
America/Los_Angeles: 2002-12-29  20:35:22
America/Anchorage: 2002-12-29  19:35:22
America/Juneau: 2002-12-29  19:35:22
America/Yakutat: 2002-12-29  19:35:22
America/Nome: 2002-12-29  19:35:22
America/Adak: 2002-12-29  18:35:22
. . .

Note that we simply change the TZ environment variable and call the System_Clock method. The variable is only changed within the application space, so the change in time zone doesn't affect the whole system--just our application.

Searching for Time Zones

Next, we need the ability to search for a particular time zone. Adding a variable, "findzone", and a simple substring search accomplishes this goal:

Listing: tztest.pl

#!/usr/bin/perl

use Time::ZoneInfo ':all';
use Date::Calc ':all';

my $findzone = (shift @ARGV);

my $zones = Time::ZoneInfo->new();

foreach my $zone ($zones->zones) {
  if ((index uc($zone), uc($findzone)) != -1) {

  $ENV{TZ} = $zone;
  ($year,$month,$day,$hour,$min,$sec, $doy,$dow,$dst) =
    System_Clock();
  print $zone.":";

  print $year."-".$month."-".$day." ";
  print $hour.":".$min.":".$sec."\n";
  }
}

Note the use of the IF statement. We uppercase both the zone and the findzone variables to help ensure a match (if "asia" is entered, it will still match all "Asia" entries). For example, running the script with this entry:

./tztest.pl pacific

displays the following:

Pacific/Easter: 2002-12-29  23:40:20
Pacific/Galapagos: 2002-12-29  22:40:20
Pacific/Yap: 2002-12-30  14:40:20
Pacific/Truk: 2002-12-30  14:40:20
Pacific/Ponape: 2002-12-30  15:40:20
Pacific/Kosrae: 2002-12-30  15:40:20
Pacific/Tarawa: 2002-12-30  16:40:20
Pacific/Enderbury: 2002-12-30  17:40:20
Pacific/Kiritimati: 2002-12-30  18:40:20
Pacific/Majuro: 2002-12-30  16:40:20
Pacific/Kwajalein: 2002-12-30  16:40:20
Pacific/Auckland: 2002-12-30  17:40:20
Pacific/Chatham: 2002-12-30  18:25:20
Pacific/Tahiti: 2002-12-29  18:40:20
Pacific/Marquesas: 2002-12-29  19:10:20
Pacific/Gambier: 2002-12-29  19:40:20
Pacific/Johnston: 2002-12-29  18:40:20
Pacific/Midway: 2002-12-29  17:40:20
Pacific/Wake: 2002-12-30  16:40:20
Pacific/Honolulu: 2002-12-29  18:40:20

Suppose that the user doesn't know what time zone he/she is looking for. For example, Pacific/Honolulu covers Hawaii, but isn't necessarily intuitive to someone looking for "Hawaii." To help the user, we will add "ALL" as a valid search that returns all zones. To do so, we doctor the IF statement accordingly:

if (((index uc($zone), uc($findzone)) != -1) ||
    ("uc($findzone)" eq "ALL" )) {

Now, if the user enters "ALL" for the search, he/she will get all known zones.

Creating WML Cards

Using methods described in previous articles, it's relatively easy to output compliant WML for mobile devices. We will need another Perl module, CGI, for a couple of purposes:

  • Parsing arguments passed to the script
  • Supplying the mobile browser with an appropriate WML header

Note: After my calendar gadget article was posted, a reader wrote to chastise me for writing my own argument-parsing routine. As I stated in the article, the fact that the script was passing itself cleanly formatted parameters was justification for simple argument parsing. However, one point I missed (brought up by the reader) was that the code might go on to be incorporated in other scripts where the sterility of the parameters could not be guaranteed. To help promote solid coding, I've pledged to use the CGI method "param" from now on.

To output a WML card, we use the following code:

# Pass WML header
print header(-type=>'text/vnd.wap.wml');
 
# Print WML header and beginning tags
print <<ENDHEADER;
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
  "http://www.wapforum.org/DTD/wml_1.1.xml">
 
<wml>
<card>
ENDHEADER

This sets up the beginnings of a generic card. Subsequent PRINT statements can supply <do> and/or <p> tags accordingly. Let's close up the card:

# Print closing tags
print <<ENDFOOTER;
</p>
</card>
</wml>
 
ENDFOOTER

Now all that's left is more Perl logic to control the flow and the resulting output.

The Final Script

Here's a listing of our final script:

Listing: timezone.pl

#!/usr/bin/perl
 
use Time::ZoneInfo ':all';
use Date::Calc ':all';
use CGI qw(:standard);
 
my $findzone = param('findzone');
 
# Pass WML header
print header(-type=>'text/vnd.wap.wml');
 
# Print WML header and beginning tags
print <<ENDHEADER;
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
  "http://www.wapforum.org/DTD/wml_1.1.xml">
 
<wml>
<card>
ENDHEADER
 
# Do we have a time zone to look up?
if ( "$findzone" eq "" ) {
 
# No time zone specified; display input card
print <<INPUTCARD
<do type="accept">
  <go href="timezone.pl?findzone=\$findzone"/>
</do>
<p>
Enter zone text to search<br/>
for or ALL for all zones:
<input name="findzone" maxlength="40" format="*A"/>
 
INPUTCARD
 
} else {
 
$findzone =~ tr/ /_/;
$findzone =~ tr/+/_/;
 
# Set up zones
my $zones = Time::ZoneInfo->new();
my $matches = 0;
 
print "<p>\n";
 
# Run through zones, looking for match
foreach my $zone ($zones->zones) {
 
  if (((index uc($zone), uc($findzone)) != -1) ||
      ( "uc($findzone)" eq "ALL" )) {
    # Display each match, or all if "ALL" was entered
    print $zone." :<br/>";
    $ENV{TZ} = $zone;
    ($year,$month,$day, $hour,$min,$sec,
      $doy,$dow,$dst) = System_Clock();
    print $year."-".$month."-".$day." ";
    print $hour.":".$min.":".$sec."<br/>\n";
    $matches++;
  }
}
 
# If no time zone matches, tell user
if ( $matches eq 0 ) {
    print "No time zone match for:<br/> \n";
    print $findzone."<br/><br/>";
}
 
}
 
# Print closing tags
print <<ENDFOOTER;
</p>
</card>
</wml>
 
ENDFOOTER
 
# End of script

The most notable addition is the IF statement to control which card is displayed. When the script is first executed (without a parameter), the input card is displayed to allow the user to input search text. The script is then called again with the name/value pair "findzone" and the zones are searched for a match.

We've also added logic to tell whether any results were returned. If no results were displayed, the code tells the user ("No time zone match for. . . ").

Note: Pay particular attention to the escaped dollar sign ($) in the <go> tag. Without the escape backslash (\), Perl would interpret "$findzone" as one of its local variables. Since the Perl variable "$findzone" is empty when the script is first called, the resulting <go> tag would incorrectly be sent to the browser as follows:

<go href="timezone.pl?findzone="/>

Room for Improvement

As with previous projects, several things could be added to improve our world time clock:

  • A better format for the time output. Right now values under 10 are output as a single digit. For example, nine o'clock AM is displayed as: "9:0."  Using a format mask or some simple logic we could pad the time accordingly (e.g., "09:00").
  • Break long listings into multiple pages/cards. For example, the "ALL" timezone listing ends up weighing in at just under 5K. That's five times the suggested 1K card data limit. Using some simple logic, the Perl script could display the data in a sequence of cards, each containing 7-12 records.
  • Add a control on the results card(s) to return to the search card.
  • Find/create a more comprehensive time zone database that includes named zones such as EST, CST, etc. (A comprehensive database can be compiled by downloading the source files found at ftp://elsie.nci.nih.gov/pub.)

What Do You Want from WML?

I'm interested in hearing what you need/want to do with WML. I'll use some of the more challenging or common ideas in upcoming articles. Send your ideas to the address below.

About the Author

Steve Schafer is the chief operating officer of Progeny Linux Systems, a Linux-based consulting company in Indianapolis, Indiana. He has written several technical books and articles and can be reached at sschafer@synergy-tech.com.

About this Series

This series of articles describes how to provide Web content to mobile devices through WML (Wireless Markup Language). This article covers creating small programs that extend the functionality of mobile devices, building on the previous two articles and examples.

Note: These articles cover WML and WMLScript version 1.1, which are supported by the majority of mobile devices in use today. The articles assume a working knowledge of HTML and general Web technologies, and further assume that you have read the previous article(s) in this series.

# # #

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date