BIG: introduced events, ezTime object now called time

pull/8/head
Rop 7 years ago
parent 133131b115
commit 635b40ce26

@ -1,10 +1,10 @@
# ezTime, an Arduino library for all of time <sup>*</sup> # ezTime, an Arduino library for all of time <sup>*</sup>
**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, millisecond precision and more.** **ezTime &mdash; pronounced "Easy Time" &mdash; 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.**
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<sup>* limitations may apply, see "2036 and 2038" chapter</sup></div> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<sup>* limitations may apply, see "2036 and 2038" chapter</sup>
<center><img src ="images/moving-clock.gif" /></center> <img src ="images/moving-clock.gif" />
## A brief history of ezTime ## A brief history of ezTime
@ -26,13 +26,15 @@ Overlooking the battlefield after implementing some part of this, it seemed like
**backwards compatible**: Anything written for the existing Arduino time library will still work. You can set which timezone the sketch should be in, or have it be in UTC which is the default. But you can also set and express time referring to multiple timezones, all very easy and intuitive. **backwards compatible**: Anything written for the existing Arduino time library will still work. You can set which timezone the sketch should be in, or have it be in UTC which is the default. But you can also set and express time referring to multiple timezones, all very easy and intuitive.
**eventful**: You can set events to have ezTime execute your own functions at a given time, and delete the events again if you change your mind.
**robust**: It doesn't fail if the timezone api goes away: it can use cached data, which ezTime can store in EEPROM (AVR Arduinos) or NVS (ESP32 through Preferences library). **robust**: It doesn't fail if the timezone api goes away: it can use cached data, which ezTime can store in EEPROM (AVR Arduinos) or NVS (ESP32 through Preferences library).
**informative**: No need to guess while you're working on something, ezTime can print messages to the serial port at your desired level of detail, telling you about the timezone's daylight savings info it receives or when it gets an NTP update and by how much your internal clock was off, for instance. **informative**: No need to guess while you're working on something, ezTime can print messages to the serial port at your desired level of detail, telling you about the timezone's daylight savings info it receives or when it gets an NTP update and by how much your internal clock was off, for instance.
**time-saving**: No more time spent on writing code to print date or time in some nicer way. Print things like "8:20 PM" or "Saturday the 23rd of August 2018" with ease. Prevent display-flicker with minuteChanged() and secondChanged() functions without storing any values to compare. **time-saving**: No more time spent on writing code to print date or time in some nicer way. Print things like "8:20 PM" or "Saturday the 23rd of August 2018" with ease. Prevent display-flicker with `minuteChanged()` and `secondChanged()` functions without storing any values to compare.
**small enough**: Works with all features and full debugging information on an Arduino Uno with an Ethernet Shield, leaving 2/3 of RAM but not too much flash for you to work with. (Even on a good day there isn't that much left if you want to do anything that involves networking.) Various `#define` options let you leave parts of it out if you want to make it smaller: you can even leave out the networking altogether if you have a different time source. **small enough**: Works with all features and full debugging information on an Arduino Uno with an Ethernet Shield, leaving 2/3 of RAM but not too much flash for you to work with. (Even on a good day there isn't that much left if you want to do anything that involves networking.) Various `#define` options let you leave parts of the library out if you want to make it smaller: you can even leave out the networking altogether if you have a different time source.
**easy to use**: Don't believe it until you see it. Have a look at some of these examples to see how easy it is to use. **easy to use**: Don't believe it until you see it. Have a look at some of these examples to see how easy it is to use.
@ -50,7 +52,7 @@ void setup() {
Serial.begin(115200); Serial.begin(115200);
WiFi.begin("your-ssid", "your-password"); WiFi.begin("your-ssid", "your-password");
ezTime.waitForSync(); time.waitForSync();
Serial.println("UTC: " + UTC.dateTime()); Serial.println("UTC: " + UTC.dateTime());
@ -126,8 +128,8 @@ Saturday, 25-Aug-18 14:32:53.303 UTC
``` ```
[...] [...]
ezTime.setInterval(60); time.setInterval(60);
ezTime.setDebugLevel(INFO); time.setDebugLevel(INFO);
} }
void loop() { void loop() {
@ -163,7 +165,7 @@ ezTime is an Arduino library. To start using it with the Arduino IDE:
* Click the row to select the library. * Click the row to select the library.
* Click the Install button to install the library. * Click the Install button to install the library.
in File -> Examples you will now see an ezTime heading down under "Examples from custom libraries". You can try running some of these examples to see if it all works. ezTime is made to be, as the name implies, quite easy to use. So you'll probably understand a lot of how things work from just looking at the examples. Now either just play with those and use the rest of this documentation only when you get stuck, or keep reading to see how things work in ezTime. in File -> Examples you will now see an ezTime heading down under "Examples from custom libraries". You can try running some of these examples to see if it all works. ezTime is made to be, as the name implies, quite easy to use. So you'll probably understand a lot of how things work from just looking at the examples. Now either just play with those and use the rest of this documentation only when you get stuck, or keep reading to see how things work in time.
# &nbsp; # &nbsp;
@ -204,13 +206,13 @@ It all starts when you include the library with `#include <ezTime.h>`. From tha
### No daemons here ### No daemons here
It is important to understand what ezTime does NOT do. It does not somehow create a background process that keeps time, contacts servers, or whatever. The Arduino does the timekeeping for us with its `millis()` counter, which keeps the time in milliseconds since the Arduino started. All ezTime does when it synchronizes time is to store a time (in seconds since 1970) and the position of the millis counter when that was. By seeing how much the millis counter has advanced and adding that starting point since 1970, ezTime tells time. But that internal clock isn't perfect, it may - very slowly - drift away from the actual time. So periodically when you ask it something, it will discover that it's time to re-synchronize its own clock with the NTP server, and then it'll go out, do that and take, say, 50 ms longer to respond back to your code. But it all happens only when you ask for it. It is important to understand what ezTime does NOT do. It does not somehow create a background process that keeps time, contacts servers, or whatever. The Arduino does the timekeeping for us with its `millis()` counter, which keeps the time in milliseconds since the Arduino started. All ezTime does when it synchronizes time is to store a time (in seconds since 1970) and the position of the millis counter when that was. By seeing how much the millis counter has advanced and adding that starting point since 1970, ezTime tells time. But that internal clock isn't perfect, it may &mdash; very slowly &mdash; drift away from the actual time. So periodically when you ask it something, it will discover that it's time to re-synchronize its own clock with the NTP server, and then it'll go out, do that and take, say, 50 ms longer to respond back to your code. But it all happens only when you ask for it.
### But I only just woke up ! ### But I only just woke up !
Your code might call `Serial.println(UTC.dateTime());` to print a complete textual representation of date and time in the default format to the serial port. The library would find out that time had not been synchronized yet, and it would send off an NTP request to one of the NTP servers that `pool.ntp.org` resolves to. If your Arduino has just woken up, it probably hasn't gotten its DHCP information, or is not connected to the WiFi network just yet. And so the time lookup would fail and the call to `.dateTime` would return a String with the date and time just after midnight on the 1st of January 1970: the zero-point for the unix-style time counter used by ezTime. It would later correct to the real time, but that's not pretty. Your code might call `Serial.println(UTC.dateTime());` to print a complete textual representation of date and time in the default format to the serial port. The library would find out that time had not been synchronized yet, and it would send off an NTP request to one of the NTP servers that `pool.ntp.org` resolves to. If your Arduino has just woken up, it probably hasn't gotten its DHCP information, or is not connected to the WiFi network just yet. And so the time lookup would fail and the call to `.dateTime` would return a String with the date and time just after midnight on the 1st of January 1970: the zero-point for the unix-style time counter used by ezTime. It would later correct to the real time, but that's not pretty.
Worse is when you set up a timezone for which you would like to retrieve the daylight savings rules from the server: it can't do that if the connection isn't up yet. So that's why there's a function called `ezTime.waitForSync` that simply requests the time until it is synchronized (or until a set number of seconds passes, see below). Worse is when you set up a timezone for which you would like to retrieve the daylight savings rules from the server: it can't do that if the connection isn't up yet. So that's why there's a function called `time.waitForSync` that simply requests the time until it is synchronized (or until a set number of seconds passes, see below).
&nbsp; &nbsp;
@ -220,11 +222,11 @@ The NTP request from the scenario above failed because the network wasn't up yet
&nbsp; &nbsp;
### ezTime.timeStatus ### time.timeStatus
`timeStatus_t ezTime.timeStatus();` `timeStatus_t time.timeStatus();`
Returns what state the clock is in. `ezTime.timeStatus()` will return one of: Returns what state the clock is in. `time.timeStatus()` will return one of:
| timeStatus | meaning | | timeStatus | meaning |
|----|----| |----|----|
@ -234,39 +236,39 @@ Returns what state the clock is in. `ezTime.timeStatus()` will return one of:
&nbsp; &nbsp;
### ezTime.waitForSync ### time.waitForSync
`bool ezTime.waitForSync(uint16_t timeout = 0);` `bool time.waitForSync(uint16_t timeout = 0);`
If your code uses timezones other than UTC, it might want to wait to initialise them until there is a valid time to see if the cached timezone definitions are still current. And if you are displaying a calendar or clock, it might look silly if it first says midnight on January 1st 1970 before showing the real time. `ezTime.waitForSync` will wait for the network to connect, and then for the time to be synchronized before returning `true`. If you specify a timeout (in seconds), it will return after that many seconds even if the clock is not in sync yet, returning `false`. (ezTime error `TIMEOUT`, see the chapter on error and debug messages further down) If your code uses timezones other than UTC, it might want to wait to initialise them until there is a valid time to see if the cached timezone definitions are still current. And if you are displaying a calendar or clock, it might look silly if it first says midnight on January 1st 1970 before showing the real time. `time.waitForSync` will wait for the network to connect, and then for the time to be synchronized before returning `true`. If you specify a timeout (in seconds), it will return after that many seconds even if the clock is not in sync yet, returning `false`. (ezTime error `TIMEOUT`, see the chapter on error and debug messages further down)
&nbsp; &nbsp;
### *ezTime.setServer and ezTime.setInterval* ### *time.setServer and time.setInterval*
`void ezTime.setServer(String ntp_server = NTP_SERVER);` `void time.setServer(String ntp_server = NTP_SERVER);`
`void ezTime.setInterval(uint16_t seconds = 0);` `void time.setInterval(uint16_t seconds = 0);`
By default, ezTime is set to poll `pool.ntp.org` every 10 minutes. These defaults should work for most people, but you can change them by specifying a new server with `ezTime.setServer` or a new interval (in seconds) with ezTime.setInterval. If you call setInterval with an interval of 0 seconds or call it as `ezTime.setInterval()`, no more NTP queries will be made. By default, ezTime is set to poll `pool.ntp.org` every 10 minutes. These defaults should work for most people, but you can change them by specifying a new server with `time.setServer` or a new interval (in seconds) with time.setInterval. If you call setInterval with an interval of 0 seconds or call it as `time.setInterval()`, no more NTP queries will be made.
&nbsp; &nbsp;
### *ezTime.updateNow* ### *time.updateNTP*
`void ezTime.updateNow();` `void time.updateNTP();`
Schedules the next update to happen immediately, and then tries to query the NTP server. If that fails, it will keep retrying when you ask for new time information, but not more often than once every 5 seconds. (Defined by `NTP_RETRY` in `ezTime.h`.) Updates the time from the NTP server immediately. Will keep retrying every 5 seconds (defined by `NTP_RETRY` in `ezTime.h`), will schedule the next update to happen after the normal interval.
&nbsp; &nbsp;
### *ezTime.queryNTP* ### *time.queryNTP*
`bool ezTime.queryNTP(String server, time_t &t, unsigned long &measured_at);` `bool time.queryNTP(String server, time_t &t, unsigned long &measured_at);`
This will send a single query to the NTP server your specify. It will put, in the `t` and `measured_at` variables passed by reference, the UTC unix-time and the `millis()` counter at the time the exact second happened. It does this by subtracting from `millis()` the fractional seconds received in the answer, as well as half the time it took to get an answer. This means it assumes the network delay was symmetrical, meaning it took just as long for the request to get to the server as for the answer to get back. This will send a single query to the NTP server your specify. It will put, in the `t` and `measured_at` variables passed by reference, the UTC unix-time and the `millis()` counter at the time the exact second happened. It does this by subtracting from `millis()` the fractional seconds received in the answer, as well as half the time it took to get an answer. This means it assumes the network delay was symmetrical, meaning it took just as long for the request to get to the server as for the answer to get back.
If the time server answers, `ezTime.queryNTP` returns `true`. If `false` is returned, `ezTime.error()` will return either `NO_NETWORK` (if the WiFi is not connected) or `TIMEOUT` if a response took more than 1500 milliseconds (defined by `NTP_TIMEOUT` in `ezTime.h`). If the time server answers, `time.queryNTP` returns `true`. If `false` is returned, `time.error()` will return either `NO_NETWORK` (if the WiFi is not connected) or `TIMEOUT` if a response took more than 1500 milliseconds (defined by `NTP_TIMEOUT` in `ezTime.h`).
Note that this function is used internally by ezTime, but does not by itself set the time ezTime keeps. You will likely never need to call this from your code. Note that this function is used internally by ezTime, but does not by itself set the time ezTime keeps. You will likely never need to call this from your code.
@ -302,7 +304,7 @@ Internally, ezTime stores everything it knows about a timezone as two strings. O
`void yourTZ.setDefault()` `void yourTZ.setDefault()`
`#include <ezTime.h>` includes the library, creates `ezTime` object and `UTC` instance of `Timezone` class, as well as `defaultTZ`, which is a reference to UTC unless you set it to another timzone by calling `yourTZ.setDefault()`. The functions in the root namespace like `hour()` and `minute()` - without a timezone in front - are interpreted as if passed to the default timezone. So if you have ezisting code, just setting up a timezone and making it the default should cause that code to work as if the time was set in local time. `#include <ezTime.h>` includes the library, creates `ezTime` object and `UTC` instance of `Timezone` class, as well as `defaultTZ`, which is a reference to UTC unless you set it to another timzone by calling `yourTZ.setDefault()`. The functions in the root namespace like `hour()` and `minute()` &mdash; without a timezone in front &mdash; are interpreted as if passed to the default timezone. So if you have ezisting code, just setting up a timezone and making it the default should cause that code to work as if the time was set in local time.
&nbsp; &nbsp;
@ -342,7 +344,7 @@ Tells you whether DST is in effect at a given time in this timezone. If you do n
`String getTimezoneName(TIME);` `String getTimezoneName(TIME);`
Provides the short code for the timezone, like `IST` for India, or `CET` (during standard time) or `CEST` (during Daylight Saving Time) for most of Europe. Provides the current short code for the timezone, like `IST` for India, or `CET` (during standard time) or `CEST` (during Daylight Saving Time) for most of Europe.
&nbsp; &nbsp;
@ -378,7 +380,7 @@ You can create a place for ezTime to store the data about the timezone. That way
### clearCache ### clearCache
`void ezTime.clearCache(bool delete_section = false);` `void time.clearCache(bool delete_section = false);`
## Getting date and time ## Getting date and time
@ -477,10 +479,19 @@ uint16_t yearISO(TIME);
``` ```
``` ```
bool yourTZ.secondChanged(); bool time.secondChanged();
bool yourTZ.minuteChanged(); bool time.minuteChanged();
``` ```
## Events
`uint8_t setEvent(void (*function)(), TIME)`
`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)`
`void time.deleteEvent(uint8_t event_handle)`
`void time.deleteEvent(void (*function)())`
## Setting date and time manually ## Setting date and time manually
@ -492,15 +503,15 @@ bool yourTZ.minuteChanged();
`void yourTZ.setTime(const uint8_t hr, const uint8_t min, const uint8_t sec,`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`const uint8_t day, const uint8_t mnth, uint16_t yr)` `void yourTZ.setTime(const uint8_t hr, const uint8_t min, const uint8_t sec,`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`const uint8_t day, const uint8_t mnth, uint16_t yr)`
`setTime` pretty much does what it says on the package: it sets the time to the time specified, aither as separate elements or as a time_t value in seconds since Jan 1st 1970. If you have another source of time - say, a GPS receiver - you can use `setTime` to set the time in the UTC timezone. Or you can set the local time in any other timezone you have set up and ezTime will set it's internal offset to the corresponding time in UTC so all timezones stay at the correct time. `setTime` pretty much does what it says on the package: it sets the time to the time specified, aither as separate elements or as a time_t value in seconds since Jan 1st 1970. If you have another source of time &mdash; say, a GPS receiver &mdash; you can use `setTime` to set the time in the UTC timezone. Or you can set the local time in any other timezone you have set up and ezTime will set it's internal offset to the corresponding time in UTC so all timezones stay at the correct time.
It's important to realise however that NTP updates will still become due and when they do time will be set to the time returned by the NTP server. If you do not want that, you can turn off NTP updates with `eztime.setInterval()`. If you do not use NTP updates at all and do not use the network lookups for timezone information either, you can compile ezTime with no network support by commenting out `#define EZTIME_NETWORK_ENABLE` in the `ezTime.h` file, creating a smaller library. It's important to realise however that NTP updates will still become due and when they do time will be set to the time returned by the NTP server. If you do not want that, you can turn off NTP updates with `eztime.setInterval()`. If you do not use NTP updates at all and do not use the network lookups for timezone information either, you can compile ezTime with no network support by commenting out `#define EZTIME_NETWORK_ENABLE` in the `ezTime.h` file, creating a smaller library.
## Working with time values ## Working with time values
### ezTime.breakTime ### time.breakTime
`void ezTime.breakTime(time_t time, tmElements_t &tm)` `void time.breakTime(time_t time, tmElements_t &tm)`
If you create a `tmElements_t` structure and pass it to `breakTime`, it will be filled with the various numeric elements of the time value specified. tmElements_t looks as follows: If you create a `tmElements_t` structure and pass it to `breakTime`, it will be filled with the various numeric elements of the time value specified. tmElements_t looks as follows:
@ -520,7 +531,7 @@ Meaning this code would print the hour:
``` ```
tmElements_t tm; tmElements_t tm;
ezTime.breakTime(UTC.now(), tm); time.breakTime(UTC.now(), tm);
Serial.print(tm.Hour); Serial.print(tm.Hour);
``` ```
@ -528,13 +539,13 @@ But `Serial.println(UTC.hour())` also works and is much simpler. `breakTime` is
&nbsp; &nbsp;
### ezTime.makeTime ### time.makeTime
`time_t ezTime.makeTime(tmElements_t &tm);` `time_t time.makeTime(tmElements_t &tm);`
This does the opposite of `ezTime.breakTime`: it takes a tmElements_t structure and turns it into a time_t value in seconds since Jan 1st 1970. This does the opposite of `time.breakTime`: it takes a tmElements_t structure and turns it into a time_t value in seconds since Jan 1st 1970.
`time_t ezTime.makeTime(uint8_t hour, uint8_t minute, uint8_t second,`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`uint8_t day, uint8_t month, int16_t year);` `time_t time.makeTime(uint8_t hour, uint8_t minute, uint8_t second,`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`uint8_t day, uint8_t month, int16_t year);`
This version takes the various numeric elements as arguments. Note that you can pass the year both as years since 1970 and as full four digit years. This version takes the various numeric elements as arguments. Note that you can pass the year both as years since 1970 and as full four digit years.
@ -542,12 +553,12 @@ This version takes the various numeric elements as arguments. Note that you can
### makeOrdinalTime ### makeOrdinalTime
`time_t ezTime.makeOrdinalTime(uint8_t hour, uint8_t minute, uint8_t second,`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`uint8_t ordinal, uint8_t wday, uint8_t month, int16_t year);` `time_t time.makeOrdinalTime(uint8_t hour, uint8_t minute, uint8_t second,`<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`uint8_t ordinal, uint8_t wday, uint8_t month, int16_t year);`
With `makeOrdinalTime` you can get the `time_t` value for a date written as "the second Tuesday in March". The `ordinal` value is 1 for first, 2 for second, 3 for third, 4 for fourth and either 5 or 0 for the last of that weekday in the month. `wday` is weekdays starting with Sunday as 1. You can use the names of ordinals, months and weekdays in all caps as they are compiler defines. So the following would find the `time_t` value for midnight at the start of the first Thursday of the year in variable `year`. With `makeOrdinalTime` you can get the `time_t` value for a date written as "the second Tuesday in March". The `ordinal` value is 1 for first, 2 for second, 3 for third, 4 for fourth and either 5 or 0 for the last of that weekday in the month. `wday` is weekdays starting with Sunday as 1. You can use the names of ordinals, months and weekdays in all caps as they are compiler defines. So the following would find the `time_t` value for midnight at the start of the first Thursday of the year in variable `year`.
``` ```
ezTime.makeOrdinalTime(0, 0, 0, FIRST, THURSDAY, JANUARY, year) time.makeOrdinalTime(0, 0, 0, FIRST, THURSDAY, JANUARY, year)
``` ```
> *This is actually a fragment of ezTime's own code, as it can print ISO week numbers and the first ISO week in a year is defined as the week that has the first Thursday in it.* > *This is actually a fragment of ezTime's own code, as it can print ISO week numbers and the first ISO week in a year is defined as the week that has the first Thursday in it.*
@ -556,7 +567,7 @@ ezTime.makeOrdinalTime(0, 0, 0, FIRST, THURSDAY, JANUARY, year)
### compileTime ### compileTime
`time_t ezTime.compileTime(String compile_date = __DATE__, String compile_time = __TIME__);` `time_t time.compileTime(String compile_date = __DATE__, String compile_time = __TIME__);`
&nbsp; &nbsp;
@ -581,9 +592,9 @@ In this second form you have to supply all arguments, and it will fill your `tzn
## Errors and debug information ## Errors and debug information
### ezTime.debugLevel ### time.debugLevel
`void ezTime.debugLevel(ezDebugLevel_t level);` `void time.debugLevel(ezDebugLevel_t level);`
Sets the level of detail at which ezTime outputs messages on the serial port. Can be set to one of: Sets the level of detail at which ezTime outputs messages on the serial port. Can be set to one of:
@ -598,19 +609,19 @@ Sets the level of detail at which ezTime outputs messages on the serial port. Ca
&nbsp; &nbsp;
### ezTime.error ### time.error
`ezError_t ezTime.error();` `ezError_t time.error();`
A number of functions in ezTime are booleans, meaning they return `true` or `false` as their return value, where `false` means some error ocurred. `ezTime.error` will return an `ezError_t` enumeration, something like `NO_NETWORK` (obvious) or `LOCKED_TO_UTC` (when you try to load some new timezone info to the UTC object). You can test for these specific errors and this document will mention which errors night happen in what functions. A number of functions in ezTime are booleans, meaning they return `true` or `false` as their return value, where `false` means some error ocurred. `time.error` will return an `ezError_t` enumeration, something like `NO_NETWORK` (obvious) or `LOCKED_TO_UTC` (when you try to load some new timezone info to the UTC object). You can test for these specific errors and this document will mention which errors might happen in what functions.
When you call `error()`, it will also reset the error, so you can clear the last error stored. When you call `error()`, it will also reset the error, so you can clear the last error stored.
&nbsp; &nbsp;
### ezTime.errorString ### time.errorString
`String ezTime.errorString(ezError_t err = LAST_ERROR);` `String time.errorString(ezError_t err = LAST_ERROR);`
This will give you a string representation of the error specified. The pseudo-error `LAST_ERROR`, which is the default, will give you the textual representation of the last error. This will not reset the last error stored. This will give you a string representation of the error specified. The pseudo-error `LAST_ERROR`, which is the default, will give you the textual representation of the last error. This will not reset the last error stored.

@ -24,7 +24,7 @@ void setup() {
Serial.println(); Serial.println();
local.setPosix(LOCALTZ_POSIX); local.setPosix(LOCALTZ_POSIX);
local.setTime(ezTime.compileTime()); local.setTime(time.compileTime());
Serial.println("Local time : " + local.dateTime()); Serial.println("Local time : " + local.dateTime());
berlin.setPosix("CET-1CEST,M3.4.0/2,M10.4.0/3"); berlin.setPosix("CET-1CEST,M3.4.0/2,M10.4.0/3");

@ -6,7 +6,7 @@ void setup() {
Serial.begin(115200); Serial.begin(115200);
WiFi.begin("your-ssid", "your-password"); WiFi.begin("your-ssid", "your-password");
ezTime.waitForSync(); time.waitForSync();
Serial.println(); Serial.println();
Serial.println("Time in various internet standard formats ..."); Serial.println("Time in various internet standard formats ...");

@ -9,7 +9,7 @@ void setup() {
// Uncomment the line below to see what it does behind the scenes // Uncomment the line below to see what it does behind the scenes
// ezTime.debugLevel(INFO); // ezTime.debugLevel(INFO);
ezTime.waitForSync(); time.waitForSync();
Serial.println(); Serial.println();
Serial.println("UTC: " + UTC.dateTime()); Serial.println("UTC: " + UTC.dateTime());

@ -6,8 +6,8 @@ void setup() {
Serial.begin(115200); Serial.begin(115200);
WiFi.begin("your-ssid", "your-password"); WiFi.begin("your-ssid", "your-password");
ezTime.setInterval(60); time.setInterval(60);
ezTime.waitForSync(); time.waitForSync();
Serial.println(); Serial.println();

@ -1,11 +1,13 @@
# ezTime object # ezTime object
ezTime KEYWORD3 time KEYWORD3
error KEYWORD2 error KEYWORD2
errorString KEYWORD2 errorString KEYWORD2
debugLevel KEYWORD2 debugLevel KEYWORD2
timeStatus KEYWORD2 timeStatus KEYWORD2
now KEYWORD2 now KEYWORD2
events KEYWORD2
deleteEvent KEYWORD2
breakTime KEYWORD2 breakTime KEYWORD2
makeTime KEYWORD2 makeTime KEYWORD2
makeUmpteenthTime KEYWORD2 makeUmpteenthTime KEYWORD2
@ -15,7 +17,7 @@ dayString KEYWORD2
secondChanged KEYWORD2 secondChanged KEYWORD2
minuteChanged KEYWORD2 minuteChanged KEYWORD2
queryNTP KEYWORD2 queryNTP KEYWORD2
updateNow KEYWORD2 updateNTP KEYWORD2
setServer KEYWORD2 setServer KEYWORD2
setInterval KEYWORD2 setInterval KEYWORD2
waitForSync KEYWORD2 waitForSync KEYWORD2
@ -30,6 +32,7 @@ DefaultTZ KEYWORD3
setPosix KEYWORD2 setPosix KEYWORD2
getPosix KEYWORD2 getPosix KEYWORD2
tzTime KEYWORD2 tzTime KEYWORD2
setEvent KEYWORD2
setDefault KEYWORD2 setDefault KEYWORD2
isDST KEYWORD2 isDST KEYWORD2
getTimezoneName KEYWORD2 getTimezoneName KEYWORD2
@ -155,4 +158,3 @@ SECOND LITERAL1
THIRD LITERAL1 THIRD LITERAL1
FOURTH LITERAL1 FOURTH LITERAL1
LAST LITERAL1 LAST LITERAL1

@ -32,18 +32,18 @@ time_t EZtime::_last_read_t = 0;
uint32_t EZtime::_last_sync_millis = 0; uint32_t EZtime::_last_sync_millis = 0;
uint16_t EZtime::_last_read_ms; uint16_t EZtime::_last_read_ms;
timeStatus_t EZtime::_time_status = timeNotSet; timeStatus_t EZtime::_time_status = timeNotSet;
ezEvent_t EZtime::_events[MAX_EVENTS];
bool EZtime::_initialised = false;
#ifdef EZTIME_NETWORK_ENABLE #ifdef EZTIME_NETWORK_ENABLE
bool EZtime::_ntp_enabled = true; bool EZtime::_ntp_enabled = true;
uint32_t EZtime::_update_due = 0; uint16_t EZtime::_ntp_interval = NTP_INTERVAL;
uint16_t EZtime::_update_backoff = 0; // seconds to add to _update_due to retry NTP
uint16_t EZtime::_update_interval = NTP_INTERVAL;
String EZtime::_ntp_server = NTP_SERVER; String EZtime::_ntp_server = NTP_SERVER;
#endif #endif
EZtime::EZtime() { EZtime::EZtime() {
for (uint8_t n = 0; n < MAX_EVENTS; n++) EZtime::_events[n] = { 0, NULL };
} }
@ -60,6 +60,7 @@ String EZtime::errorString(ezError_t err /* = LAST_ERROR */) {
case LOCKED_TO_UTC: return F("Locked to UTC"); case LOCKED_TO_UTC: return F("Locked to UTC");
case NO_CACHE_SET: return F("No cache set"); case NO_CACHE_SET: return F("No cache set");
case CACHE_TOO_SMALL: return F("Cache too small"); case CACHE_TOO_SMALL: return F("Cache too small");
case TOO_MANY_EVENTS: return F("Too many events");
default: return F("Unkown error"); default: return F("Unkown error");
} }
} }
@ -130,9 +131,6 @@ String EZtime::dayString(uint8_t day) {
timeStatus_t EZtime::timeStatus() { return _time_status; } timeStatus_t EZtime::timeStatus() { return _time_status; }
time_t EZtime::now(bool update_last_read /* = true */) { time_t EZtime::now(bool update_last_read /* = true */) {
#ifdef EZTIME_NETWORK_ENABLE
updateIfNeeded();
#endif
time_t t; time_t t;
uint32_t m = millis(); uint32_t m = millis();
t = _last_sync_time + ((m - _last_sync_millis) / 1000); t = _last_sync_time + ((m - _last_sync_millis) / 1000);
@ -143,6 +141,39 @@ time_t EZtime::now(bool update_last_read /* = true */) {
return t; return t;
} }
void EZtime::events() {
#ifdef EZTIME_NETWORK_ENABLE
if (!_initialised) {
// Start the cycle of updateNTP running and then setting an event for its next run
updateNTP();
_initialised = true;
}
#endif
// See if any events are due
for (uint8_t n = 0; n < MAX_EVENTS; n++) {
if (_events[n].function && now() >= _events[n].time) {
debug(F("Running event (#")); debug(n + 1); debug(F(") set for ")); debugln(UTC.dateTime(_events[n].time));
void (*tmp)() = _events[n].function;
_events[n] = { 0, NULL }; // reset the event
(tmp)(); // execute the function
}
}
}
void EZtime::deleteEvent(uint8_t event_handle) {
debug(F("Deleted event (#")); debug(event_handle); debug(F("), set for ")); debugln(UTC.dateTime(_events[event_handle - 1].time));
_events[event_handle - 1] = { 0, NULL };
}
void EZtime::deleteEvent(void (*function)()) {
for (uint8_t n = 0; n< MAX_EVENTS; n++) {
if (_events[n].function == function) {
debug(F("Deleted event (#")); debug(n + 1); debug(F("), set for ")); debugln(UTC.dateTime(_events[n].time));
_events[n] = { 0, NULL };
}
}
}
void EZtime::breakTime(time_t timeInput, tmElements_t &tm){ void EZtime::breakTime(time_t timeInput, tmElements_t &tm){
// break the given time_t into time components // break the given time_t into time components
// this is a more compact version of the C library localtime function // this is a more compact version of the C library localtime function
@ -338,9 +369,8 @@ bool EZtime::minuteChanged() {
#ifdef EZTIME_NETWORK_ENABLE #ifdef EZTIME_NETWORK_ENABLE
void EZtime::updateIfNeeded() { void EZtime::updateNTP() {
if (_update_interval && (millis() >= _update_due + (_update_backoff * 1000)) ) { time.deleteEvent(updateNTP); // Delete any events pointing here, in case called manually
if (millis() - _update_due > NTP_STALE_AFTER * 1000) _time_status = timeNeedsSync; // If unable to sync for an hour, timeStatus = timeNeedsSync
time_t t; time_t t;
unsigned long measured_at; unsigned long measured_at;
if (queryNTP(_ntp_server, t, measured_at)) { if (queryNTP(_ntp_server, t, measured_at)) {
@ -365,12 +395,10 @@ bool EZtime::minuteChanged() {
} else { } else {
infoln(""); infoln("");
} }
_update_due = millis() + (uint32_t)_update_interval * 1000; if (_ntp_interval) UTC.setEvent(updateNTP, t + _ntp_interval);
_update_backoff = 0;
_time_status = timeSet; _time_status = timeSet;
} else { } else {
_update_backoff += NTP_RETRY; UTC.setEvent(updateNTP, now() + NTP_RETRY);
}
} }
} }
@ -437,12 +465,14 @@ bool EZtime::minuteChanged() {
return true; return true;
} }
void EZtime::setInterval(uint16_t seconds /* = 0 */) { _update_interval = seconds; } void EZtime::setInterval(uint16_t seconds /* = 0 */) {
deleteEvent(updateNTP);
_ntp_interval = seconds;
if (seconds) UTC.setEvent(updateNTP, now() + _ntp_interval);
}
void EZtime::setServer(String ntp_server /* = NTP_SERVER */) { _ntp_server = ntp_server; } void EZtime::setServer(String ntp_server /* = NTP_SERVER */) { _ntp_server = ntp_server; }
void EZtime::updateNow() { _update_due = millis(); now(); }
bool EZtime::waitForSync(uint16_t timeout /* = 0 */) { bool EZtime::waitForSync(uint16_t timeout /* = 0 */) {
unsigned long start = millis(); unsigned long start = millis();
@ -463,7 +493,7 @@ bool EZtime::minuteChanged() {
while (_time_status != timeSet) { while (_time_status != timeSet) {
if ( timeout && (millis() - start) / 1000 > timeout ) { error(TIMEOUT); return false;}; if ( timeout && (millis() - start) / 1000 > timeout ) { error(TIMEOUT); return false;};
delay(250); delay(250);
now(); events();
} }
infoln(F("Time is in sync")); infoln(F("Time is in sync"));
} }
@ -473,7 +503,7 @@ bool EZtime::minuteChanged() {
#endif // EZTIME_NETWORK_ENABLE #endif // EZTIME_NETWORK_ENABLE
EZtime ezTime; EZtime time;
@ -499,7 +529,7 @@ Timezone::Timezone(bool locked_to_UTC /* = false */) {
} }
bool Timezone::setPosix(String posix) { bool Timezone::setPosix(String posix) {
if (_locked_to_UTC) { ezTime.error(LOCKED_TO_UTC); return false; } if (_locked_to_UTC) { time.error(LOCKED_TO_UTC); return false; }
_posix = posix; _posix = posix;
_olsen = ""; _olsen = "";
} }
@ -514,10 +544,10 @@ time_t Timezone::tzTime(time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL
time_t Timezone::tzTime(time_t t, ezLocalOrUTC_t local_or_utc, String &tzname, bool &is_dst, int16_t &offset) { time_t Timezone::tzTime(time_t t, ezLocalOrUTC_t local_or_utc, String &tzname, bool &is_dst, int16_t &offset) {
if (t == TIME_NOW) { if (t == TIME_NOW) {
t = ezTime.now(); t = time.now();
local_or_utc = UTC_TIME; local_or_utc = UTC_TIME;
} else if (t == LAST_READ) { } else if (t == LAST_READ) {
t = ezTime._last_read_t; t = time._last_read_t;
local_or_utc = UTC_TIME; local_or_utc = UTC_TIME;
} }
@ -680,11 +710,11 @@ time_t Timezone::tzTime(time_t t, ezLocalOrUTC_t local_or_utc, String &tzname, b
int16_t dst_offset = std_offset - dst_shift_hr * 60 - dst_shift_min; int16_t dst_offset = std_offset - dst_shift_hr * 60 - dst_shift_min;
// to find the year // to find the year
tmElements_t tm; tmElements_t tm;
ezTime.breakTime(t, tm); time.breakTime(t, tm);
// in local time // in local time
time_t dst_start = ezTime.makeOrdinalTime(start_time_hr, start_time_min, 0, start_week, start_dow, start_month, tm.Year + 1970); time_t dst_start = time.makeOrdinalTime(start_time_hr, start_time_min, 0, start_week, start_dow, start_month, tm.Year + 1970);
time_t dst_end = ezTime.makeOrdinalTime(end_time_hr, end_time_min, 0, end_week, end_dow, end_month, tm.Year + 1970); time_t dst_end = time.makeOrdinalTime(end_time_hr, end_time_min, 0, end_week, end_dow, end_month, tm.Year + 1970);
if (local_or_utc == UTC_TIME) { if (local_or_utc == UTC_TIME) {
dst_start -= std_offset; dst_start -= std_offset;
@ -719,17 +749,17 @@ String Timezone::getPosix() { return _posix; }
info(F("Timezone lookup for: ")); info(F("Timezone lookup for: "));
infoln(location); infoln(location);
if (_locked_to_UTC) { ezTime.error(LOCKED_TO_UTC); return false; } if (_locked_to_UTC) { time.error(LOCKED_TO_UTC); return false; }
#ifndef EZTIME_ETHERNET #ifndef EZTIME_ETHERNET
if (!WiFi.isConnected()) { ezTime.error(NO_NETWORK); return false; } if (!WiFi.isConnected()) { time.error(NO_NETWORK); return false; }
#endif #endif
String path; String path;
if (location.indexOf("/") != -1) { if (location.indexOf("/") != -1) {
path = F("/api/timezone/?"); path += ezTime.urlEncode(location); path = F("/api/timezone/?"); path += time.urlEncode(location);
} else if (location != "") { } else if (location != "") {
path = F("/api/address/?"); path += ezTime.urlEncode(location); path = F("/api/address/?"); path += time.urlEncode(location);
} else { } else {
path = F("/api/ip"); path = F("/api/ip");
} }
@ -740,7 +770,7 @@ String Timezone::getPosix() { return _posix; }
EthernetClient client; EthernetClient client;
#endif #endif
if (!client.connect("timezoneapi.io", 80)) { ezTime.error(CONNECT_FAILED); return false; } if (!client.connect("timezoneapi.io", 80)) { time.error(CONNECT_FAILED); return false; }
client.print(F("GET ")); client.print(F("GET "));
client.print(path); client.print(path);
@ -786,7 +816,7 @@ String Timezone::getPosix() { return _posix; }
} }
} }
debugln(F("\r\n\r\n")); debugln(F("\r\n\r\n"));
if (search_state != 4 || tzinfo == "") { ezTime.error(DATA_NOT_FOUND); return false; } if (search_state != 4 || tzinfo == "") { time.error(DATA_NOT_FOUND); return false; }
infoln(F("success.")); infoln(F("success."));
info(F("Found: ")); infoln(tzinfo); info(F("Found: ")); infoln(tzinfo);
@ -801,7 +831,7 @@ String Timezone::getPosix() { return _posix; }
#ifdef EZTIME_CACHE_EEPROM #ifdef EZTIME_CACHE_EEPROM
bool Timezone::setCache(const int16_t address) { bool Timezone::setCache(const int16_t address) {
if (address + EEPROM_CACHE_LEN > EEPROM.length()) { ezTime.error(CACHE_TOO_SMALL); return false; } if (address + EEPROM_CACHE_LEN > EEPROM.length()) { time.error(CACHE_TOO_SMALL); return false; }
_eeprom_address = address; _eeprom_address = address;
return setCache(); return setCache();
} }
@ -835,12 +865,12 @@ String Timezone::getPosix() { return _posix; }
void Timezone::clearCache(bool delete_section /* = false */) { void Timezone::clearCache(bool delete_section /* = false */) {
#ifdef EZTIME_CACHE_EEPROM #ifdef EZTIME_CACHE_EEPROM
if (_eeprom_address < 0) { ezTime.error(NO_CACHE_SET); return; } if (_eeprom_address < 0) { time.error(NO_CACHE_SET); return; }
for (int16_t n = _eeprom_address; n < _eeprom_address + EEPROM_CACHE_LEN; n++) EEPROM.write(n, 0); for (int16_t n = _eeprom_address; n < _eeprom_address + EEPROM_CACHE_LEN; n++) EEPROM.write(n, 0);
#endif #endif
#ifdef EZTIME_CACHE_NVS #ifdef EZTIME_CACHE_NVS
if (_nvs_name = "" || _nvs_key = "") { ezTime.error(NO_CACHE_SET); return; } if (_nvs_name = "" || _nvs_key = "") { time.error(NO_CACHE_SET); return; }
Preferences prefs; Preferences prefs;
prefs.begin(_nvs_name, false); prefs.begin(_nvs_name, false);
if (delete_section) { if (delete_section) {
@ -863,7 +893,7 @@ String Timezone::getPosix() { return _posix; }
#ifdef EZTIME_CACHE_EEPROM #ifdef EZTIME_CACHE_EEPROM
if (_eeprom_address < 0) return false; if (_eeprom_address < 0) return false;
info(F("Caching timezone data ")); info(F("Caching timezone data "));
if (str.length() > MAX_CACHE_PAYLOAD) { ezTime.error(CACHE_TOO_SMALL); return false; } if (str.length() > MAX_CACHE_PAYLOAD) { time.error(CACHE_TOO_SMALL); return false; }
uint16_t last_byte = _eeprom_address + EEPROM_CACHE_LEN - 1; uint16_t last_byte = _eeprom_address + EEPROM_CACHE_LEN - 1;
uint16_t addr = _eeprom_address; uint16_t addr = _eeprom_address;
@ -930,7 +960,7 @@ String Timezone::getPosix() { return _posix; }
bool Timezone::readCache(String &olsen, String &posix, uint8_t &months_since_jan_2018) { bool Timezone::readCache(String &olsen, String &posix, uint8_t &months_since_jan_2018) {
#ifdef EZTIME_CACHE_EEPROM #ifdef EZTIME_CACHE_EEPROM
if (_eeprom_address < 0) { ezTime.error(NO_CACHE_SET); return false; } if (_eeprom_address < 0) { time.error(NO_CACHE_SET); return false; }
uint16_t last_byte = _eeprom_address + EEPROM_CACHE_LEN - 1; uint16_t last_byte = _eeprom_address + EEPROM_CACHE_LEN - 1;
@ -994,7 +1024,7 @@ String Timezone::getPosix() { return _posix; }
#endif #endif
#ifdef EZTIME_CACHE_NVS #ifdef EZTIME_CACHE_NVS
if (_nvs_name = "" || _nvs_key = "") { ezTime.error(NO_CACHE_SET); return; } if (_nvs_name = "" || _nvs_key = "") { time.error(NO_CACHE_SET); return; }
Preferences prefs; Preferences prefs;
prefs.begin(_nvs_name, true); prefs.begin(_nvs_name, true);
@ -1046,16 +1076,31 @@ int16_t Timezone::getOffset(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_ut
return offset; return offset;
} }
time_t Timezone::now() { uint8_t Timezone::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) {
return tzTime(); time_t t = time.makeTime(hr, min, sec, day, mnth, yr);
return setEvent(function, t);
}
uint8_t Timezone::setEvent(void (*function)(), time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL_TIME) {
t = tzTime(t, local_or_utc);
for (uint8_t n = 0; n < MAX_EVENTS; n++) {
if (!time._events[n].function) {
time._events[n].function = function;
time._events[n].time = t;
debug(F("Set event (#")); debug(n + 1); debug(F(") to trigger on: ")); debugln(UTC.dateTime(t));
return n + 1;
}
}
time.error(TOO_MANY_EVENTS);
return 0;
} }
void Timezone::setTime(time_t t, uint16_t ms /* = 0 */) { void Timezone::setTime(time_t t, uint16_t ms /* = 0 */) {
int16_t offset; int16_t offset;
offset = getOffset(t); offset = getOffset(t);
ezTime._last_sync_time = t + offset * 60; time._last_sync_time = t + offset * 60;
ezTime._last_sync_millis = millis() - ms; time._last_sync_millis = millis() - ms;
ezTime._time_status = timeSet; time._time_status = timeSet;
} }
void Timezone::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 Timezone::setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t mnth, uint16_t yr) {
@ -1073,7 +1118,7 @@ void Timezone::setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, c
tm.Hour = hr; tm.Hour = hr;
tm.Minute = min; tm.Minute = min;
tm.Second = sec; tm.Second = sec;
setTime(ezTime.makeTime(tm)); setTime(time.makeTime(tm));
} }
String Timezone::dateTime(String format /* = DEFAULT_TIMEFORMAT */) { String Timezone::dateTime(String format /* = DEFAULT_TIMEFORMAT */) {
@ -1096,7 +1141,7 @@ String Timezone::dateTime(time_t t, ezLocalOrUTC_t local_or_utc, String format /
String out = ""; String out = "";
tmElements_t tm; tmElements_t tm;
ezTime.breakTime(t, tm); time.breakTime(t, tm);
int8_t hour12 = tm.Hour % 12; int8_t hour12 = tm.Hour % 12;
if (hour12 == 0) hour12 = 12; if (hour12 == 0) hour12 = 12;
@ -1121,16 +1166,16 @@ String Timezone::dateTime(time_t t, ezLocalOrUTC_t local_or_utc, String format /
escape_char = true; escape_char = true;
break; break;
case 'd': // Day of the month, 2 digits with leading zeros case 'd': // Day of the month, 2 digits with leading zeros
out += ezTime.zeropad(tm.Day, 2); out += time.zeropad(tm.Day, 2);
break; break;
case 'D': // A textual representation of a day, three letters case 'D': // A textual representation of a day, three letters
out += ezTime.dayString(tm.Wday).substring(0,3); out += time.dayString(tm.Wday).substring(0,3);
break; break;
case 'j': // Day of the month without leading zeros case 'j': // Day of the month without leading zeros
out += String(tm.Day); out += String(tm.Day);
break; break;
case 'l': // (lowercase L) A full textual representation of the day of the week case 'l': // (lowercase L) A full textual representation of the day of the week
out += ezTime.dayString(tm.Wday); out += time.dayString(tm.Wday);
break; break;
case 'N': // ISO-8601 numeric representation of the day of the week. ( 1 = Monday, 7 = Sunday ) case 'N': // ISO-8601 numeric representation of the day of the week. ( 1 = Monday, 7 = Sunday )
tmpint8 = tm.Wday - 1; tmpint8 = tm.Wday - 1;
@ -1157,13 +1202,13 @@ String Timezone::dateTime(time_t t, ezLocalOrUTC_t local_or_utc, String format /
out += String(tm.Wday); out += String(tm.Wday);
break; break;
case 'F': // A full textual representation of a month, such as January or March case 'F': // A full textual representation of a month, such as January or March
out += ezTime.monthString(tm.Month); out += time.monthString(tm.Month);
break; break;
case 'm': // Numeric representation of a month, with leading zeros case 'm': // Numeric representation of a month, with leading zeros
out += ezTime.zeropad(tm.Month, 2); out += time.zeropad(tm.Month, 2);
break; break;
case 'M': // A short textual representation of a month, three letters case 'M': // A short textual representation of a month, three letters
out += ezTime.monthString(tm.Month).substring(0,3); out += time.monthString(tm.Month).substring(0,3);
break; break;
case 'n': // Numeric representation of a month, without leading zeros case 'n': // Numeric representation of a month, without leading zeros
out += String(tm.Month); out += String(tm.Month);
@ -1175,7 +1220,7 @@ String Timezone::dateTime(time_t t, ezLocalOrUTC_t local_or_utc, String format /
out += String(tm.Year + 1970); out += String(tm.Year + 1970);
break; break;
case 'y': // A two digit representation of a year case 'y': // A two digit representation of a year
out += ezTime.zeropad((tm.Year + 1970) % 100, 2); out += time.zeropad((tm.Year + 1970) % 100, 2);
break; break;
case 'a': // am or pm case 'a': // am or pm
out += (tm.Hour < 12) ? F("am") : F("pm"); out += (tm.Hour < 12) ? F("am") : F("pm");
@ -1190,22 +1235,22 @@ String Timezone::dateTime(time_t t, ezLocalOrUTC_t local_or_utc, String format /
out += String(tm.Hour); out += String(tm.Hour);
break; break;
case 'h': // 12-hour format of an hour with leading zeros case 'h': // 12-hour format of an hour with leading zeros
out += ezTime.zeropad(hour12, 2); out += time.zeropad(hour12, 2);
break; break;
case 'H': // 24-hour format of an hour with leading zeros case 'H': // 24-hour format of an hour with leading zeros
out += ezTime.zeropad(tm.Hour, 2); out += time.zeropad(tm.Hour, 2);
break; break;
case 'i': // Minutes with leading zeros case 'i': // Minutes with leading zeros
out += ezTime.zeropad(tm.Minute, 2); out += time.zeropad(tm.Minute, 2);
break; break;
case 's': // Seconds with leading zeros case 's': // Seconds with leading zeros
out += ezTime.zeropad(tm.Second, 2); out += time.zeropad(tm.Second, 2);
break; break;
case 'T': // abbreviation for timezone case 'T': // abbreviation for timezone
out += tzname; out += tzname;
break; break;
case 'v': // milliseconds as three digits case 'v': // milliseconds as three digits
out += ezTime.zeropad(ezTime._last_read_ms, 3); out += time.zeropad(time._last_read_ms, 3);
break; break;
case 'e': // Timezone identifier (Olsen) case 'e': // Timezone identifier (Olsen)
out += getOlsen(); out += getOlsen();
@ -1215,9 +1260,9 @@ String Timezone::dateTime(time_t t, ezLocalOrUTC_t local_or_utc, String format /
o = offset; o = offset;
out += (o < 0) ? "+" : "-"; // reversed from our offset out += (o < 0) ? "+" : "-"; // reversed from our offset
if (o < 0) o = 0 - o; if (o < 0) o = 0 - o;
out += ezTime.zeropad(o / 60, 2); out += time.zeropad(o / 60, 2);
out += (c == 'P') ? ":" : ""; out += (c == 'P') ? ":" : "";
out += ezTime.zeropad(o % 60, 2); out += time.zeropad(o % 60, 2);
break; break;
case 'Z': //Timezone offset in seconds. West of UTC is negative, east of UTC is positive. case 'Z': //Timezone offset in seconds. West of UTC is negative, east of UTC is positive.
out += String(0 - offset * 60); out += String(0 - offset * 60);
@ -1226,7 +1271,7 @@ String Timezone::dateTime(time_t t, ezLocalOrUTC_t local_or_utc, String format /
out += String(dayOfYear(t)); // The day of the year (starting from 0) out += String(dayOfYear(t)); // The day of the year (starting from 0)
break; break;
case 'W': case 'W':
out += ezTime.zeropad(weekISO(t), 2); // ISO-8601 week number of year, weeks starting on Monday out += time.zeropad(weekISO(t), 2); // ISO-8601 week number of year, weeks starting on Monday
break; break;
case 'X': case 'X':
out += String(yearISO(t)); // ISO-8601 year-week notation year, see https://en.wikipedia.org/wiki/ISO_week_date out += String(yearISO(t)); // ISO-8601 year-week notation year, see https://en.wikipedia.org/wiki/ISO_week_date
@ -1259,42 +1304,42 @@ uint8_t Timezone::second(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /
uint16_t Timezone::ms(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { uint16_t Timezone::ms(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) {
// Note that here passing anything but TIME_NOW or LAST_READ is pointless // Note that here passing anything but TIME_NOW or LAST_READ is pointless
if (t == TIME_NOW) { ezTime.now(); return ezTime._last_read_ms; } if (t == TIME_NOW) { time.now(); return time._last_read_ms; }
if (t == LAST_READ) return ezTime._last_read_ms; if (t == LAST_READ) return time._last_read_ms;
return 0; return 0;
} }
uint8_t Timezone::day(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { uint8_t Timezone::day(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) {
t = tzTime(t, local_or_utc); t = tzTime(t, local_or_utc);
tmElements_t tm; tmElements_t tm;
ezTime.breakTime(t, tm); time.breakTime(t, tm);
return tm.Day; return tm.Day;
} }
uint8_t Timezone::weekday(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { uint8_t Timezone::weekday(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) {
t = tzTime(t, local_or_utc); t = tzTime(t, local_or_utc);
tmElements_t tm; tmElements_t tm;
ezTime.breakTime(t, tm); time.breakTime(t, tm);
return tm.Wday; return tm.Wday;
} }
uint8_t Timezone::month(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { uint8_t Timezone::month(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) {
t = tzTime(t, local_or_utc); t = tzTime(t, local_or_utc);
tmElements_t tm; tmElements_t tm;
ezTime.breakTime(t, tm); time.breakTime(t, tm);
return tm.Month; return tm.Month;
} }
uint16_t Timezone::year(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { uint16_t Timezone::year(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) {
t = tzTime(t, local_or_utc); t = tzTime(t, local_or_utc);
tmElements_t tm; tmElements_t tm;
ezTime.breakTime(t, tm); time.breakTime(t, tm);
return tm.Year + 1970; return tm.Year + 1970;
} }
uint16_t Timezone::dayOfYear(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { uint16_t Timezone::dayOfYear(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) {
t = tzTime(t, local_or_utc); t = tzTime(t, local_or_utc);
time_t jan_1st = ezTime.makeTime(0, 0, 0, 1, 1, year(t)); time_t jan_1st = time.makeTime(0, 0, 0, 1, 1, year(t));
return (t - jan_1st) / SECS_PER_DAY; return (t - jan_1st) / SECS_PER_DAY;
} }
@ -1304,7 +1349,7 @@ uint16_t Timezone::dayOfYear(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_u
// definition for week 01 is the week with the Gregorian year's first Thursday in it. // definition for week 01 is the week with the Gregorian year's first Thursday in it.
// See https://en.wikipedia.org/wiki/ISO_week_date // See https://en.wikipedia.org/wiki/ISO_week_date
// //
#define startISOyear(year...) ezTime.makeOrdinalTime(0, 0, 0, FIRST, THURSDAY, JANUARY, year) - 3UL * SECS_PER_DAY; #define startISOyear(year...) time.makeOrdinalTime(0, 0, 0, FIRST, THURSDAY, JANUARY, year) - 3UL * SECS_PER_DAY;
uint8_t Timezone::weekISO(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { uint8_t Timezone::weekISO(time_t t /*= TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) {
t = tzTime(t, local_or_utc); t = tzTime(t, local_or_utc);
int16_t yr = year(t); int16_t yr = year(t);
@ -1347,15 +1392,15 @@ Timezone& defaultTZ = UTC;
uint8_t hourFormat12(time_t t = TIME_NOW) { return (defaultTZ.hour(t) % 12); } uint8_t hourFormat12(time_t t = TIME_NOW) { return (defaultTZ.hour(t) % 12); }
bool isAM(time_t t = TIME_NOW) { return (defaultTZ.hour(t) < 12) ? true : false; } bool isAM(time_t t = TIME_NOW) { return (defaultTZ.hour(t) < 12) ? true : false; }
bool isPM(time_t t = TIME_NOW) { return (defaultTZ.hour(t) >= 12) ? true : false; } bool isPM(time_t t = TIME_NOW) { return (defaultTZ.hour(t) >= 12) ? true : false; }
String monthStr(const uint8_t month) { return ezTime.monthString(month); } String monthStr(const uint8_t month) { return time.monthString(month); }
String monthShortStr(const uint8_t month) { return ezTime.monthString(month).substring(0,3); } String monthShortStr(const uint8_t month) { return time.monthString(month).substring(0,3); }
String dayStr(const uint8_t day) { return ezTime.dayString(day); } String dayStr(const uint8_t day) { return time.dayString(day); }
String dayShortStr(const uint8_t day) { return ezTime.dayString(day).substring(0,3); } String dayShortStr(const uint8_t day) { return time.dayString(day).substring(0,3); }
void setTime(time_t t) { defaultTZ.setTime(t); } void setTime(time_t t) { defaultTZ.setTime(t); }
void setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t month, const uint16_t yr) { defaultTZ.setTime(hr, min, sec, day, month, yr); } void setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t month, const uint16_t yr) { defaultTZ.setTime(hr, min, sec, day, month, yr); }
void breakTime(time_t t, tmElements_t &tm) { ezTime.breakTime(t, tm); } void breakTime(time_t t, tmElements_t &tm) { time.breakTime(t, tm); }
time_t makeTime(tmElements_t &tm) { return ezTime.makeTime(tm); } time_t makeTime(tmElements_t &tm) { return time.makeTime(tm); }
time_t makeTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t day, uint8_t month, int16_t year) { return ezTime.makeTime(hour, minute, second, day, month, year); } time_t makeTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t day, uint8_t month, int16_t year) { return time.makeTime(hour, minute, second, day, month, year); }
timeStatus_t timeStatus() { return ezTime.timeStatus(); } timeStatus_t timeStatus() { return time.timeStatus(); }
#endif #endif

@ -12,10 +12,10 @@
#define EZTIME_NETWORK_ENABLE #define EZTIME_NETWORK_ENABLE
// Arduino Ethernet shields // Arduino Ethernet shields
// #define EZTIME_ETHERNET #define EZTIME_ETHERNET
// Uncomment one of the below to only put only messages up to a certain level in the compiled code // Uncomment one of the below to only put only messages up to a certain level in the compiled code
// (You still need to turn them on with ezTime.debugLevel(someLevel) to see them) // (You still need to turn them on with time.debugLevel(someLevel) to see them)
// #define EZTIME_MAX_DEBUGLEVEL_NONE // #define EZTIME_MAX_DEBUGLEVEL_NONE
// #define EZTIME_MAX_DEBUGLEVEL_ERROR // #define EZTIME_MAX_DEBUGLEVEL_ERROR
// #define EZTIME_MAX_DEBUGLEVEL_INFO // #define EZTIME_MAX_DEBUGLEVEL_INFO
@ -53,7 +53,8 @@ typedef enum {
DATA_NOT_FOUND, DATA_NOT_FOUND,
LOCKED_TO_UTC, LOCKED_TO_UTC,
NO_CACHE_SET, NO_CACHE_SET,
CACHE_TOO_SMALL CACHE_TOO_SMALL,
TOO_MANY_EVENTS
} ezError_t; } ezError_t;
typedef enum { typedef enum {
@ -69,7 +70,7 @@ typedef enum {
} ezLocalOrUTC_t; } ezLocalOrUTC_t;
// Defines that can make your code more readable. For example, if you are looking for the first // Defines that can make your code more readable. For example, if you are looking for the first
// Thursday in a year, you could write: ezTime.makeOrdinalTime(0, 0, 0, JANUARY, FIRST, THURSDAY, year) // Thursday in a year, you could write: time.makeOrdinalTime(0, 0, 0, JANUARY, FIRST, THURSDAY, year)
// (As is done within ezTime to calculate ISO weeks) // (As is done within ezTime to calculate ISO weeks)
#define SUNDAY 1 #define SUNDAY 1
@ -107,26 +108,26 @@ typedef enum {
#define debug(args...) "" #define debug(args...) ""
#define debugln(args...) "" #define debugln(args...) ""
#elif defined(EZTIME_MAX_DEBUGLEVEL_ERROR) #elif defined(EZTIME_MAX_DEBUGLEVEL_ERROR)
#define err(args...) if (ezTime._debug_level >= ERROR) Serial.print(args) #define err(args...) if (time._debug_level >= ERROR) Serial.print(args)
#define errln(args...) if (ezTime._debug_level >= ERROR) Serial.println(args) #define errln(args...) if (time._debug_level >= ERROR) Serial.println(args)
#define info(args...) "" #define info(args...) ""
#define infoln(args...) "" #define infoln(args...) ""
#define debug(args...) "" #define debug(args...) ""
#define debugln(args...) "" #define debugln(args...) ""
#elif defined(EZTIME_MAX_DEBUGLELEL_INFO) #elif defined(EZTIME_MAX_DEBUGLELEL_INFO)
#define err(args...) if (ezTime._debug_level >= ERROR) Serial.print(args) #define err(args...) if (time._debug_level >= ERROR) Serial.print(args)
#define errln(args...) if (ezTime._debug_level >= ERROR) Serial.println(args) #define errln(args...) if (time._debug_level >= ERROR) Serial.println(args)
#define info(args...) if (ezTime._debug_level >= INFO) Serial.print(args) #define info(args...) if (time._debug_level >= INFO) Serial.print(args)
#define infoln(args...) if (ezTime._debug_level >= INFO) Serial.println(args) #define infoln(args...) if (time._debug_level >= INFO) Serial.println(args)
#define debug(args...) "" #define debug(args...) ""
#define debugln(args...) "" #define debugln(args...) ""
#else // nothing specified compiles everything in. #else // nothing specified compiles everything in.
#define err(args...) if (ezTime._debug_level >= ERROR) Serial.print(args) #define err(args...) if (time._debug_level >= ERROR) Serial.print(args)
#define errln(args...) if (ezTime._debug_level >= ERROR) Serial.println(args) #define errln(args...) if (time._debug_level >= ERROR) Serial.println(args)
#define info(args...) if (ezTime._debug_level >= INFO) Serial.print(args) #define info(args...) if (time._debug_level >= INFO) Serial.print(args)
#define infoln(args...) if (ezTime._debug_level >= INFO) Serial.println(args) #define infoln(args...) if (time._debug_level >= INFO) Serial.println(args)
#define debug(args...) if (ezTime._debug_level >= DEBUG) Serial.print(args) #define debug(args...) if (time._debug_level >= DEBUG) Serial.print(args)
#define debugln(args...) if (ezTime._debug_level >= DEBUG) Serial.println(args) #define debugln(args...) if (time._debug_level >= DEBUG) Serial.println(args)
#endif #endif
//////////////////////// ////////////////////////
@ -152,12 +153,11 @@ typedef enum {
} timeStatus_t; } timeStatus_t;
typedef struct { typedef struct {
uint8_t stdname_end; //Positions in _posix String. stdname_begin fixed at 0 time_t time;
uint8_t dstname_begin; void (*function)();
uint8_t dstname_end; } ezEvent_t;
bool is_dst;
int16_t offset; // offset from UTC in minutes #define MAX_EVENTS 8
} tzTimeData_t;
#define TIME_NOW 0xFFFFFFFF #define TIME_NOW 0xFFFFFFFF
#define LAST_READ 0xFFFFFFFE #define LAST_READ 0xFFFFFFFE
@ -217,6 +217,7 @@ class EZtime {
EZtime(); EZtime();
static timeStatus_t timeStatus(); static timeStatus_t timeStatus();
static time_t now(bool update_last_read = true); static time_t now(bool update_last_read = true);
static void events();
static void breakTime(time_t time, tmElements_t &tm); // break time_t into elements static void breakTime(time_t time, tmElements_t &tm); // break time_t into elements
static time_t makeTime(tmElements_t &tm); // convert time elements into time_t static time_t makeTime(tmElements_t &tm); // convert time elements into time_t
static time_t makeTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t day, uint8_t month, uint16_t year); static time_t makeTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t day, uint8_t month, uint16_t year);
@ -226,28 +227,29 @@ class EZtime {
static String dayString(uint8_t day); static String dayString(uint8_t day);
static bool secondChanged(); static bool secondChanged();
static bool minuteChanged(); static bool minuteChanged();
static void deleteEvent(uint8_t event_handle);
static void deleteEvent(void (*function)());
private: private:
static ezEvent_t _events[MAX_EVENTS];
static time_t _last_sync_time, _last_read_t; static time_t _last_sync_time, _last_read_t;
static uint32_t _last_sync_millis; static uint32_t _last_sync_millis;
static bool _ntp_enabled; static bool _ntp_enabled;
static uint16_t _last_read_ms; static uint16_t _last_read_ms;
static timeStatus_t _time_status; static timeStatus_t _time_status;
static bool _initialised;
#ifdef EZTIME_NETWORK_ENABLE #ifdef EZTIME_NETWORK_ENABLE
public: public:
static bool queryNTP(String server, time_t &t, unsigned long &measured_at); // measured_at: millis() at measurement, t is converted to secs since 1970 static bool queryNTP(String server, time_t &t, unsigned long &measured_at); // measured_at: millis() at measurement, t is converted to secs since 1970
static void updateNow(); static void updateNTP();
static void setServer(String ntp_server = NTP_SERVER); static void setServer(String ntp_server = NTP_SERVER);
static void setInterval(uint16_t seconds = 0); // 0 = no NTP updates static void setInterval(uint16_t seconds = 0); // 0 = no NTP updates
static bool waitForSync(uint16_t timeout = 0); // timeout in seconds static bool waitForSync(uint16_t timeout = 0); // timeout in seconds
private: private:
static void updateIfNeeded(); static uint16_t _ntp_interval; // in seconds
static uint16_t _update_interval; // in seconds
static uint32_t _update_due;
static uint16_t _update_backoff; // seconds to add to _update_due to retry NTP
static String _ntp_server; static String _ntp_server;
#endif // EZTIME_NETWORK_ENABLE #endif // EZTIME_NETWORK_ENABLE
@ -260,7 +262,7 @@ class EZtime {
}; };
extern EZtime ezTime; // declares the "ezTime" instance of the "EZtime" class extern EZtime time; // declares the "time" instance of the "EZtime" class
// //
@ -280,6 +282,8 @@ class Timezone {
String getTimezoneName(time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL_TIME); String getTimezoneName(time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
int16_t getOffset(time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL_TIME); int16_t getOffset(time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
time_t now(); time_t now();
uint8_t setEvent(void (*function)(), time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL_TIME);
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);
void setTime(time_t t, uint16_t ms = 0); void setTime(time_t t, 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); 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);
String dateTime(String format = DEFAULT_TIMEFORMAT); String dateTime(String format = DEFAULT_TIMEFORMAT);

Loading…
Cancel
Save