improved timezone support

cal celestial can now query the timezone at the place of the observer (use -l)
This commit is contained in:
Steffen Vogel 2016-10-27 02:25:32 -04:00
parent 112fd4cfd6
commit 33aed1302a
4 changed files with 308 additions and 229 deletions

View file

@ -44,7 +44,6 @@
#include "../config.h"
#include "objects.h"
#include "helpers.h"
#include "formatter.h"
#include "geonames.h"
@ -59,6 +58,7 @@ static struct option long_options[] = {
{"lon", required_argument, 0, 'o'},
#ifdef GEONAMES_SUPPORT
{"query", required_argument, 0, 'q'},
{"local", no_argument, 0, 'l'},
#endif
{"timezone", required_argument, 0, 'z'},
{"universal", no_argument, 0, 'u'},
@ -70,16 +70,17 @@ static struct option long_options[] = {
static const char *long_options_descs[] = {
"calc for celestial object: sun, moon, mars, neptune,\n\t\t\t jupiter, mercury, uranus, saturn, venus or pluto",
"calc rise/set time with twilight: nautic, civil or astronomical",
"calc at given time: YYYY-MM-DD [HH:MM:SS]",
"calc at given time: YYYY-MM-DD[_HH:MM:SS]",
"calc position at moment of: rise, set, transit",
"use rise, set, transit time of tomorrow",
"output format: see strftime (3) and calcelestial (1) for more details",
"geographical latitude of observer: -90° to 90°",
"geographical longitude of oberserver: -180° to 180°",
#ifdef GEONAMES_SUPPORT
"query geonames.org for geographical coordinates",
"query coordinates using the geonames.org geolocation service",
"query local timezone using the geonames.org geolocation service",
#endif
"override system timezone",
"override system timezone (TZ environment variable)",
"use universial time for parsing and formatting",
"show usage help",
"show version"
@ -120,7 +121,7 @@ int main(int argc, char *argv[])
int ret;
time_t t;
double jd;
struct tm *date = NULL;
struct tm tm;
const struct object *obj;
/* Default options */
@ -128,12 +129,16 @@ int main(int argc, char *argv[])
int tz = INT_MAX;
char *obj_str = basename(argv[0]);
char *format = "time: %Y-%m-%d %H:%M:%S az: §a (§s) alt: §h";
char *format = "time: %Y-%m-%d %H:%M:%S (%Z) az: §a (§s) alt: §h";
char tzid[32] = "";
char *query = NULL;
bool horizon_set = false;
bool utc = false;
bool next = false;
bool local_tz = false;
time(&t);
localtime_r(&t, &tm);
enum {
MOMENT_NOW,
@ -147,7 +152,7 @@ int main(int argc, char *argv[])
/* parse command line arguments */
while (1) {
char c = getopt_long(argc, argv, "+hvnut:d:f:a:o:q:z:p:m:H:", long_options, NULL);
char c = getopt_long(argc, argv, "+hvnult:d:f:a:o:q:z:p:m:H:", long_options, NULL);
/* detect the end of the options. */
if (c == -1)
@ -173,13 +178,14 @@ int main(int argc, char *argv[])
break;
case 't':
date = malloc(sizeof(struct tm));
date->tm_isdst = -1; /* update dst */
if (strptime(optarg, "%Y-%m-%d %H:%M:%S", date)) { }
else if (strptime(optarg, "%Y-%m-%d", date)) { }
tm.tm_isdst = -1; /* update dst */
if (strchr(optarg, '_')) {
if (!strptime(optarg, "%Y-%m-%d_%H:%M:%S", &tm))
usage_error("invalid time/date parameter");
}
else {
free(date);
usage_error("invalid date parameter");
if (!strptime(optarg, "%Y-%m-%d", &tm))
usage_error("invalid time/date parameter");
}
break;
@ -213,19 +219,21 @@ int main(int argc, char *argv[])
case 'q':
query = strdup(optarg);
break;
#endif
case 'l':
local_tz = true;
break;
#endif
case 'p':
obj_str = optarg;
break;
case 'z':
tz = atoi(optarg);
strncpy(tzid, optarg, sizeof(tzid));
break;
case 'u':
utc = true;
tz = 0;
strncpy(tzid, "UTC", sizeof(tzid));
break;
case 'v':
@ -250,33 +258,35 @@ int main(int argc, char *argv[])
#ifdef GEONAMES_SUPPORT
/* Lookup place at http://geonames.org */
if (query) {
ret = geonames_lookup(query, &obs, NULL, 0);
ret = geonames_lookup_latlng(query, &obs, NULL, 0);
if (ret)
usage_error("failed to lookup location");
}
if (local_tz) {
int gmt_offset;
ret = geonames_lookup_tz(obs, &gmt_offset, tzid, sizeof(tzid));
if (ret)
usage_error("failed to lookup location");
}
#endif
setenv("TZ", tzid, 1);
tzset();
/* Validate observer coordinates */
if (fabs(obs.lat) > 90)
usage_error("invalid latitude, use --lat");
if (fabs(obs.lng) > 180)
usage_error("invalid longitude, use --lon");
if (horizon_set && !strcmp(object_name(obj), "sun"))
if (horizon_set && strcmp(object_name(obj), "sun"))
usage_error("the twilight parameter can only be used for the sun");
/* Calculate julian date */
if (date) {
t = (utc) ? mktimeutc(date) : mktime(date);
free(date);
}
else
t = time(NULL);
t = mktime(&tm);
jd = ln_get_julian_from_timet(&t);
date = localtime(&t);
result.tz = (tz == INT_MAX) ? date->tm_gmtoff / 3600 : tz;
result.obs = obs;
#ifdef DEBUG
@ -285,7 +295,7 @@ int main(int argc, char *argv[])
printf("Debug: for position: N %f, E %f\n", obs.lat, obs.lng);
printf("Debug: for object: %s\n", object_name(obj));
printf("Debug: with horizon: %f\n", horizon);
printf("Debug: with timezone: %d\n", result.tz);
printf("Debug: with timezone: %s\n", tzid);
#endif
/* calc rst date */
@ -309,11 +319,12 @@ rst: if (object_rst(obj, jd - .5, horizon, &result.obs, &result.rst) == 1) {
goto rst;
}
}
ln_get_timet_from_julian(result.jd, &t);
localtime_r(&t, &result.tm);
/* calc position */
object_pos(obj, result.jd, &result);
object_pos(obj, jd, &result);
/* format & output */
format_result(format, &result);
return 0;

View file

@ -39,176 +39,38 @@
#include "../config.h"
#include "geonames.h"
#include "formatter.h"
const char* username = "libastro";
const char* request_url_tpl = "http://api.geonames.org/search?name=%s&maxRows=1&username=%s&type=json&orderby=relevance";
static const char* url_tpl = "http://api.geonames.org/search?q=%s&maxRows=1&username=libastro&type=json&orderby=relevance";
static const char* url_tz_tpl = "http://api.geonames.org/timezoneJSON?lat=%.6f&lng=%.6f&username=libastro";
static size_t json_parse_callback(void *contents, size_t size, size_t nmemb, void *userp)
{
static struct json_tokener *jtok;
static struct json_object *jobj;
size_t realsize = size * nmemb;
struct string {
char *ptr;
size_t len;
};
/* initialize tokener */
if (jtok == NULL) {
jtok = json_tokener_new();
jtok->err = json_tokener_continue;
}
struct ctx_latlng {
struct ln_lnlat_posn *coords;
char *name;
size_t namelen;
};
if (jtok->err == json_tokener_continue) {
#ifdef DEBUG
printf("Debug: received chunk: %zu * %zu = %zu bytes\r\n", size, nmemb, realsize);
printf(" %.*s\r\n", (int) realsize, (char *) contents);
#endif
struct ctx_tz {
int *gmt_offset;
char *tzid;
size_t tzidlen;
};
jobj = json_tokener_parse_ex(jtok, (char *) contents, realsize);
if (jtok->err == json_tokener_success) {
*(struct json_object **) userp = jobj;
json_tokener_free(jtok);
}
else if (jtok->err != json_tokener_continue) {
const char *err = json_tokener_error_desc(json_tokener_get_error(jtok));
fprintf(stderr, "json parse error: %s\r\n", err);
*(void **) userp = NULL;
json_tokener_free(jtok);
}
}
return realsize;
}
int geonames_lookup(const char *place, struct ln_lnlat_posn *result, char *name, int n)
{
#ifdef GEONAMES_CACHE_SUPPORT
int ret;
ret = geonames_cache_db(1, place, result);
if (ret == 0) {
strncpy(name, place, n);
#ifdef DEBUG
printf("Debug: using cached geonames entry\n");
#endif
return 0;
}
#endif
enum cache_op { STORE, LOOKUP, DELETE };
CURL *ch;
CURLcode res;
struct json_object *jobj;
/* setup curl */
ch = curl_easy_init();
if (!ch)
return -1;
/* prepare url */
int len = strlen(place) + strlen(request_url_tpl) + 1;
char *request_url = malloc(len);
if (!request_url)
return -2;
snprintf(request_url, len, request_url_tpl, place, username);
#ifdef DEBUG
printf("Debug: request url: %s\r\n", request_url);
#endif
curl_easy_setopt(ch, CURLOPT_URL, request_url);
curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, json_parse_callback);
curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void *) &jobj);
curl_easy_setopt(ch, CURLOPT_USERAGENT, "libastro/1.0");
/* perform request */
res = curl_easy_perform(ch);
/* always cleanup */
curl_easy_cleanup(ch);
if (res != CURLE_OK) {
fprintf(stderr, "request failed: %s\n", curl_easy_strerror(res));
return -1;
}
if (jobj) {
int ret = geonames_parse(jobj, result, name, n);
if (!ret) {
#ifdef GEONAMES_CACHE_SUPPORT
geonames_cache_db(0, place, result);
#ifdef DEBUG
printf("Debug: storing cache entry\n");
#endif
#endif
}
json_object_put(jobj);
return ret;
}
else
return -1;
}
int geonames_parse(struct json_object *jobj, struct ln_lnlat_posn *result, char *name, int n)
{
json_bool exists;
json_object *jobj_count, *jobj_geonames, *jobj_place, *jobj_lat, *jobj_lng, *jobj_name;
int results;
exists = json_object_object_get_ex(jobj, "totalResultsCount", &jobj_count);
if (!exists)
return -1;
exists = json_object_object_get_ex(jobj, "geonames", &jobj_geonames);
if (!exists)
return -1;
results = json_object_get_int(jobj_count);
if (results == 0)
return -1;
jobj_place = json_object_array_get_idx(jobj_geonames, 0);
if (!jobj_place)
return -1;
exists = json_object_object_get_ex(jobj_place, "lat", &jobj_lat);
if (!exists)
return -1;
exists = json_object_object_get_ex(jobj_place, "lng", &jobj_lng);
if (!exists)
return -1;
exists = json_object_object_get_ex(jobj_name, "name", &jobj_name);
if (!exists)
return -1;
result->lat = json_object_get_double(jobj_lat);
result->lng = json_object_get_double(jobj_lng);
if (name && n > 0)
strncpy(name, json_object_get_string(jobj_name), n);
return 0;
}
int geonames_cache_db(int lookup, const char *place, struct ln_lnlat_posn *coords)
static int cache(enum cache_op op, const char *url, struct string *s)
{
int ret;
DB *dbp;
DBT key, data;
char filename[256];
char *place_lower;
place_lower = strdup(place);
if (!place_lower)
return -1;
for (char *p = place_lower; *p; ++p)
*p = tolower(*p);
snprintf(filename, sizeof(filename), "%s/%s", getenv("HOME"), GEONAMES_CACHE_FILE);
dbp = dbopen(filename, O_RDWR | O_CREAT, 0664, DB_BTREE, NULL);
@ -220,37 +82,43 @@ int geonames_cache_db(int lookup, const char *place, struct ln_lnlat_posn *coord
memset(&key, 0, sizeof(key));
memset(&data, 0, sizeof(data));
key.data = place_lower;
key.size = strlen(place_lower) + 1;
key.data = (void *) url;
key.size = strlen(url) + 1;
if (lookup) {
ret = dbp->get(dbp, &key, &data, 0);
if (ret)
goto err;
switch (op) {
case LOOKUP:
ret = dbp->get(dbp, &key, &data, 0);
if (ret)
goto err;
#ifdef DEBUG
printf("Debug: cache key retrieved: %s => %f %f.\n", (char *) key.data,
((struct ln_lnlat_posn *) data.data)->lat,
((struct ln_lnlat_posn *) data.data)->lng);
#endif
if (data.size != sizeof(struct ln_lnlat_posn))
goto err;
printf("Debug: cache key retrieved: %s => %s.\n", (char *) key.data, (char *) data.data);
#endif /* DEBUG */
s->ptr = malloc(data.size);
s->len = data.size;
if (!s->ptr) {
fprintf(stderr, "malloc() failed: %s\n", strerror(errno));
exit(1);
}
memcpy(coords, data.data, sizeof(struct ln_lnlat_posn));
}
else {
data.data = coords;
data.size = sizeof(struct ln_lnlat_posn);
memcpy(s->ptr, data.data, data.size);
break;
ret = dbp->put(dbp, &key, &data, 0);
if (ret)
goto err;
case STORE:
data.data = s->ptr;
data.size = s->len;
ret = dbp->put(dbp, &key, &data, 0);
if (ret)
goto err;
#ifdef DEBUG
printf("Debug: cache key stored: %s => %f %f.\n", (char *) key.data,
((struct ln_lnlat_posn *) data.data)->lat,
((struct ln_lnlat_posn *) data.data)->lng);
#endif
printf("Debug: cache key stored: %s => %s\n", (char *) key.data, (char *) data.data);
#endif /* DEBUG */
break;
case DELETE: {
// TODO
}
}
err:
@ -258,3 +126,201 @@ err:
return ret;
}
#endif /* GEONAMES_CACHE_SUPPORT */
static size_t writefunction(void *contents, size_t size, size_t nmemb, void *userp)
{
struct string *s = userp;
size_t new_len = s->len + size*nmemb;
s->ptr = realloc(s->ptr, new_len + 1);
if (s->ptr == NULL) {
fprintf(stderr, "realloc() failed\n");
exit(EXIT_FAILURE);
}
memcpy(s->ptr + s->len, contents, size*nmemb);
s->ptr[new_len] = '\0';
s->len = new_len;
return size * nmemb;
}
static int request_json(const char *url, int (*parser)(struct json_object *jobj, void *ctx), void *ctx)
{
int ret, cached;
CURL *ch;
CURLcode res;
struct json_object *jobj;
enum json_tokener_error error;
struct string s = { 0 };
#ifdef GEONAMES_CACHE_SUPPORT
cached = cache(LOOKUP, url, &s) == 0;
if (cached)
goto cached;
#endif /* GEONAMES_CACHE_SUPPORT */
/* Setup curl */
ch = curl_easy_init();
if (!ch)
return -1;
#ifdef DEBUG
printf("Debug: request url: %s\r\n", url);
#endif /* DEBUG */
curl_easy_setopt(ch, CURLOPT_URL, url);
curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, writefunction);
curl_easy_setopt(ch, CURLOPT_WRITEDATA, (void *) &s);
curl_easy_setopt(ch, CURLOPT_USERAGENT, "libastro/1.0");
/* perform request */
res = curl_easy_perform(ch);
/* always cleanup */
curl_easy_cleanup(ch);
if (res != CURLE_OK) {
fprintf(stderr, "Error: request failed: %s\n", curl_easy_strerror(res));
return -1;
}
#ifdef DEBUG
printf("Debug: request completed: %s\r\n", s.ptr);
#endif /* DEBUG */
cached:
jobj = json_tokener_parse_verbose(s.ptr, &error);
if (!jobj) {
#ifdef DEBUG
printf("Debug: failed to parse json: %s\r\n", json_tokener_error_desc(error));
#endif /* DEBUG */
}
ret = parser(jobj, ctx);
if (!ret && !cached) {
#ifdef GEONAMES_CACHE_SUPPORT
cache(STORE, url, &s);
#endif /* GEONAMES_CACHE_SUPPORT */
}
#ifdef DEBUG
else
printf("Debug: failed to parse: %d\n", ret);
#endif /* DEBUG */
json_object_put(jobj);
return ret;
}
static int parser_tz(struct json_object *jobj, void *userp)
{
struct ctx_tz *ctx = userp;
json_bool exists;
struct json_object *jobj_offset, *jobj_tzid;
exists = json_object_object_get_ex(jobj, "gmtOffset", &jobj_offset);
if (!exists)
return -1;
exists = json_object_object_get_ex(jobj, "timezoneId", &jobj_tzid);
if (!exists)
return -1;
*ctx->gmt_offset = json_object_get_int(jobj_offset);
if (ctx->tzid)
strncpy(ctx->tzid, json_object_get_string(jobj_tzid), ctx->tzidlen);
return 0;
}
static int parser_latlng(struct json_object *jobj, void *userp)
{
struct ctx_latlng *ctx = userp;
json_bool exists;
struct json_object *jobj_count, *jobj_geonames, *jobj_place, *jobj_lat, *jobj_lng, *jobj_name;
int results;
exists = json_object_object_get_ex(jobj, "totalResultsCount", &jobj_count);
if (!exists)
return -1;
exists = json_object_object_get_ex(jobj, "geonames", &jobj_geonames);
if (!exists)
return -2;
results = json_object_get_int(jobj_count);
if (results == 0)
return -3;
jobj_place = json_object_array_get_idx(jobj_geonames, 0);
if (!jobj_place)
return -4;
exists = json_object_object_get_ex(jobj_place, "lat", &jobj_lat);
if (!exists)
return -5;
exists = json_object_object_get_ex(jobj_place, "lng", &jobj_lng);
if (!exists)
return -6;
exists = json_object_object_get_ex(jobj_place, "name", &jobj_name);
if (!exists)
return -7;
ctx->coords->lat = json_object_get_double(jobj_lat);
ctx->coords->lng = json_object_get_double(jobj_lng);
if (ctx->name)
strncpy(ctx->name, json_object_get_string(jobj_name), ctx->namelen);
return 0;
}
int geonames_lookup_tz(struct ln_lnlat_posn coords, int *gmt_offset, char *tzid, size_t tzidlen)
{
char url[256];
struct ctx_tz ctx = {
.gmt_offset = gmt_offset,
.tzid = tzid,
.tzidlen = tzidlen
};
snprintf(url, sizeof(url), url_tz_tpl, coords.lat, coords.lng);
return request_json(url, parser_tz, &ctx);
}
int geonames_lookup_latlng(const char *place, struct ln_lnlat_posn *coords, char *name, size_t namelen)
{
int ret;
char url[256];
struct ctx_latlng ctx = {
.coords = coords,
.name = name,
.namelen = namelen
};
//char *escaped_place = curl_escape(place, 0);
char *escaped_place = strrepl(place, " ", "+");
snprintf(url, sizeof(url), url_tpl, escaped_place);
ret = request_json(url, parser_latlng, &ctx);
//curl_free(escaped_place);
free(escaped_place);
return ret;
}

View file

@ -32,10 +32,9 @@
struct ln_lnlat_posn;
#define GEONAMES_CACHE_SUPPORT 1
#define GEONAMES_CACHE_FILE ".geonames.cache" /* in users home dir */
#define GEONAMES_CACHE_FILE ".geonames.db" /* in users home dir */
int geonames_lookup(const char *place, struct ln_lnlat_posn *coords, char *name, int n);
int geonames_cache_db(int lookup, const char *place, struct ln_lnlat_posn *coords);
int geonames_parse(struct json_object *jobj, struct ln_lnlat_posn *result, char *name, int n);
int geonames_lookup_latlng(const char *place, struct ln_lnlat_posn *coords, char *name, size_t namelen);
int geonames_lookup_tz(struct ln_lnlat_posn coords, int *gmt_offset, char *tzid, size_t tzidlen);
#endif /* _GEONAMES_H_ */

View file

@ -32,21 +32,24 @@
#include "geonames.h"
int main(int argc, char *argv[]) {
int ret, gmt_offset;
struct ln_lnlat_posn res;
char *result_name, *name;
result_name = malloc(128);
if (result_name == NULL)
return -1;
char name[128], tzid[32];
if (argc != 2)
fprintf(stderr, "Usage: geonames LOCATION\n");
int ret = geonames_lookup(argv[1], &res, result_name, 32);
if (!ret)
printf("%s is at (%.4f, %.4f)\r\n", result_name, res.lat, res.lng);
free(result_name);
ret = geonames_lookup_latlng(argv[1], &res, name, sizeof(name));
if (ret) {
fprintf(stderr, "Error: Failed to lookup coordinates of %s\n", argv[1]);
return 1;
}
ret = geonames_lookup_tz(res, &gmt_offset, tzid, sizeof(tzid));
if (ret)
fprintf(stderr, "Error: Failed to lookup timezone for %.4f %.4f\n", res.lat, res.lng);
printf("%s is at %.4f, %.4f with timezone %s (GMT%+d)\r\n", name, res.lat, res.lng, tzid, gmt_offset);
return ret;
}