The information is now a full URL
[google-weather-el.git] / google-weather.el
blob6b408475937153a3da533fe42c74ae4d8a6a6730
1 ;;; google-weather.el --- Fetch Google Weather forecasts.
3 ;; Copyright (C) 2010 Julien Danjou
5 ;; Author: Julien Danjou <julien@danjou.info>
6 ;; Keywords: comm
8 ;; This file is NOT part of GNU Emacs.
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23 ;;; Commentary:
24 ;; This module allows you to fetch Google Weather forecast from the
25 ;; Internet.
27 ;;; Code:
29 (require 'url)
30 (require 'url-cache)
31 (require 'xml)
32 (require 'time-date)
34 (eval-when-compile
35 (require 'cl))
37 (defgroup google-weather nil
38 "Google Weather."
39 :group 'comm)
41 (defcustom google-weather-use-https t
42 "Default protocol to use to access the Google Weather API."
43 :group 'google-weather)
45 (defconst google-weather-url
46 "www.google.com/ig/api"
47 "URL of the Google Weather API.")
49 (defcustom google-weather-unit-system-temperature-assoc
50 '(("SI" . "℃")
51 ("US" . "℉"))
52 "Find temperature symbol from unit system."
53 :group 'google-weather)
55 (defun google-weather-cache-expired (url expire-time)
56 "Check if URL is cached for more than EXPIRE-TIME."
57 (cond (url-standalone-mode
58 (not (file-exists-p (url-cache-create-filename url))))
59 (t (let ((cache-time (url-is-cached url)))
60 (if cache-time
61 (time-less-p
62 (time-add
63 cache-time
64 (seconds-to-time expire-time))
65 (current-time))
66 t)))))
68 (defun google-weather-cache-fetch (url)
69 "Fetch URL from the cache."
70 (with-current-buffer (generate-new-buffer " *temp*")
71 (url-cache-extract (url-cache-create-filename url))
72 (current-buffer)))
74 (defun google-weather-retrieve-data-raw (url &optional expire-time)
75 "Retrieve URL and return its data as string.
76 If EXPIRE-TIME is set, the data will be fetched from the cache if
77 their are not older than EXPIRE-TIME seconds. Otherwise, they
78 will be fetched and then cached. Therefore, setting EXPIRE-TIME
79 to 0 force a cache renewal."
80 (let* ((expired (if expire-time
81 (google-weather-cache-expired url expire-time)
82 t))
83 (buffer (if expired
84 (url-retrieve-synchronously url)
85 (google-weather-cache-fetch url)))
86 data)
87 (when (and expired expire-time)
88 (url-store-in-cache buffer))
89 buffer))
91 (defun google-weather-retrieve-data (url &optional expire-time)
92 "Retrieve URL and return its data as string.
93 If EXPIRE-TIME is set, the data will be fetched from the cache if
94 their are not older than EXPIRE-TIME seconds. Otherwise, they
95 will be fetched and then cached. Therefore, setting EXPIRE-TIME
96 to 0 force a cache renewal."
97 (with-current-buffer (google-weather-retrieve-data-raw
98 url expire-time)
99 (goto-char (point-min))
100 (unless (search-forward "\n\n" nil t)
101 (error "Data not found"))
102 (decode-coding-region
103 (point) (point-max)
104 (detect-coding-region (point) (point-max) t))
105 (set-buffer-multibyte t)
106 (setq data (xml-parse-region (point) (point-max)))
107 (kill-buffer (current-buffer))
108 data)))
110 (defun google-weather-build-url (location &optional language)
111 "Build URL to retrieve weather for LOCATION in LANGUAGE."
112 (concat "http" (when google-weather-use-https "s") "://" google-weather-url "?weather=" (url-hexify-string location)
113 (when language
114 (concat "&hl=" language))))
116 (defun google-weather-get-data (location &optional language expire-time)
117 "Get weather data for LOCATION in LANGUAGE.
118 See `google-weather-retrieve-data' for the use of EXPIRE-TIME."
119 (google-weather-retrieve-data
120 (google-weather-build-url location language) expire-time))
122 (defun google-weather-data->weather (data)
123 "Return all weather information from DATA."
124 (cddr (assoc 'weather (cdr (assoc 'xml_api_reply data)))))
126 (defun google-weather-data->forecast-information (data)
127 "Return the forecast information of DATA."
128 (cddr (assoc 'forecast_information (google-weather-data->weather data))))
130 (defun google-weather-assoc (key data)
131 "Extract value of field KEY from DATA."
132 (cdr (assoc 'data (cadr (assoc key data)))))
134 (defun google-weather-data->city (data)
135 "Return the city where the DATA come from."
136 (google-weather-assoc
137 'city
138 (google-weather-data->forecast-information data)))
140 (defun google-weather-data->postal-code (data)
141 "Return the postal code where the DATA come from."
142 (google-weather-assoc
143 'postal_code
144 (google-weather-data->forecast-information data)))
146 (defun google-weather-data->unit-system (data)
147 "Return the unit system used for DATA."
148 (google-weather-assoc
149 'unit_system
150 (google-weather-data->forecast-information data)))
152 (defun google-weather-data->forecast-date (data)
153 "Return the unit system used for DATA."
154 (google-weather-assoc
155 'forecast_date
156 (google-weather-data->forecast-information data)))
158 (defun google-weather-data->forecast (data)
159 "Get forecast list from DATA."
160 ;; Compute date of the forecast in the same format as `current-time'
161 (let ((date (apply 'encode-time
162 (parse-time-string
163 (concat (google-weather-data->forecast-date data) " 00:00:00")))))
164 (mapcar
165 (lambda (forecast)
166 (let* ((forecast-date (decode-time date))
167 (forecast-encoded-date (list (nth 4 forecast-date)
168 (nth 3 forecast-date)
169 (nth 5 forecast-date))))
170 ;; Add one day to `date'
171 (setq date (time-add date (days-to-time 1)))
172 `(,forecast-encoded-date
173 (low ,(google-weather-assoc 'low forecast))
174 (high ,(google-weather-assoc 'high forecast))
175 (icon ,(google-weather-assoc 'icon forecast))
176 (condition ,(google-weather-assoc 'condition forecast)))))
177 (loop for entry in (google-weather-data->weather data)
178 when (eq (car entry) 'forecast_conditions)
179 collect entry))))
181 (defun google-weather-data->forecast-for-date (data date)
182 "Return forecast for DATE from DATA.
183 DATE should be in the same format used by calendar,
184 i.e. (MONTH DAY YEAR)."
185 (cdr (assoc date (google-weather-data->forecast data))))
187 (defun google-weather-data->temperature-symbol (data)
188 "Return the temperature to be used according in DATA.
189 It uses `google-weather-unit-system-temperature-assoc' to find a
190 match."
191 (cdr (assoc (google-weather-data->unit-system data) google-weather-unit-system-temperature-assoc)))
194 (defun google-weather-data->problem-cause (data)
195 "Return a string if DATA contains a problem cause, `nil' otherwise.
197 An error message example:
199 ((xml_api_reply
200 ((version . \"1\"))
201 (weather
202 ((module_id . \"0\") (tab_id . \"0\") (mobile_row . \"0\")
203 (mobile_zipped . \"1\") (row . \"0\") (section . \"0\"))
204 (problem_cause ((data . \"Information is temporarily unavailable.\"))))))))"
205 (google-weather-assoc
206 'problem_cause
207 (google-weather-data->weather data)))
209 (provide 'google-weather)