0.7.4: timezoneapi.io -> timezoned.rop.nl

also added EthernetShield example
pull/8/head 0.7.4
Rop 7 years ago
parent a2187f20c6
commit 4b1ffe5379

@ -12,6 +12,8 @@
 
> **Newsflash**: *To find the timezone information, ezTime originally used timezoneapi.io, which could be used for free for up to 50 queries a day. They have changed this policy and forced the use of https, both breaking ezTime. As of version 0.7.4, ezTime makes use of its very own online timezone lookup daemon, removing a dependency on some third party that might change their policy just like timezoneapi did. Please see details for [*`setLocation`*](#setlocation) because the interface changed a little. You can now also do GeoIP lookups for automatic local time setting (only in countries which do not span multiple timezones).*
## A brief history of ezTime
I was working on [M5ez](https://github.com/ropg/M5ez), an interface library to easily make cool-looking programs for the "[M5Stack](http://m5stack.com/)" ESP32 hardware. The status bar of M5ez needed to display the time. That was all, I swear. I figured I would use [Time](https://github.com/PaulStoffregen/Time), Michael Margolis' and Paul Stoffregen's library to do time things on Arduino. Then I needed to sync that to an NTP server, so I figured I would use [NTPclient](https://github.com/arduino-libraries/NTPClient), one of the existing NTP client libraries. And then I wanted it to show the local time, so I would need some way for the user to set an offset between UTC and local time.
@ -368,7 +370,11 @@ Provide the offset from UTC in minutes at the indicated time (or now if you do n
`boolsetLocation(String location = "")`    — **MUST** be prefixed with name of a timezone
With `setLocation` you can provide a string to do an internet lookup for a timezone. If the string contains a forward slash, the string is taken to be on Olsen timezone name, like `Europe/Berlin`. If it does not, it is parsed as a free form address, for which the system will try to find a timezone. You can enter "Paris" and get the info for "Europe/Paris", or enter "Paris, Texas" and get the timezone info for "America/Chicago", which is the Central Time timezone that Texas is in. After the information is retrieved, it is loaded in the current timezone, and cached if a cache is set (see below). `setLocation` will return `false` (Setting either `NO_NETWORK`, `CONNECT_FAILED` or `DATA_NOT_FOUND`) if it cannot find the information online.
With `setLocation` you can provide a string to do an internet lookup for a timezone. The string can either be an Olsen timezone name, like `Europe/Berlin` (case-sensitive). ([Here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) is a complete list of these names.) Or it can be a two-letter country code for any country that does not span multiple timezones, like `NL` or `DE` (but not `US`). After the information is retrieved, it is loaded in the current timezone, and cached if a cache is set (see below). `setLocation` will return `false` (Setting either `NO_NETWORK`, `DATA_NOT_FOUND` or `SERVER_ERROR`) if it cannot get timezone information.
If you provide no location ( `YourTZ.setLocation()` ), ezTime will attempt to do a GeoIP lookup fo find the country associated with your IP-address. If that is a country that has a single timezone, that timezone will be loaded, otherwise a `SERVER_ERROR` ("Country Spans Multiple Timezones") will result.
In the case of `SERVER_ERROR`, `errorString()` returns the error from the server, which might be "Country Spans Multiple Timezones", "Country Not Found", "GeoIP Lookup Failed" or "Timezone Not Found".
 

@ -0,0 +1,88 @@
/*
* Note: to use an ethernet shield, You must also set #define EZTIME_ETHERNET in $sketch_dir/libraries/ezTime/src/ezTime.h
*
* Also note that all ezTime examples can be used with an Ethernet shield if you just replace the beginning of the sketch
* with the beginning of this one.
*/
#include <ezTime.h>
#include <Ethernet.h>
// Enter a MAC address for your controller below. (Or use address below, just make sure it's unique on your network)
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
#define MAC_ADDRESS { 0xBA, 0xDB, 0xAD, 0xC0, 0xFF, 0xEE }
void setup() {
// Open serial communications and wait for port to open:
Serial.begin(115200);
while (!Serial) { ; } // wait for serial port to connect. Needed for native USB port only
Serial.println();
// You can use Ethernet.init(pin) to configure the CS pin
//Ethernet.init(10); // Most Arduino shields (default if unspecified)
//Ethernet.init(5); // MKR ETH shield
//Ethernet.init(0); // Teensy 2.0
//Ethernet.init(20); // Teensy++ 2.0
//Ethernet.init(15); // ESP8266 with Adafruit Featherwing Ethernet
//Ethernet.init(33); // ESP32 with Adafruit Featherwing Ethernet
Serial.print(F("Ethernet connection ... "));
byte mac [] = MAC_ADDRESS;
if (Ethernet.begin(mac) == 0) {
Serial.println(F("failed. (Reset to retry.)"));
while (true) { ; }; // Hang
} else {
Serial.print(F("got DHCP IP: "));
Serial.println(Ethernet.localIP());
}
// give the Ethernet shield a second to initialize:
delay(1000);
// OK, we're online... So the part above here is what you swap in before the waitForSync() in the other examples...
// Wait for ezTime to get its time synchronized
waitForSync();
Serial.println();
Serial.println("UTC: " + UTC.dateTime());
Timezone myTZ;
// Provide official timezone names
// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
myTZ.setLocation(F("Pacific/Auckland"));
Serial.print(F("New Zealand: "));
Serial.println(myTZ.dateTime());
// Or country codes for countries that do not span multiple timezones
myTZ.setLocation(F("de"));
Serial.print(F("Germany: "));
Serial.println(myTZ.dateTime());
// See if local time can be obtained (does not work in countries that span multiple timezones)
Serial.print(F("Local (GeoIP): "));
if (myTZ.setLocation()) {
Serial.println(myTZ.dateTime());
} else {
Serial.println(errorString());
}
Serial.println();
Serial.println(F("Now ezTime will show an NTP sync every 60 seconds"));
// Set NTP polling interval to 60 seconds. Way too often, but good for demonstration purposes.
setInterval(60);
// Make ezTime show us what it is doing
setDebug(INFO);
}
void loop() {
events();
}

@ -17,10 +17,24 @@ void setup() {
Timezone myTZ;
// Anything with a slash in it is interpreted as an official timezone name
// Provide official timezone names
// https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
myTZ.setLocation("Pacific/Auckland");
Serial.println("Auckland: " + myTZ.dateTime());
myTZ.setLocation(F("Pacific/Auckland"));
Serial.print(F("New Zealand: "));
Serial.println(myTZ.dateTime());
// Or country codes for countries that do not span multiple timezones
mtTZ.setLocation(F("de"));
Serial.print(F("Germany: "));
Serial.println(myTZ.dateTime());
// See if local time can be obtained (does not work in countries that span multiple timezones)
Serial.print(F("Local (GeoIP): "));
if (myTZ.setLocation()) {
Serial.println(myTZ.dateTime());
} else {
Serial.println(errorString());
}
}

@ -4,16 +4,16 @@
"keywords": "time date ntp timezone events milliseconds",
"authors": {
"name": "Rop Gonggrijp",
"url": "https://github.com/ropg"
"url": "https://github.com/ropg",
"maintainer": true
},
"repository": {
"type": "git",
"url": "https://github.com/ropg/ezTime"
},
"version": "0.7.3",
"version": "0.7.4",
"framework": "arduino",
"platforms": "*"
"platforms": "*",
"build": {
"libArchive": false
}

@ -1,5 +1,5 @@
name=ezTime
version=0.7.3
version=0.7.4
author=Rop Gonggrijp
maintainer=Rop Gonggrijp
sentence=ezTime - pronounced "Easy Time" - is a very easy to use Arduino time and date library that provides NTP network time lookups, extensive timezone support, formatted time and date strings, user events, millisecond precision and more.

@ -22,6 +22,7 @@
#include <EthernetUdp.h>
#else
#include <WiFi.h>
#include <WiFiUdp.h>
#endif
#endif
@ -62,6 +63,7 @@ const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts m
namespace {
ezError_t _last_error = NO_ERROR;
String _server_error = "";
ezDebugLevel_t _debug_level = NONE;
Print *_debug_device = (Print *)&Serial;
ezEvent_t _events[MAX_EVENTS];
@ -123,6 +125,7 @@ String errorString(const ezError_t err /* = LAST_ERROR */) {
case NO_CACHE_SET: return F("No cache set");
case CACHE_TOO_SMALL: return F("Cache too small");
case TOO_MANY_EVENTS: return F("Too many events");
case SERVER_ERROR: return _server_error;
default: return F("Unkown error");
}
}
@ -458,16 +461,14 @@ bool minuteChanged() {
info(F(" ... "));
#ifndef EZTIME_ETHERNET
#ifndef ARDUINO_SAMD_MKR1000
if (!WiFi.isConnected()) { error(NO_NETWORK); return false; }
#endif
if (WiFi.status() != WL_CONNECTED) { error(NO_NETWORK); return false; }
WiFiUDP udp;
#else
EthernetUDP udp;
#endif
udp.flush();
udp.begin(NTP_LOCAL_TIME_PORT);
udp.begin(NTP_LOCAL_PORT);
// Send NTP packet
byte buffer[NTP_PACKET_SIZE];
@ -526,11 +527,12 @@ bool minuteChanged() {
unsigned long start = millis();
#if !defined(EZTIME_ETHERNET) && !defined(ARDUINO_SAMD_MKR1000)
if (!WiFi.isConnected()) {
#if !defined(EZTIME_ETHERNET)
if (WiFi.status() != WL_CONNECTED) {
info(F("Waiting for WiFi ... "));
while (!WiFi.isConnected()) {
while (WiFi.status() != WL_CONNECTED) {
if ( timeout && (millis() - start) / 1000 > timeout ) { error(TIMEOUT); return false;};
events();
delay(25);
}
infoln(F("connected"));
@ -797,92 +799,65 @@ String Timezone::getPosix() { return _posix; }
#ifdef EZTIME_NETWORK_ENABLE
bool Timezone::setLocation(const String location /* = "" */) {
bool Timezone::setLocation(const String location /* = "GeoIP" */) {
info(F("Timezone lookup for: "));
infoln(location);
if (_locked_to_UTC) { error(LOCKED_TO_UTC); return false; }
#if !defined(EZTIME_ETHERNET) && !defined(ARDUINO_SAMD_MKR1000)
if (!WiFi.isConnected()) { error(NO_NETWORK); return false; }
#endif
String path;
if (location.indexOf("/") != -1) {
path = F("/api/timezone/?"); path += urlEncode(location);
} else if (location != "") {
path = F("/api/address/?"); path += urlEncode(location);
} else {
path = F("/api/ip");
}
#ifndef EZTIME_ETHERNET
WiFiClient client;
if (WiFi.status() != WL_CONNECTED) { error(NO_NETWORK); return false; }
WiFiUDP udp;
#else
EthernetClient client;
#endif
if (!client.connect("timezoneapi.io", 80)) { error(CONNECT_FAILED); return false; }
client.print(F("GET "));
client.print(path);
client.println(F(" HTTP/1.1"));
client.println(F("Host: timezoneapi.io"));
client.println(F("Connection: close"));
client.println();
client.setTimeout(3000);
EthernetUDP udp;
#endif
udp.flush();
udp.begin(TIMEZONED_LOCAL_PORT);
debug(F("Sent request for http://timezoneapi.io")); debugln(path);
debugln(F("Reply from server:\r\n"));
udp.beginPacket(TIMEZONED_REMOTE_HOST, TIMEZONED_REMOTE_PORT);
udp.write((const uint8_t*)location.c_str(), location.length());
udp.endPacket();
// This "JSON parser" (bwahaha!) fits in the small memory of the AVRs
String tzinfo = "";
String needle = "\"id\":\"";
uint8_t search_state = 0;
uint8_t char_found = 0;
uint32_t start = millis();
while ( search_state < 4 && millis() - start < TIMEZONEAPI_TIMEOUT) {
if (client.available()) {
char c = client.read();
debug(c);
if (c == needle.charAt(char_found)) {
char_found++;
if (char_found == needle.length()) {
search_state++;
c = 0;
}
} else {
char_found = 0;
}
if (search_state == 1 || search_state == 3) {
if (c == '"') {
search_state++;
if (search_state == 2) {
needle = "\"tz_string\":\"";
tzinfo += ' ';
}
} else if (c && c != '\\') {
tzinfo += c;
}
}
// Wait for packet or return false with timed out
unsigned long started = millis();
uint16_t packetsize = 0;
while (!udp.parsePacket()) {
delay (1);
if (millis() - started > TIMEZONED_TIMEOUT) {
udp.stop();
error(TIMEOUT);
udp.stop();
return false;
}
}
debugln(F("\r\n\r\n"));
if (search_state != 4 || tzinfo == "") { error(DATA_NOT_FOUND); return false; }
infoln(F("success."));
info(F("Found: ")); infoln(tzinfo);
_olsen = tzinfo.substring(0, tzinfo.indexOf(' '));
_posix = tzinfo.substring(tzinfo.indexOf(' ') + 1);
#if defined(EZTIME_CACHE_EEPROM) || defined(EZTIME_CACHE_NVS)
writeCache(tzinfo); // caution, byref to save memory, tzinfo mangled afterwards
#endif
return true;
// Stick result in String recv
String recv;
recv.reserve(60);
while (udp.available()) recv += (char)udp.read();
udp.stop();
if (recv.substring(0,6) == "ERROR ") {
_server_error = recv.substring(6);
error (SERVER_ERROR);
return false;
}
if (recv.substring(0,3) == "OK ") {
_olsen = recv.substring(3, recv.indexOf(" ", 4));
_posix = recv.substring(recv.indexOf(" ", 4) + 1);
infoln(F("success."));
info(F(" Olsen: ")); infoln(_olsen);
info(F(" Posix: ")); infoln(_posix);
#if defined(EZTIME_CACHE_EEPROM) || defined(EZTIME_CACHE_NVS)
String tzinfo = _olsen + " " + _posix;
writeCache(tzinfo); // caution, byref to save memory, tzinfo mangled afterwards
#endif
return true;
}
error (DATA_NOT_FOUND);
return false;
}
String Timezone::getOlsen() {
return _olsen;
}

@ -1,3 +1,5 @@
/* Extensive API documentation is at https://github.com/ropg/ezTime */
#ifndef _EZTIME_H_
#ifdef __cplusplus
#define _EZTIME_H_
@ -57,7 +59,8 @@ typedef enum {
LOCKED_TO_UTC,
NO_CACHE_SET,
CACHE_TOO_SMALL,
TOO_MANY_EVENTS
TOO_MANY_EVENTS,
SERVER_ERROR
} ezError_t;
typedef enum {
@ -135,14 +138,17 @@ typedef struct {
#define LAST_READ (int32_t)0x7FFFFFFE // (So yes, ezTime might malfunction two seconds before everything else...)
#define NTP_PACKET_SIZE 48
#define NTP_LOCAL_TIME_PORT 2342
#define NTP_LOCAL_PORT 4242
#define NTP_SERVER "pool.ntp.org"
#define NTP_TIMEOUT 1500 // milliseconds
#define NTP_INTERVAL 600 // default update interval in seconds
#define NTP_RETRY 5 // Retry after this many seconds on failed NTP
#define NTP_STALE_AFTER 3600 // If update due for this many seconds, set timeStatus to timeNeedsSync
#define TIMEZONEAPI_TIMEOUT 2000 // milliseconds
#define TIMEZONED_REMOTE_HOST "timezoned.rop.nl"
#define TIMEZONED_REMOTE_PORT 2342
#define TIMEZONED_LOCAL_PORT 2342
#define TIMEZONED_TIMEOUT 2000 // milliseconds
#define EEPROM_CACHE_LEN 50
#define MAX_CACHE_PAYLOAD ((EEPROM_CACHE_LEN - 3) / 3) * 4 + ( (EEPROM_CACHE_LEN - 3) % 3) // 2 bytes for len and date, then 4 to 3 (6-bit) compression on rest
@ -204,41 +210,41 @@ class Timezone {
String dateTime(const String format = DEFAULT_TIMEFORMAT);
String dateTime(time_t t, const String format = DEFAULT_TIMEFORMAT);
String dateTime(time_t t, const ezLocalOrUTC_t local_or_utc, const String format = DEFAULT_TIMEFORMAT);
uint8_t day(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // 1-31
uint16_t dayOfYear(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // days from start of year, jan 1st = 0
uint8_t day(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint16_t dayOfYear(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
int16_t getOffset(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
String getPosix();
String getTimezoneName(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint8_t hour(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // 0-23
uint8_t hourFormat12(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // 1-12
uint8_t hour(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint8_t hourFormat12(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
bool isAM(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
bool isDST(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
bool isPM(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
String militaryTZ(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint8_t minute(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // 0-59
uint8_t month(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // 1-12
uint16_t ms(time_t t = TIME_NOW); // 0-999
uint8_t minute(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint8_t month(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint16_t ms(time_t t = TIME_NOW);
time_t now();
uint8_t second(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // 0-59
uint8_t second(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
void setDefault();
uint8_t setEvent(void (*function)(), const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t mnth, uint16_t yr);
uint8_t setEvent(void (*function)(), time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
bool setPosix(const String posix);
void setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t mnth, uint16_t yr);
void setTime(const time_t t, const uint16_t ms = 0);
void setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t mnth, uint16_t yr);
time_t tzTime(time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
time_t tzTime(time_t t, ezLocalOrUTC_t local_or_utc, String &tzname, bool &is_dst, int16_t &offset);
uint8_t weekISO(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // ISO-8601 week number (weeks starting on Monday)
uint8_t weekday(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // Day of the week (1-7), Sunday is day 1
uint16_t year(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // four digit year
uint16_t yearISO(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); // ISO-8601 year, can differ from actual year, plus or minus one
uint8_t weekISO(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint8_t weekday(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint16_t year(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
uint16_t yearISO(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
private:
String _posix, _olsen;
bool _locked_to_UTC;
#ifdef EZTIME_NETWORK_ENABLE
public:
bool setLocation(const String location = "");
bool setLocation(const String location = "GeoIP");
String getOlsen();
#ifdef EZTIME_CACHE_EEPROM
public:

Loading…
Cancel
Save