Commits

Miki Tebeka committed 9b3182e

timeat

Comments (0)

Files changed (1)

+// Mash between Google maps API and geonames API to get the current time at a
+// given location
+package main
+
+import (
+	"encoding/json"
+	"encoding/xml"
+	"flag"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"net/url"
+	"os"
+)
+
+const (
+	googleKey    = "your google API key goes here"
+	geonamesUser = "your geonames user"
+)
+
+type Location struct {
+	location string
+	lat, lng float64
+	time     string
+	err      error
+}
+
+// parseTimeReply parse the XML returned from geonames, returns the time
+func parseTimeReply(reader io.Reader) (string, error) {
+	dec := xml.NewDecoder(reader)
+	get := false
+
+	// SAX style parsing, we're only interested in first <time> element
+	for {
+		tok, err := dec.Token()
+		if err != nil {
+			return "", err
+		}
+
+		switch typ := tok.(type) {
+		case xml.StartElement:
+			if typ.Name.Local == "time" {
+				get = true
+			}
+		case xml.CharData:
+			if get {
+				return string(typ), nil
+			}
+		}
+	}
+
+	return "", fmt.Errorf("'time' element not found in reply")
+}
+
+// Find current time in location from geonames
+func findTime(loc *Location, out chan *Location) {
+	// Emit loc to channel when done
+	defer func() {
+		out <- loc
+	}()
+
+	values := url.Values{
+		"username": {geonamesUser},
+		"lat":      {fmt.Sprintf("%f", loc.lat)},
+		"lng":      {fmt.Sprintf("%f", loc.lng)},
+	}
+
+	url := fmt.Sprintf("http://api.geonames.org/timezone?%s", values.Encode())
+	resp, err := http.Get(url)
+	if err != nil {
+		loc.err = err
+		return
+	}
+
+	defer resp.Body.Close()
+	loc.time, loc.err = parseTimeReply(resp.Body)
+}
+
+// findPlaces finds matches for location from Google maps API
+func findPlaces(location string) (chan *Location, error) {
+	values := url.Values{
+		"key": {googleKey},
+		"q":   {location},
+	}
+
+	url := fmt.Sprintf("http://maps.google.com/maps/geo?%s", values.Encode())
+	resp, err := http.Get(url)
+	if err != nil {
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+
+	dec := json.NewDecoder(resp.Body)
+	// Just enought to get the data from the JSON reply
+	var reply struct {
+		Status struct {
+			Code int
+		}
+
+		Placemark []struct {
+			Point struct {
+				Coordinates []float64
+			}
+			Address string
+		}
+	}
+
+	if err = dec.Decode(&reply); err != nil {
+		return nil, err
+	}
+
+	out := make(chan *Location)
+	// Fill the channel in a different goroutine
+	go func() {
+		for _, pm := range reply.Placemark {
+			out <- &Location{
+				lng:      pm.Point.Coordinates[0],
+				lat:      pm.Point.Coordinates[1],
+				location: pm.Address,
+			}
+		}
+		close(out)
+	}()
+
+	return out, nil
+}
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, "usage: %s LOCATION\n", os.Args[0])
+	}
+	flag.Parse()
+
+	if flag.NArg() != 1 {
+		log.Fatalf("error: wrong number of arguments")
+	}
+
+	places, err := findPlaces(flag.Arg(0))
+	if err != nil {
+		log.Fatalf("error: %s\n", err)
+	}
+
+	out := make(chan *Location)
+
+	// We count since we don't close the channel
+	count := 0
+	for loc := range places {
+		go findTime(loc, out)
+		count++
+	}
+
+	for ; count > 0; count-- {
+		loc := <-out
+		if loc.err != nil {
+			fmt.Fprintf(os.Stderr, "error: %s [%s]", loc.err, loc.location)
+			continue
+		}
+		fmt.Printf("%s: %s\n", loc.location, loc.time)
+	}
+}