├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── component.mk ├── examples ├── EthernetShield │ └── EthernetShield.ino ├── EventsAndOrdinalTime │ └── EventsAndOrdinalTime.ino ├── NoNetwork │ └── NoNetwork.ino ├── TimeFormats │ └── TimeFormats.ino ├── Timezones │ └── Timezones.ino └── milliseconds │ └── milliseconds.ino ├── images ├── Arduino-Due.jpg ├── Arduino-MKR-1000.jpg ├── Arduino-Micro.jpg ├── Arduino-Nano.jpg ├── ESP8266.jpg ├── M5Stack-debug.jpg ├── M5Stack.jpg ├── Teensy-3.2.jpg ├── Uno-with-Ethernet.jpg ├── esp-ntp-clock.jpg ├── moving-clock.gif ├── setting-clock.jpg ├── ticker-and-ntpclock.jpg └── timezones.gif ├── keywords.txt ├── library.json ├── library.properties ├── server ├── README.md ├── server ├── timezoned └── update └── src ├── ezTime.cpp ├── ezTime.h └── lang ├── CA ├── DE ├── EN ├── ES ├── FR ├── IT └── NL /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .development 3 | gh-md-toc 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 R. Gonggrijp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jump to:   [Table of Contents](#table-of-contents)  —  [Function Reference](#function-reference)  —  [dateTime function](#datetime) 2 | 3 | # ezTime, an Arduino library for all of time * 4 | 5 |                                             written by Rop Gonggrijp 6 | 7 | **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.** 8 | 9 |                                                                                                                                         * limitations may apply, see "[2036 and 2038](#2036-and-2038)" chapter 10 | 11 | ![](images/moving-clock.gif) 12 | 13 |   14 | 15 | ## A brief history of ezTime 16 | 17 | 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. 18 | 19 | So far, so good. 20 | 21 | Then I remembered how annoyed I always am when daylight savings time comes or goes, as I have to manually set some of my clocks such as the microwave oven, the clock in the car dashboard, etc etc. It's 2018, my clock should know about timezone rules. So I could get Jack Christensen's [Timezone library](https://github.com/JChristensen/Timezone). But it needs the timezone's rules, like *"DST goes into effect on the last Sunday in March at 02:00 local time"* told to it. I figured I would simply get this data from the internet and parse it. 22 | 23 | Then I wanted 12 or 24 hour time displayed, and thought about various formats for date and time. Wouldn't it be nice to have some function to print formatted time like many programming languages offer? 24 | 25 | Overlooking the battlefield after implementing some part of this, it seemed like there had to be a better way. Some way in which all this work would benefit more people. This is how ezTime — the project that was only going to take a few days — came to be. 26 | 27 | ## ezTime is ... 28 | 29 | **self-contained**: It only depends on other libraries to get online, but then it doesn't need other libraries for NTP and timezone data lookups. (And even networking can be disabled completely if you have another source for time.) 30 | 31 | **precise**: Unlike other libraries, ezTime does not throw away or mangle the fractional second information from the NTP server. An NTP request to pool.ntp.org only takes 40ms round-trip on home DSL these days, so adding sub-second precision to a time library makes sense. ezTime reads the fractional seconds and tries to account for network latency to give you precise time. 32 | 33 | **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. 34 | 35 | **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. 36 | 37 | **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 (e.g. ESP32 through Preferences library). 38 | 39 | **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. 40 | 41 | **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. 42 | 43 | **multilingual**: Can display names of days and months in different languages. Easy to add your own language. 44 | 45 | **small enough**: Works with all features and full debugging information on an old Arduino Uno with an Ethernet Shield, leaving 2/3 of RAM and even some of the flash for you to work with. 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. 46 | 47 | **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. 48 | 49 |   50 | 51 | ### Timezones 52 | 53 | (a complete sketch to show how simple it is) 54 | 55 | ``` 56 | #include 57 | #include 58 | 59 | void setup() { 60 | Serial.begin(115200); 61 | WiFi.begin("your-ssid", "your-password"); 62 | 63 | waitForSync(); 64 | 65 | Serial.println("UTC: " + UTC.dateTime()); 66 | 67 | Timezone NewZealand; 68 | NewZealand.setLocation("Pacific/Auckland"); 69 | Serial.println("New Zealand time: " + NewZealand.dateTime()); 70 | } 71 | 72 | void loop() { } 73 | ``` 74 | 75 | ``` 76 | UTC: Friday, 07-Sep-2018 11:25:10 UTC 77 | New Zealand time: Friday, 07-Sep-2018 23:25:11 NZST 78 | ``` 79 | 80 |   81 | 82 | ### Formatted date and time 83 | 84 | ``` 85 | Serial.println("COOKIE: " + UTC.dateTime(COOKIE)); 86 | Serial.println("ISO8601: " + UTC.dateTime(ISO8601)); 87 | Serial.println("RFC822: " + UTC.dateTime(RFC822)); 88 | Serial.println("RFC850: " + UTC.dateTime(RFC850)); 89 | Serial.println("RFC3339: " + UTC.dateTime(RFC3339)); 90 | Serial.println("RFC3339_EXT: " + UTC.dateTime(RFC3339_EXT)); 91 | Serial.println("RSS: " + UTC.dateTime(RSS)); 92 | Serial.println(); 93 | Serial.println("or like " + UTC.dateTime("l ~t~h~e jS ~o~f F Y, g:i A") ); 94 | ``` 95 | 96 | ``` 97 | COOKIE: Saturday, 25-Aug-2018 14:23:45 UTC 98 | ISO8601: 2018-08-25T14:23:45+0000 99 | RFC822: Sat, 25 Aug 18 14:23:45 +0000 100 | RFC850: Saturday, 25-Aug-18 14:23:45 UTC 101 | RFC3339: 2018-08-25T14:23:45+00:00 102 | RFC3339_EXT: 2018-08-25T14:23:45.846+00:00 103 | RSS: Sat, 25 Aug 2018 14:23:45 +0000 104 | 105 | or like Saturday the 25th of August 2018, 2:23 PM 106 | ``` 107 | 108 |   109 | 110 | ### milliseconds 111 | 112 | ``` 113 | for (int n = 0; n < 10; n++) { 114 | Serial.println(UTC.dateTime("l, d-M-y H:i:s.v T")); 115 | } 116 | ``` 117 | 118 | ``` 119 | Saturday, 25-Aug-18 14:32:53.282 UTC 120 | Saturday, 25-Aug-18 14:32:53.283 UTC 121 | Saturday, 25-Aug-18 14:32:53.284 UTC 122 | Saturday, 25-Aug-18 14:32:53.285 UTC 123 | Saturday, 25-Aug-18 14:32:53.287 UTC 124 | Saturday, 25-Aug-18 14:32:53.290 UTC 125 | Saturday, 25-Aug-18 14:32:53.293 UTC 126 | Saturday, 25-Aug-18 14:32:53.297 UTC 127 | Saturday, 25-Aug-18 14:32:53.300 UTC 128 | Saturday, 25-Aug-18 14:32:53.303 UTC 129 | ``` 130 | 131 | > *This is on my ESP32. See how it fills up the serial buffer real fast at first, and then has to wait for the characters to be sent before it can return?* 132 | 133 |   134 | 135 | ### Rich information and *... oh my just look at these NTP updates* 136 | 137 | ``` 138 | [...] 139 | setInterval(60); 140 | setDebug(INFO); 141 | } 142 | 143 | void loop() { 144 | events(); 145 | } 146 | ``` 147 | 148 | ``` 149 | ezTime debug level set to INFO 150 | Querying pool.ntp.org ... success (round trip 42 ms) 151 | Received time: Saturday, 25-Aug-18 14:34:53.410 UTC (internal clock was 1 ms fast) 152 | Querying pool.ntp.org ... success (round trip 43 ms) 153 | Received time: Saturday, 25-Aug-18 14:35:53.480 UTC (internal clock was 1 ms slow) 154 | Querying pool.ntp.org ... success (round trip 43 ms) 155 | Received time: Saturday, 25-Aug-18 14:36:53.525 UTC (internal clock was 1 ms slow) 156 | Querying pool.ntp.org ... success (round trip 36 ms) 157 | Received time: Saturday, 25-Aug-18 14:37:53.573 UTC (internal clock was 4 ms slow) 158 | Querying pool.ntp.org ... success (round trip 35 ms) 159 | Received time: Saturday, 25-Aug-18 14:38:53.636 UTC (internal clock was spot on) 160 | Querying pool.ntp.org ... success (round trip 32 ms) 161 | Received time: Saturday, 25-Aug-18 14:39:53.674 UTC (internal clock was 1 ms slow) 162 | ``` 163 | 164 |   165 | 166 | ## Getting started 167 | 168 | ezTime is an Arduino library. To start using it with the Arduino IDE: 169 | 170 | * Choose Sketch -> Include Library -> Manage Libraries... 171 | * Type `ezTime` into the search box. 172 | * Click the row to select the library. 173 | * Click the Install button to install the library. 174 | 175 | 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. 176 | 177 | #   178 | 179 | # ezTime User Manual 180 | 181 |   182 | 183 | ## About this manual 184 | 185 | ### Semi-internal functions 186 | 187 | Some functions are not necessarily useful for everyday users of this library, but might be useful to someone someday. For instance, this library checks with the NTP servers automatically, so there should be no need to ever "manually" get an NTP response. But the function to do that is still exposed to the user. Even some functions that have nothing to do with time, like `zeropad` are there for you to use, simply because they *might* be useful to someone, and the library needed them internally so they come at no extra cost in terms of size. In this manual, the names of these functions are printed in *italics* in their chapter headings, just to make it a easier for you to see which functions are core functionality and which are really not needed in everyday use. 188 | 189 | ### Specifying time 190 | 191 | I hate documentation that still makes me reach for for the source code, so this manual supplies the function prototype with each function so you can see what types or arguments each function takes and what type the return value is. I took one shortcut though. A lot of functions allow you to specify a time. In the function prototype this looks like: 192 | 193 | `time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL` 194 | 195 | Throughout this manual, we replace these two optional arguments in the function definitions with: 196 | 197 | `TIME` 198 | 199 | That's because the prior is just a little too long to be repeating a thousand times, and it also makes things look more complicated than they need to be. In most places where you specify a time in ezTime, you are most likely to mean "right now". This can be done by supplying no arguments at all, or `TIME_NOW`. You might make a number of requests in a row, and want to make sure that the time didn't change between them. No need to stick the time value in a variable. After you have made a call specifying no time (meaning `TIME_NOW`), you can specify `LAST_READ` to use the time from the exact moment you made that first call. 200 | 201 | Otherwise, you can specify a `time_t` value, a well-known 32-bit signed integer way of specifying time in seconds elapsed since 00:00 Jan 1st 1970. If you specify a value other than `TIME_NOW` or `LAST_READ`, you can then specify whether you mean in UTC or local time, by following it with a second argument that is either `UTC_TIME` or `LOCAL_TIME`. 202 | 203 | For example, if you have set up a timezone called Berlin, `Berlin.isDST(1536314299, UTC_TIME)` tells you whether Daylight Savings Time is in effect on that time, as seconds from 00:00 Jan 1st 1970 UTC, as opposed to the same amount of seconds from that time in Berlin (which would be the default). There will be some examples later on, showing you how to create and process such timestamps. Mostly though, you don't need specify anything at all because you just want something time-related about "right now". 204 | 205 | > *Time-geek sidenote: ezTime does not have historical information about the daylight savings rules of the past or future, it only applies the rules it has now as if they also applied in the past or future. Check [here](https://www.timeanddate.com/) for historical records for timezones.* 206 | 207 |   208 | 209 | ## How it all works 210 | 211 | ### What happens when you include the library 212 | 213 | It all starts when you include the library with `#include `. From that point forward you can use the functions in this manual to control the behaviour of ezTime. There will then also be a timezone object called `UTC`, which will be set as the default timezone for all commands that take an optional timezone prefix. 214 | 215 | ### No daemons here 216 | 217 | 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 synchronises 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. That's why there is a periodic event set to synchronise the clock with the NTP server. 218 | 219 | If you want events to happen — whether your own or the NTP updates that ezTime does periodically) — you should have `events()` in the main loop of your program. 220 | 221 | ### But I only just woke up ! 222 | 223 | 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 synchronised 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. 224 | 225 | 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 `waitForSync` that simply calls `events()` until it is synchronized (or until a set number of seconds passes, see below). 226 | 227 |   228 | 229 | ## Setting and synchronising time 230 | 231 | The NTP request from the scenario above failed because the network wasn't up yet, so the clock would still not be synchronised. A new request will be scheduled for 1801 seconds later, and sent when your code (or `waitForSync`) calls `events`. 232 | 233 |   234 | 235 | ### timeStatus 236 | 237 | `timeStatus_t timeStatus();` 238 | 239 | Returns what state the clock is in. `timeStatus()` will return one of: 240 | 241 | | timeStatus | meaning | 242 | |----|----| 243 | | `timeNotSet` | No NTP update or manual setting of the clock (by calling the `.setTime` method of a timezone) has taken place | 244 | | `timeSet` | The clock should have the current time | 245 | | `timeNeedsSync` | A scheduled NTP request has been due for more than an hour. (The time an update needs to be due before `timeNeedsSync` is set is configured by the `NTP_STALE_AFTER` define in the `ezTime.h` file.) | 246 | 247 |   248 | 249 | ### waitForSync 250 | 251 | `bool waitForSync(uint16_t timeout = 0);` 252 | 253 | 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. `waitForSync` will wait for the network to connect, and then for the time to be synchronised 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](#errors-and-debug-information) further down) 254 | 255 |   256 | 257 | ### *setServer and setInterval* 258 | 259 | `void setServer(String ntp_server = NTP_SERVER);` 260 | 261 | `void setInterval(uint16_t seconds = 0);` 262 | 263 | By default, ezTime is set to poll `pool.ntp.org` about every 30 minutes. These defaults should work for most people, but you can change them by specifying a new server with `setServer` or a new interval (in seconds) with setInterval. If you call setInterval with an interval of 0 seconds or call it as `setInterval()`, no more NTP queries will be made. 264 | 265 |   266 | 267 | ### *updateNTP* 268 | 269 | `void updateNTP();` 270 | 271 | Updates the time from the NTP server immediately. Will keep retrying about every 30 minutes (defined by `NTP_RETRY` in `ezTime.h`), will schedule the next update to happen after the normal interval. 272 | 273 |   274 | 275 | ### *lastNtpUpdateTime* 276 | 277 | `time_t lastNtpUpdateTime();` 278 | 279 | Will return the last time the time was successfully synchronized with the NTP server. 280 | 281 |   282 | 283 | ### *queryNTP* 284 | 285 | `bool queryNTP(String server, time_t &t, unsigned long &measured_at);` 286 | 287 | 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. 288 | 289 | If the time server answers, `queryNTP` returns `true`. If `false` is returned, `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`). 290 | 291 | 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. 292 | 293 |   294 | 295 | ## Timezones 296 | 297 | > *If only it was as uncomplicated as this map suggests. Every band is actually made up of countries that all change to their Daylight Saving Time on different dates, and they even frequently change the rules for when that happens.* 298 | 299 | ![](images/timezones.gif) 300 | 301 | Timezones in ezTime are objects. They can be created with `Timezone yourTZ`, where `yourTZ` is the name you choose to refer to the timezone. In this manual, this name will be used from now on. But you can naturally choose any name you want. 302 | 303 | Internally, ezTime stores everything it knows about a timezone as two strings. One is the official name of the timezone in "Olson" format (like `Europe/Berlin`). That name is used to then update when needed all the other information needed to represent time in that timezone. This is in another string, in so-called "posix" format. It's often a little longer and for Berlin it is `CET-1CEST,M3.5.0,M10.5.0/3`. The elements of this string have the following meanings: 304 | 305 | | Element | meaning | 306 | | ---- | ---- | 307 | | `CET` | Name of timezone in standard time (CET = Central European Time in this case.) 308 | | `-1` | Hours offset from UTC, meaning subtract one hour from this time to get to UTC. (Note offset is often written elsewhere the other way around (so +1 in this case), just to confuse things.) Could also specify minutes, like `-05:30` for India. | 309 | | `CEST` | Name of timezone in Daylight Saving Time (DST), CEST stands for Central European Summer Time | 310 | | `,M3` | DST starts in March | 311 | | `.5` | On the last occurrence of 312 | | `.0` | a Sunday | 313 | | `/2` | at 02:00 local time | 314 | | `,M10` | DST ends in October | 315 | | `.5` | on the last occurrence of | 316 | | `.0` | a Sunday | 317 | | `/3` | at 03:00 local time | 318 | 319 |   320 | 321 | ### setDefault 322 | 323 | `void setDefault()`    — **MUST** be prefixed with name of a timezone, like `yourTz.setDefault()` 324 | 325 | `#include ` 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 timezone by calling `yourTZ.setDefault()`. ezTime is compatible with the classic Arduino time library, and thus you can call various functions in the root namespace like `hour()` and `minute()` — without a timezone in front. They are interpreted as if passed to the default timezone. So if you have existing 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. New code that depends on ezTime should probably explicitly mention the timezone. 326 | 327 |   328 | 329 | ### setPosix 330 | 331 | `bool setPosix(String posix)`    — **MUST** be prefixed with name of a timezone, like `India.setPosix("IST-5:30")` 332 | 333 | Allows you to directly enter the posix information for a timezone. For simple timezones, you could set things up manually. For example for India, a mere 334 | 335 | ``` 336 | Timezone India; 337 | India.setPosix("IST-5:30"); 338 | Serial.println(India.dateTime()); 339 | ``` 340 | 341 | is enough, because the time in India doesn't go back and forth with the coming and going of Daylight Savings Time (even though the half hour offset to UTC is pretty weird.) 342 | 343 |   344 | 345 | ### getPosix 346 | 347 | `String getPosix()`    — **MUST** be prefixed with name of a timezone, like `India.getPosix()` 348 | 349 | `getPosix` does what you would expect and simply returns the posix string stored in ezTime for a given timezone. 350 | 351 |   352 | 353 | ### isDST 354 | 355 | `bool isDST(TIME);`    — Assumes default timezone if no timezone is prefixed 356 | 357 | Tells you whether DST is in effect at a given time in this timezone. If you do not provide arguments, it's interpreted as 'right now'. You can also specify a time (in seconds since 1970, we'll get back to that) in the first argument. If you want to know a certain time in UTC in within the DST windown in a given timezone you can set the second argument to `false`, otherwise it is assumed you are asking about a time expressed as local time. 358 | 359 |   360 | 361 | ### getTimezoneName 362 | 363 | `String getTimezoneName(TIME);`    — Assumes default timezone if no timezone is prefixed 364 | 365 | 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. 366 | 367 |   368 | 369 | ### getOffset 370 | 371 | `int16_t getOffset(TIME)`    — Assumes default timezone if no timezone is prefixed 372 | 373 | Provide the offset from UTC in minutes at the indicated time (or now if you do not specify anything). The offset here is in the same direction as the posix information, so -120 means 2 hours east of UTC. 374 | 375 |   376 | 377 | ### setLocation 378 | 379 | `boolsetLocation(String location = "")`    — **MUST** be prefixed with name of a timezone 380 | 381 | With `setLocation` you can provide a string to do an internet lookup for a timezone. The string can either be an Olson timezone name, like `Europe/Berlin` (or some unique part of such a name). ([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. 382 | 383 | If you provide no location ( `YourTZ.setLocation()` ), ezTime will attempt to do a GeoIP lookup to 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. 384 | 385 | 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". 386 | 387 | If you execute multiple calls to `setLocation`, make sure they are more than 3 seconds apart, because the server will not answer if calls from the same IP come within 3 seconds of one another (see below). 388 | 389 |   390 | 391 | ### timezoned.rop.nl 392 | 393 | `timezoned.rop.nl` is ezTime's own timezone service that it connects to. It is a simple UDP service that gets a packet on UDP port 2342 with the request, and responds with a packet that holds the POSIX information for that timezone (after `OK `) or the error (after `ERR `). It will only respond to the same IP-number once every three seconds to prevent being used in DDoS attacks. 394 | 395 | The service has the potential of seeing which IP-numbers use ezTime and what timezone data they request. Any GeoIP lookups are done against a local database, no third parties are involved. The service does not keep logfiles unless something is wrong and needs debugging. In such a case any logfiles will be deleted after work is done, but within 48 hours at the latest. 396 | 397 | Data has never been used for any other purposes than debugging, nor is any other use envisioned in the future. 398 | 399 | The code for the timezoned server is included in the server directory of the library repository, in case someone wnats to know how that works or insists on running a timezone information server themselves. Be aware that it is a bit of an ugly hack at the time of writing this... 400 | 401 |   402 | 403 | ### Timezone caching, EEPROM or NVS 404 | 405 | If you use setLocation, the timezone information comes from timezoned.rop.nl. I'll try to keep that running as stable as possible, but if that service has a problem, your Arduino would not know what time it is anymore. 406 | 407 | That is why you can create a place for ezTime to store the data about the timezone. That way, it doens't need to get the information anew every time the Arduino boots. You can store the cache for a timezone in EEPROM (the default) or NVS. 408 | 409 | If your code says `tz.setCache(0)` it will try to read and interpret the data from EEPROM location 0 immediately, and it will store any future updated data it receives for that timezone there. Some programs might want to just start up in whatever timezone the user has set before, so they just call `yourTZ.setCache(0)` when they start and `yourTZ.setLocation` when the user goes to settings to select a different timezone. Simple programs might do: 410 | 411 | ``` 412 | if (!someTZ.setCache(0)) someTZ.setLocation("Europe/Berlin"); 413 | ``` 414 | 415 | To only get the timezone data from the internet when the cache is empty or outdated and use the cached information all the other times. (Note that if you change the city in the above example it will still get the Berlin information from the cache and not execute the `setLocation` until you run `someTZ.clearCache()`. 416 | 417 |   418 | 419 | ### setCache 420 | 421 | `bool setCache(int16_t address)`    — **MUST** be prefixed with name of a timezone 422 | 423 | If your ezTime is compiled with `#define EZTIME_CACHE_EEPROM` (which is the default), you can supply an EEPROM location. A single timezone needs 50 bytes to cache. The data is written in compressed form so that the Olson and Posix strings fit in 3/4 of the space they would normally take up, and along with it is stored a checksum, a length field and a single byte for the month in which the cache was retrieved, in months after January 2018. 424 | 425 | `bool setCache(String name, String key)`    — **MUST** be prefixed with name of a timezone 426 | 427 | On ESP32 and possibly other platforms, there is an emulation for the EEPROM in flash, but there is also a nicer mechanism that stores keys and values in flash. You can use this by enabling `#define EZTIME_CACHE_NVS` in `ezTime.h` You can then supply a section name and a key to serve as the cache storage location for a given timezone. 428 | 429 |   430 | 431 | ### clearCache 432 | 433 | `void clearCache(bool delete_section = false)`    — **MUST** be prefixed with name of a timezone 434 | 435 | Clears the cache for a timezone. If you use EEPROM, the bytes are overwritten with zeroes, if you use NVS, the key is deleted. If you provide the argument `true` using NVS the entire section is deleted. Do this only if that section does not contain anything else that you want to keep. 436 | 437 |   438 | 439 | ### Crazy timezones 440 | 441 | #### Chatham Islands and Nepal 442 | 443 | The Chatham Islands are in Pacific about 800 kilometres east of New Zealand. Some 600 people live there, but they have their own timezone (UTC+12:45). It is one of only three time zones with a 45-minute offset from UTC, the others being Nepal Time (UTC+05:45) and the unofficial Australian Central Western Time (UTC+08:45). These timezones work fine in ezTime. 444 | 445 | #### Morocco 446 | 447 | Morocco goes on and off Daylight Saving Time twice per year. This currently breaks ezTime as our parser can only handle one DST period per year. Fortunately they will stop doing this in 2020: the Moroccans probably got tired of all the clocks that did not adjust properly. 448 | 449 |   450 | 451 | ## Getting date and time 452 | 453 | ### dateTime 454 | 455 | `String dateTime(TIME, String format = DEFAULT_TIMEFORMAT);`
456 |     — Assumes default timezone if no timezone is prefixed 457 | 458 | We'll start with one of the most powerful functions of ezTime. With `dateTime` you can represent a date and/or a time in any way you want. You do this in the same way you do in many programming languages: by providing a special formatting string. Many characters in this string have special meanings and will be replaced. What this means is that `UTC.dateTime("l, d-M-y H:i:s.v T")` might return `Saturday, 25-Aug-18 14:32:53.282 UTC`. Below is the list of characters and what they are replaced by. Any characters not on this list are simply not replaced and stay as is. See the last two entries for a way to use characters on this list in your string. 459 | 460 | | char | replaced by 461 | | ----- | :----- 462 | | `d` | Day of the month, 2 digits with leading zeros 463 | | `D` | First three letters of day in English, like `Tue` 464 | | `j` | Day of the month without leading zeros 465 | | `l` | (lowercase L) Day of the week in English, like `Tuesday` 466 | | `N` | // ISO-8601 numeric representation of the day of the week. (1 = Monday, 7 = Sunday) 467 | | `S` | English ordinal suffix for the day of the month, 2 characters (st, nd, rd, th) 468 | | `w` | Numeric representation of the day of the week (0 = Sunday) 469 | | `F` | A month's name, such as `January` 470 | | `m` | Numeric representation of a month, with leading zeros 471 | | `M` | Three first letters of a month in English, like `Apr` 472 | | `n` | Numeric representation of a month, without leading zeros 473 | | `t` | Number of days in the given month 474 | | `Y` | A full numeric representation of the year, 4 digits 475 | | `y` | Last two digits of the year 476 | | `a` | am or pm 477 | | `A` | AM or PM 478 | | `g` | 12-hour format of an hour without leading zeros 479 | | `G` | 24-hour format of an hour without leading zeros 480 | | `h` | 12-hour format of an hour with leading zeros 481 | | `H` | 24-hour format of an hour with leading zeros 482 | | `i` | Minutes with leading zeros 483 | | `s` | Seconds with leading zero 484 | | `T` | abbreviation for timezone, like `CEST` 485 | | `v` | milliseconds as three digits 486 | | `e` | Timezone identifier (Olson name), like `Europe/Berlin` 487 | | `O` | Difference to Greenwich time (GMT) in hours and minutes written together, like `+0200`. Here a positive offset means east of UTC. 488 | | `P` | Same as O but with a colon between hours and minutes, like `+02:00` 489 | | `Z` | Timezone offset in seconds. West of UTC is negative, east of UTC is positive. 490 | | `z` | The day of the year (starting from 0) 491 | | `W` | ISO-8601 week number. See right below for explanation link. 492 | | `X` | ISO-8601 year for year-week notation as four digit year. Warning: Not guaranteed to be same as current year, may be off by one at start or end of year. See [here](https://en.wikipedia.org/wiki/ISO_week_date) 493 | | `B` | One-letter military code for the timezone, or `?` if the offset is not a whole number of hours. 494 | | `\` | Not printed, but escapes the following character, meaning it will not be replaced. But inserting a backslash in the string means you have to supply two backslashes `\\` to be interpreted as one. 495 | | `~` | (tilde) Same as backslash above, except easier to insert in the string. Example: `~t~h~e` will print the word `the` in the string. Letters should be escaped even if they are not on the list because they may be replaced in future versions. 496 | 497 | So as an example: `UTC.dateTime("l ~t~h~e jS ~o~f F Y, g:i A")` yields date and time in this format: `Saturday the 25th of August 2018, 2:23 PM`. 498 | 499 |   500 | 501 | ### Built-in date and time formats 502 | 503 | There are built-in values to specify some standard date and time formats. For example: `UTC.dateTIme(RSS)` (without quotes around RSS) returns something like `Sat, 25 Aug 2018 14:23:45 +0000`. Here's a list of all these built in format abbreviations. 504 | 505 | | name | formatted date and time 506 | |:------|:------| 507 | | ATOM | 2018-08-25T14:23:45+00:00 508 | | COOKIE | Saturday, 25-Aug-2018 14:23:45 UTC 509 | | ISO8601 | 2018-08-25T14:23:45+0000 510 | | RFC822 | Sat, 25 Aug 18 14:23:45 +0000 511 | | RFC850 | Saturday, 25-Aug-18 14:23:45 UTC 512 | | RFC1036 | Sat, 25 Aug 18 14:23:45 +0000 513 | | RFC1123 | Sat, 25 Aug 2018 14:23:45 +0000 514 | | RFC2822 | Sat, 25 Aug 2018 14:23:45 +0000 515 | | RFC3339 | 2018-08-25T14:23:45+00:00 516 | | RFC3339_EXT | 2018-08-25T14:23:45.846+00:00 517 | | RSS | Sat, 25 Aug 2018 14:23:45 +0000 518 | | W3C | 2018-08-25T14:23:45+00:00 519 | | ISO8601_YWD | 2018-W34-5 520 | 521 |   522 | 523 | ### Time and date as numbers 524 | 525 | `time_t now()`    — Assumes default timezone if no timezone is prefixed 526 | 527 | Returns the current time in seconds since midnight Jan 1st 1970 in the timezone specified. 528 | 529 | `uint8_t hour(TIME)`
530 | `uint8_t hourFormat12(TIME)`
531 | `uint8_t minute(TIME)`
532 | `uint8_t second(TIME)`
533 | `uint16_t ms(TIME)`
534 | `uint8_t day(TIME)`
535 | `uint8_t weekday(TIME)`
536 | `uint8_t month(TIME)`
537 | `uint16_t year(TIME);` 538 | 539 | These functions return the various elements of date or time for right now (no arguments) or for a given time in seconds sinds 1970. `weekday` returns a number starting with 1 for Sunday. `hourFormat12` does hours from 1 to 12. 540 | 541 | If you want to compare you can use compiler defines in all capital letters for names of days and months, like: 542 | 543 | ``` 544 | if (weekday() == TUESDAY) Serial.print("Tuesday!!"); 545 | ``` 546 | 547 | ``` 548 | if (month() == FEBRUARY && day() == 14) Serial.print("Valentine's day!"); 549 | ``` 550 | 551 |   552 | 553 | `bool isAM(TIME)`    — Both assume default timezone if no timezone is prefixed
554 | `bool isPM(TIME)` 555 | 556 | These will tell if it is before or after noon for a given `TIME`, return `true` or `false`. 557 | 558 |   559 | 560 | `uint16_t dayOfYear(TIME)`    — Assumes default timezone if no timezone is prefixed 561 | 562 | Returns how many days have passed in the year. January 1st returns 0, 563 | 564 |   565 | 566 | ### *weekISO and yearISO* 567 | 568 | `uint8_t weekISO(TIME)`
`uint16_t yearISO(TIME)`    — Both assume default timezone if no timezone is prefixed 569 | 570 | These functions return the ISO-8601 Year-week notation year and week number. Note that the year returned here can differ one from the current year at the first or last days or the year. ISO-8601 defines the first year of the week as the first week that has a Thursday in it. Meaning the start of the ISO-year can be a few days earlier (in December) or a few days later (in January). 571 | 572 |   573 | 574 | ### *militaryTZ* 575 | 576 | `String militaryTZ(TIME)`    — Assumes default timezone if no timezone is prefixed 577 | 578 | Returns the one-letter military code for the timezone. See [here](https://www.timeanddate.com/time/zones/military) for details. If the offset for the current timezone is not a whole number of hours, "?" is returned. 579 | 580 |   581 | 582 | ### secondChanged and minuteChanged 583 | 584 | `bool secondChanged()` 585 | 586 | `bool minuteChanged()` 587 | 588 | You might have code that put the time on a display in some really nice-looking format, using `dateTime`. The main loop wants to keep the time updated, but not every time the main loop runs, because it would cause the display to flicker. The classic solution for this is to store the time, recreate the string every time and compare to see if it changed. With `secondChanged` and `minuteChanged` you can just write something like: 589 | 590 | ``` 591 | if (minuteChanged()) WriteToSomeDisplay(UTC.dateTime("H:i")); 592 | ``` 593 | 594 |   595 | 596 | > Note that this uses a single variable internally to store when the time was last accessed, so your code can only poll this in one place. 597 | 598 | ### names of days and months 599 | 600 | `String dayStr(const uint8_t day)` 601 | 602 | `String dayShortStr(const uint8_t day)` 603 | 604 | `String MonthStr(const uint8_t month)` 605 | 606 | `String MonthShortStr(const uint8_t month)` 607 | 608 | These functions will take a numeric argument and convert it to the name of the day or the name of the months. These functions do not tell you the current day or month, they just convert the number `1` to `Sunday`, `Sun`, `January` or `Jan` respectively. They are here to be compatible with the classic Time library. The [`dateTime`](#datetime) function can provide all sorts of strings and is much more flexible. 609 | 610 |   611 | 612 | ### different languages 613 | 614 | If you edit the ezTime.h file in the library directory and set the EZTIME_LANGUAGE define to NL or DE, you will get the names of the months and days in Dutch or German respectively. The functions that return these names are separated out in files in the `src/lang` directory, the files there will show you what languages are currently supported. If you add a file in this directory you will add a language, it is that easy. Please submit the files you make via a pull request so others can use ezTime in their own language too. 615 | 616 |   617 | 618 | ## Events 619 | 620 | ### events 621 | 622 | `void events()` 623 | 624 | This is what your loop functions should call if they want events executed. This includes user-set events (see below) and the NTP updates that ezTime does periodically. `events()` also calls the Arduino function `yield()`, so you do not need to call that anymore (but once more doesn't hurt). 625 | 626 |   627 | 628 | ### setEvent 629 | 630 | `uint8_t setEvent(void (*function)(), TIME)`    — Both assume default timezone if no timezone is prefixed 631 | 632 | `uint8_t setEvent(void (*function)(), uint8_t hr, uint8_t min, uint8_t sec,`
          `uint8_t day, uint8_t mnth, uint16_t yr)` 633 | 634 | 635 | With ezTime, you can set your own events to run at a specified time. Simply run `setEvent` specifying the name of the function you would like to call (without the brackets) and a time you would like to call it. The first time `events` runs and notices that it is at or after the time you specified it will run and then delete the event. If you want an event to recur, simply set a new event in the function that gets called. You can have a maximum of 8 events by default (easily changed by changing `MAX_EVENTS` in `ezTime.h`). ezTime uses one event internally to trigger the next NTP update. 636 | 637 | `setevent` returns an 8-bit event handle between 1 and MAX_EVENTS which you can store in a variable and use to delete the event with `deleteEvent` should your program need to. Zero is returned and the error `TOO_MANY_EVENTS` set if there are no more free slots for your new event. 638 | 639 |   640 | 641 | ### deleteEvent 642 | 643 | `void deleteEvent(uint8_t event_handle)` 644 | 645 | Deletes the event with the handle as returned by `setEvent`. 646 | 647 | `void deleteEvent(void (*function)())` 648 | 649 | Buy you can also call `deleteEvent` with the name of the function (again without the brackets) to delete all events that would have executed that function. 650 | 651 |   652 | 653 | 654 | ## Setting date and time manually 655 | 656 | ![](images/setting-clock.jpg) 657 | 658 | ### setTime 659 | 660 | `void setTime(time_t t, uint16_t ms = 0)`    — Both assume default timezone if no timezone is prefixed 661 | 662 | `void setTime(uint8_t hr, uint8_t min, uint8_t sec,`
          `uint8_t day, uint8_t mnth, uint16_t yr)` 663 | 664 | `setTime` pretty much does what it says on the package: it sets the time to the time specified, either 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 its internal offset to the corresponding time in UTC so all timezones stay at the correct time. 665 | 666 | 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 `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. 667 | 668 | ### Alternate sources of time 669 | 670 | If your time source is not NTP, the way to update time is to create a user function that gets the time from somewhere and then sets the clock with `setTime` and then schedules the next time it synchronises the clock with `setEvent`. This way you have full flexibility: you can schedule the next update sooner if this update fails, for instance. Remember to turn off NTP updates if you want your new time to stick. 671 | 672 | ## Working with time values 673 | 674 | ### *breakTime* 675 | 676 | `void breakTime(time_t time, tmElements_t &tm)` 677 | 678 | 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: 679 | 680 | ``` 681 | typedef struct { 682 | uint8_t Second; 683 | uint8_t Minute; 684 | uint8_t Hour; 685 | uint8_t Wday; // day of week, sunday is day 1 686 | uint8_t Day; 687 | uint8_t Month; 688 | uint8_t Year; // offset from 1970; 689 | } tmElements_t; 690 | ``` 691 | 692 | Meaning this code would print the hour: 693 | 694 | ``` 695 | tmElements_t tm; 696 | breakTime(UTC.now(), tm); 697 | Serial.print(tm.Hour); 698 | ``` 699 | 700 | But `Serial.println(UTC.hour())` also works and is much simpler. `breakTime` is used internally and is a part of the original Time library, so it is available for you to use. Mind that the year is a single byte value, years since 1970. 701 | 702 |   703 | 704 | ### makeTime 705 | 706 | `time_t makeTime(tmElements_t &tm);` 707 | 708 | This does the opposite of `breakTime`: it takes a `tmElements_t` structure and turns it into a `time_t` value in seconds since Jan 1st 1970. 709 | 710 | `time_t makeTime(uint8_t hour, uint8_t minute, uint8_t second,`
          `uint8_t day, uint8_t month, int16_t year);` 711 | 712 | 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. 713 | 714 |   715 | 716 | ### *makeOrdinalTime* 717 | 718 | `time_t makeOrdinalTime(uint8_t hour, uint8_t minute, uint8_t second,`
          `uint8_t ordinal, uint8_t wday, uint8_t month, int16_t year);` 719 | 720 | 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`. 721 | 722 | ``` 723 | makeOrdinalTime(0, 0, 0, FIRST, THURSDAY, JANUARY, year) 724 | ``` 725 | 726 | > *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.* 727 | 728 |   729 | 730 | ### *compileTime* 731 | 732 | `time_t compileTime(String compile_date = __DATE__, String compile_time = __TIME__);` 733 | 734 | You can ignore the arguments above and just say `compileTime()`. Returns the time value for when you compiled your sketch. You can check out the "NoNetwork" example with this library to see it in use: it makes your Arduino pretend to know what time it is. 735 | 736 |   737 | 738 | ### *tzTime* 739 | 740 | `time_t tzTime(TIME)`    — Both forms **MUST** be prefixed with name of a timezone 741 | 742 | This is the internal workhorse function that converts `time_t` in UTC to `time_t` in a timezone or vice versa. It is used by almost all the functions that apply to a timezone, and it takes `TIME` — meaning nothing for "right now", or a `time_t` value and an optional argument to specify whether that is `LOCAL_TIME` or `UTC_TIME`, and then it will convert to the opposite. `TIME_NOW` and `LAST_READ` are always output as `time_t` in that timezone. 743 | 744 | `time_t tzTime(time_t t, ezLocalOrUTC_t local_or_utc, String &tzname, bool &is_dst, int16_t &offset)` 745 | 746 | In this second form you have to supply all arguments, and it will fill your `tzname`, `is_dst` and `offset` variables with the appropriate values, the offset is in minutes west of UTC. Note that there are easier functions for you to get this information: `getTimezoneName`, `isDST` and `getOffset` respectively. If your code calls all three in a tight loop you might consider using `tzTime` instead as the other functions each do the whole parsing using `tzTime`, so you would be calling it three times and it does quite a bit. 747 | 748 |   749 | 750 | ## Various functions 751 | 752 | These functions are available for you to use because ezTime needed them internally, so they come at no extra cost, so to speak. 753 | 754 | ### *zeropad* 755 | 756 | `String zeropad(uint32_t number, uint8_t length);` 757 | 758 | Pads `number` with zeroes to the left until the resulting string is `length` places long. 759 | 760 |   761 | 762 | ## Errors and debug information 763 | 764 | ### *setDebug* 765 | 766 | `void setDebug(ezDebugLevel_t level)`
`void setDebug(ezDebugLevel_t level, Print &device)` 767 | 768 | `level` sets the level of detail at which ezTime outputs messages on the serial port. Can be set to one of: 769 | 770 | | level | effect | 771 | |---|---| 772 | | `NONE` | ezTime does not output anything on the serial port | 773 | | `ERROR` | ezTime will show when errors occur. Note that these may be transient errors that ezTime recovers from, such as NTP timeouts. | 774 | | `INFO` | Essentially shows you what ezTime is doing in the background. Includes messages about NTP updates, initialising timezones, etc etc. | 775 | | `DEBUG` | Detailed debugging information unlikely to be of much use unless you are trying to get to the bottom of certain internal behaviour of ezTime. | 776 | 777 | *Note:* you can specify which level of debug information would be compiled into the library. This is especially significant for AVR Arduino users that need to limit the flash and RAM footprint of ezTtime. See the "Smaller footprint, AVR Arduinos" chapter further down. 778 | `device` is optional and can specify a device to receive the debug messages. This defaults to the Hardwareserial object named `Serial` but can be any device that has inherited from the `Print` class. Don't worry if you don't understand that: it means you can specify not only serial ports, but also a handle to a file you have opened on the SD card as well as a lot of LCD screen devices. For instance, on my M5Stack device I can — after `#include ` and `m5.begin()` — do: `setDebug(INFO, m5.lcd)` 779 | 780 | ![](images/M5Stack-debug.jpg) 781 | 782 | You cannot send debug information to multiple devices at the same time. 783 | 784 |   785 | 786 | ### *error* 787 | 788 | `ezError_t error(bool reset = false);` 789 | 790 | A number of functions in ezTime are booleans, meaning they return `true` or `false` as their return value, where `false` means some error occurred. `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. 791 | 792 | When you call `error(true)`, it will also reset the error to `OK`, so you can make sure no new errors happened after a certain point. 793 | 794 |   795 | 796 | ### *errorString* 797 | 798 | `String errorString(ezError_t err = LAST_ERROR);` 799 | 800 | 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. 801 | 802 |   803 | 804 | ## Compatibility with Arduino Time library 805 | 806 | The classic Arduino time library has a lot of functions and defines that end up in the root namespace, meaning you can just run `hour()` instead of `someTZ.hour()`. These functions are supported by ezTime and will act as if they are prefixed with the defaultTZ. This is UTC by default, but you can make any timezone the default by writing `someTZ.setDefault();` 807 | 808 | If you do not wish to have these functions in your namespace, you can comment out `#define ARDUINO_TIMELIB_COMPATIBILITY` in `ezTime.h`. New code depending on ezTime should probably explicitly state the timezone, especially in code with multiple timezones. 809 | 810 |   811 | 812 | ## Smaller footprint, AVR Arduinos 813 | 814 | This library compiles on an Arduino Uno with an Ethernet shield. However, it will use up almost all of the flash on that, which is fine if you were making a date and time display anyway. But if your code is bigger than that, you will want to make it smaller. By uncommenting `#define EZTIME_MAX_DEBUGLEVEL_NONE` in `ezTime.h` you get no debugging information and no textual errors, which saves a couple of kilobytes. If you do not use networking, you should also comment out `#define EZTIME_NETWORK_ENABLE`, that will save a *ton* of space: not just in ezTime but also because the networking library does not get loaded. 815 | 816 |   817 | 818 | ## 2036 and 2038 819 | 820 | The NTP timestamps used here run until the 7th of February 2036. NTP itself has 128 bits of time precision, I haven't looked into it much. Didn't have to, because just a little later, on the 19th of January 2038, the time_t 32 bit signed integer overflows. This is 20 years from today, in 2018. The Arduino world, if it still exists around then, will have come together around some solution that probably involves 64-bit time like in many operating systems of 2018. If you use this library in your nuclear generating station (**NOOOOO!**), make sure you're not around when these timers wrap around. 821 | 822 | Should you be the one doing maintenance on this in some far-ish future: For ezTime I created another overflowing counter: the cache age for the timezone information is written as a single unsigned byte in months after January 2018, so that could theoretically cause problems in 2039, but I think everything will just roll over and use 2039 as the new anchor date. 823 | 824 |   825 | 826 | ## Inspiration 827 | 828 | Please do tell me if you made something cool and I'll include it here. 829 | 830 | ### Arduino clock 831 | 832 | Github user [BugerDread](https://github.com/BugerDread) made an [Arduino clock](https://github.com/BugerDread/esp8266-ezTIME-wifi-clock) using ESP8266, ezTIME and MAX7219 LED display modules. It's pretty: 833 | 834 | ![](images/ticker-and-ntpclock.jpg) 835 | 836 | ![](images/esp-ntp-clock.jpg) 837 | 838 |   839 | 840 | ## ezTime on various Arduino platforms 841 | 842 | If your Arduino has anything like normal Arduino networking, we can make it work. In some cases it might take an exception in the code if it needs a special header file or so, but no big deal. And if it has `EEPROM.h` or `Preferences.h` to store things in flash, we can make the cache work too. Please open an issue on [github](htttps://github.com/ropg/ezTime) to tell me if something doesn't work. Here's a list of boards that ezTime has been tested on. 843 | 844 | ### DSD Tech ESP8266 845 | 846 | ![](images/ESP8266.jpg) 847 | 848 | ezTime 0.7.2 ran fine. Board: Generic ESP8266, Reset Method: nodemcu. Don't forget to replace `#include ` with `#include ` in your sketch. 849 | 850 |   851 | 852 | ### Teensy 3.2 853 | 854 | ![](images/Teensy-3.2.jpg) 855 | 856 | ezTime 0.7.2 ran fine. Did not test networking, so compiled with `#define EZTIME_NETWORK_ENABLE` commented out, used NoNetwork example. 857 | 858 |   859 | 860 | ### Arduino Uno R3 (clone) with Ethernet Shield W5100 861 | 862 | ![](images/Uno-with-Ethernet.jpg) 863 | 864 | ezTime 0.7.4 ran, the EthernetShield example leaves some 5k of flash: 865 | 866 | ``` 867 | Sketch uses 26536 bytes (82%) of program storage space. Maximum is 32256 bytes. 868 | Global variables use 733 bytes (35%) of dynamic memory, leaving 1315 bytes for local variables. Maximum is 2048 bytes. 869 | ``` 870 | 871 | By setting `#define EZTIME_MAX_DEBUGLEVEL_NONE` in `eztime.h` we can free up some more flash: 872 | 873 | ``` 874 | Sketch uses 23870 bytes (74%) of program storage space. Maximum is 32256 bytes. 875 | Global variables use 729 bytes (35%) of dynamic memory, leaving 1319 bytes for local variables. Maximum is 2048 bytes. 876 | ``` 877 | 878 | ezTime and NoNetwork example without `#define EZTIME_NETWORK_ENABLE` (if you have another time source and are willing to put in the Posix information for timezones yourself.): 879 | 880 | ``` 881 | Sketch uses 11558 bytes (35%) of program storage space. Maximum is 32256 bytes. 882 | Global variables use 354 bytes (17%) of dynamic memory, leaving 1694 bytes for local variables. Maximum is 2048 bytes. 883 | ``` 884 | 885 |   886 | 887 | ### M5Stack (ESP32) 888 | 889 | ![](images/M5Stack.jpg) 890 | 891 | ezTime 0.7.2 ran fine. 892 | 893 |   894 | 895 | ### Arduino Micro 896 | 897 | ![](images/Arduino-Micro.jpg) 898 | 899 | USB took a while to be recognized on my Mac, and then I took a while to discover that this is one that needs the 900 | 901 | ``` 902 | while (!Serial) { ; } // wait for serial port to connect. Needed for native USB port only 903 | ``` 904 | 905 | line that you see in many sketches. But then ezTime 0.7.2 ran fine using NoNetwork example. 906 | 907 |   908 | 909 | ### Arduino Due 910 | 911 | ![](images/Arduino-Due.jpg) 912 | 913 | ezTime 0.7.2 runs fine (No networking on board, so tested with NoNetwork example). If you use the native USB port it also needs the `while (!Serial) { ; }` and you need to change all the `Serial.` to `SerialUSB.` in your sketch. Note that if you want debugging info you can pass the SerialUSB port as the second argument to `setDebug`. 914 | 915 |   916 | 917 | ### Arduino MKR1000 918 | 919 | ![](images/Arduino-MKR-1000.jpg) 920 | 921 | ezTime 0.7.2 worked, eventually. But I didn't like this one. Getting online is difficult. Install Wifi101 library from the library manager and make sure to start your sketch with: 922 | 923 | ``` 924 | #include 925 | #include 926 | ``` 927 | 928 | * Test sketch complained about WiFi firmware / driver mismatch. Couldn't get the firmware update tool to work, but WiFi worked anyway. 929 | * The WiFi object does not have the `isConnected` method so I wrote some detection for ezTime to skip the NO_NETWORK checks. This means that if you have debug level at ERROR or higher, waitForSync will throw some NTP TIMEOUT errors (and then continue just fine after wifi is online). 930 | * It doesn't have `EEPROM.h` or `Preferences.h` but some proprietary `FlashStorage.h`. So no cache for the moment. (Turn off both cache defines at the beginning of `ezTime.h`. I'll write it if the third person wants it. 931 | 932 |   933 | 934 | ### Arduino Nano 935 | 936 | ![](images/Arduino-Nano.jpg) 937 | 938 | ezTime 0.7.2 runs fine (No networking on board, so tested with NoNetwork example) 939 | 940 | 941 |   942 | 943 | ## Table of Contents 944 | 945 | * [ezTime, an Arduino library for all of time *](#eztime-an-arduino-library-for-all-of-time-) 946 | * [A brief history of ezTime](#a-brief-history-of-eztime) 947 | * [ezTime is ...](#eztime-is-) 948 | * [Timezones](#timezones) 949 | * [Formatted date and time](#formatted-date-and-time) 950 | * [milliseconds](#milliseconds) 951 | * [Rich information and ... oh my just look at these NTP updates](#rich-information-and--oh-my-just-look-at-these-ntp-updates) 952 | * [Getting started](#getting-started) 953 | * [ezTime User Manual](#eztime-user-manual) 954 | * [About this manual](#about-this-manual) 955 | * [Semi-internal functions](#semi-internal-functions) 956 | * [Specifying time](#specifying-time) 957 | * [How it all works](#how-it-all-works) 958 | * [What happens when you include the library](#what-happens-when-you-include-the-library) 959 | * [No daemons here](#no-daemons-here) 960 | * [But I only just woke up !](#but-i-only-just-woke-up-) 961 | * [Setting and synchronising time](#setting-and-synchronising-time) 962 | * [timeStatus](#timestatus) 963 | * [waitForSync](#waitforsync) 964 | * [setServer and setInterval](#setserver-and-setinterval) 965 | * [updateNTP](#updatentp) 966 | * [lastNtpUpdateTime](#lastNtpUpdateTime) 967 | * [queryNTP](#queryntp) 968 | * [Timezones](#timezones-1) 969 | * [setDefault](#setdefault) 970 | * [setPosix](#setposix) 971 | * [getPosix](#getposix) 972 | * [isDST](#isdst) 973 | * [getTimezoneName](#gettimezonename) 974 | * [getOffset](#getoffset) 975 | * [setLocation](#setlocation) 976 | * [timezoned.rop.nl](#timezoned-rop-nl) 977 | * [Timezone caching, EEPROM or NVS](#timezone-caching-eeprom-or-nvs) 978 | * [setCache](#setcache) 979 | * [clearCache](#clearcache) 980 | * [Crazy timezones](#crazy-timezones) 981 | * [Chatham Islands and Nepal](#chatham-islands-and-nepal) 982 | * [Morocco](#morocco) 983 | * [Getting date and time](#getting-date-and-time) 984 | * [dateTime](#datetime) 985 | * [Built-in date and time formats](#built-in-date-and-time-formats) 986 | * [Time and date as numbers](#time-and-date-as-numbers) 987 | * [weekISO and yearISO](#weekiso-and-yeariso) 988 | * [militaryTZ](#militarytz) 989 | * [secondChanged and minuteChanged](#secondchanged-and-minutechanged) 990 | * [names of days and months](#names-of-days-and-months) 991 | * [different languages](#different-languages) 992 | * [Events](#events) 993 | * [events](#events-1) 994 | * [setEvent](#setevent) 995 | * [deleteEvent](#deleteevent) 996 | * [Setting date and time manually](#setting-date-and-time-manually) 997 | * [setTime](#settime) 998 | * [Alternate sources of time](#alternate-sources-of-time) 999 | * [Working with time values](#working-with-time-values) 1000 | * [breakTime](#breaktime) 1001 | * [makeTime](#maketime) 1002 | * [makeOrdinalTime](#makeordinaltime) 1003 | * [compileTime](#compiletime) 1004 | * [tzTime](#tztime) 1005 | * [Various functions](#various-functions) 1006 | * [zeropad](#zeropad) 1007 | * [Errors and debug information](#errors-and-debug-information) 1008 | * [setDebug](#setdebug) 1009 | * [error](#error) 1010 | * [errorString](#errorstring) 1011 | * [Compatibility with Arduino Time library](#compatibility-with-arduino-time-library) 1012 | * [Smaller footprint, AVR Arduinos](#smaller-footprint-avr-arduinos) 1013 | * [2036 and 2038](#2036-and-2038) 1014 | * [Inspiration](#Inspiration) 1015 | * [Arduino clock](#Arduino-clock) 1016 | * [ezTime on various Arduino platforms](#eztime-on-various-arduino-platforms) 1017 | * [DSD Tech ESP8266](#dsd-tech-esp8266) 1018 | * [Teensy 3.2](#teensy-32) 1019 | * [Arduino Uno R3 (clone) with Ethernet Shield W5100](#arduino-uno-r3-clone-with-ethernet-shield-w5100) 1020 | * [M5Stack (ESP32)](#m5stack-esp32) 1021 | * [Arduino Micro](#arduino-micro) 1022 | * [Arduino Due](#arduino-due) 1023 | * [Arduino MKR1000](#arduino-mkr1000) 1024 | * [Arduino Nano](#arduino-nano) 1025 | * [Table of Contents](#table-of-contents) 1026 | * [Function reference](#function-reference) 1027 | 1028 |   1029 | 1030 | ### Function reference 1031 | 1032 | | function | returns | arguments | TZ prefix | network | cache | 1033 | |:---------|:--------|:----------|:----------|:--------|:------| 1034 | | [**`breakTime`**](#breaktime) | `void` | `time_t time`, `tmElements_t &tm` | no | no | no 1035 | | [**`clearCache`**](#clearcache) | `void` | `bool delete_section = false` | yes | yes | NVS 1036 | | [**`clearCache`**](#clearcache) | `void` | | yes | yes | EEPROM 1037 | | [**`compileTime`**](#compiletime) | `time_t` | `String compile_date = __DATE__`, `String compile_time = __TIME__` | no | no | no 1038 | | [**`dateTime`**](#datetime) | `String` | `TIME`, `String format = DEFAULT_TIMEFORMAT` | optional | no | no 1039 | | [**`day`**](#time-and-date-as-numbers) | `uint8_t` | `TIME` | optional | no | no 1040 | | [**`dayOfYear`**](#time-and-date-as-numbers) | `uint16_t` | `TIME` | optional | no | no 1041 | | [**`dayShortStr`**](#names-of-days-and-months) | `String` | `uint8_t day` | no | no | no 1042 | | [**`dayStr`**](#names-of-days-and-months) | `String` | `uint8_t day` | no | no | no 1043 | | [**`deleteEvent`**](#deleteevent) | `void` | `uint8_t event_handle` | no | no | no 1044 | | [**`deleteEvent`**](#deleteevent) | `void` | `void (`*function`)(``)` | no | no | no 1045 | | [**`error`**](#error) | `ezError_t` | `bool reset = false` | no | no | no 1046 | | [**`errorString`**](#errorstring) | `String` | `ezError_t err = LAST_ERROR` | no | no | no 1047 | | [**`events`**](#events) | `void` | | no | no | no 1048 | | [**`getOffset`**](#getoffset) | `int16_t` | `TIME` | optional | no | no 1049 | | **function** | **returns** | **arguments** | **TZ prefix** | **network** | **cache** | 1050 | | [**`getOlson`**](#getolson) | `String` | | optional | yes | yes | 1051 | | [**`getPosix`**](#getposix) | `String` | | yes | no | no 1052 | | [**`getTimezoneName`**](#gettimezonename) | `String` | `TIME` | optional | no | no 1053 | | [**`hour`**](#time-and-date-as-numbers) | `uint8_t` | `TIME` | optional | no | no 1054 | | [**`hourFormat12`**](#time-and-date-as-numbers) | `uint8_t` | `TIME` | optional | no | no 1055 | | [**`isAM`**](#time-and-date-as-numbers) | `bool` | `TIME` | optional | no | no 1056 | | [**`isDST`**](#isdst) | `bool` | `TIME` | optional | no | no 1057 | | [**`isPM`**](#time-and-date-as-numbers) | `bool` | `TIME` | optional | no | no 1058 | | [**`lastNtpUpdateTime`](#lastNtpUpdateTime) | `time_t` | | no | yes | no 1059 | | [**`makeOrdinalTime`**](#makeordinaltime) | `time_t` | `uint8_t hour`, `uint8_t minute`, `uint8_t second`, `uint8_t ordinal`, `uint8_t wday`, `uint8_t month`, `uint16_t year` | no | no | no 1060 | | [**`makeTime`**](#maketime) | `time_t` | `tmElements_t &tm` | no | no | no 1061 | | [**`makeTime`**](#maketime) | `time_t` | `uint8_t hour`, `uint8_t minute`, `uint8_t second`, `uint8_t day`, `uint8_t month`, `uint16_t year` | no | no | no 1062 | | [**`militaryTZ`**](#militarytz) | `String` | `TIME` | optional | no | no 1063 | | [**`minute`**](#time-and-date-as-numbers) | `uint8_t` | `TIME` | optional | no | no 1064 | | [**`minuteChanged`**](#secondchanged-and-minutechanged) | `bool` | | no | no | no 1065 | | [**`month`**](#time-and-date-as-numbers) | `uint8_t` | `TIME` | optional | no | no 1066 | | **function** | **returns** | **arguments** | **TZ prefix** | **network** | **cache** | 1067 | | [**`monthShortStr`**](#names-of-days-and-months) | `String` | `uint8_t month` | no | no | no 1068 | | [**`monthStr`**](#names-of-days-and-months) | `String` | `uint8_t month` | no | no | no 1069 | | [**`ms`**](#time-and-date-as-numbers) | `uint16_t` | `TIME_NOW` or `LAST_READ` | optional | no | no 1070 | | [**`now`**](#time-and-date-as-numbers) | `time_t` | | optional | no | no 1071 | | [**`queryNTP`**](#queryntp) | `bool` | `String server`, `time_t &t`, `unsigned long &measured_at` | no | yes | no 1072 | | [**`second`**](#time-and-date-as-numbers) | `uint8_t` | `TIME` | optional | no | no 1073 | | [**`secondChanged`**](#secondchanged-and-minutechanged) | `bool` | | no | no | no 1074 | | [**`setCache`**](#setcache) | `bool` | `String name`, `String key` | yes | yes | NVS 1075 | | [**`setCache`**](#setcache) | `bool` | `int16_t address` | yes | yes | EEPROM 1076 | | [**`setDebug`**](#setdebug) | `void` | `ezDebugLevel_t level` | no | no | no 1077 | | [**`setDebug`**](#setdebug) | `void` | `ezDebugLevel_t level`, `Print &device` | no | no | no 1078 | | [**`setDefault`**](#setdefault) | `void` | | yes | no | no 1079 | | [**`setEvent`**](#setevent) | `uint8_t` | `void (*function)()`, `TIME` | optional | no | no 1080 | | [**`setEvent`**](#setevent) | `uint8_t` | `void (*function)()`, `uint8_t hr`, `uint8_t min`, `uint8_t sec`, `uint8_t day`, `uint8_t mnth`, `uint16_t yr` | optional | no | no 1081 | | [**`setInterval`**](#setserver-and-setinterval) | `void` | `uint16_t seconds = 0` | | yes | no 1082 | | **function** | **returns** | **arguments** | **TZ prefix** | **network** | **cache** | 1083 | | [**`setLocation`**](#setlocation) | `bool` | `String location = ""` | yes | yes | no 1084 | | [**`setPosix`**](#setposix) | `bool` | `String posix` | yes | yes | no 1085 | | [**`setServer`**](#setserver-and-setinterval) | `void` | `String ntp_server = NTP_SERVER` | no | yes | no 1086 | | [**`setTime`**](#settime) | `void` | `time_t t`, `uint16_t ms = 0` | optional | no | no 1087 | | [**`setTime`**](#settime) | `void` | `uint8_t hr`, `uint8_t min`, `uint8_t sec`, `uint8_t day`, `uint8_t mnth`, `uint16_t yr` | optional | no | no 1088 | | [**`timeStatus`**](#timestatus) | `timeStatus_t` | | no | no | no 1089 | | [**`tzTime`**](#tztime) | `time_t` | `TIME` | yes | no | no 1090 | | [**`tzTime`**](#tztime) | `time_t` | `TIME`, `String &tzname`, `bool &is_dst`, `int16_t &offset` | yes | no | no 1091 | | [**`updateNTP`**](#updatentp) | `void` | | no | yes | no 1092 | | [**`waitForSync`**](#waitforsync) | `bool` | `uint16_t timeout = 0` | no | yes | no 1093 | | [**`weekISO`**](#weekiso-and-yeariso) | `uint8_t` | `TIME` | optional | no | no 1094 | | [**`weekday`**](#time-and-date-as-numbers) | `uint8_t` | `TIME` | optional | no | no 1095 | | [**`year`**](#time-and-date-as-numbers) | `uint16_t` | `TIME` | optional | no | no 1096 | | [**`yearISO`**](#weekiso-and-yeariso) | `uint16_t` | `TIME` | optional | no | no 1097 | | [**`zeropad`**](#zeropad) | `String` | `uint32_t number`, `uint8_t length` | no | no | no 1098 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Main Makefile. This is basically the same as a component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | COMPONENT_SRCDIRS := src 7 | COMPONENT_ADD_INCLUDEDIRS := src 8 | -------------------------------------------------------------------------------- /examples/EthernetShield/EthernetShield.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * Note: to use an ethernet shield, You must also set #define EZTIME_ETHERNET in $sketch_dir/libraries/ezTime/src/ezTime.h 3 | * 4 | * Also note that all ezTime examples can be used with an Ethernet shield if you just replace the beginning of the sketch 5 | * with the beginning of this one. 6 | */ 7 | 8 | #include 9 | 10 | #include 11 | 12 | // Enter a MAC address for your controller below. (Or use address below, just make sure it's unique on your network) 13 | // Newer Ethernet shields have a MAC address printed on a sticker on the shield 14 | #define MAC_ADDRESS { 0xBA, 0xDB, 0xAD, 0xC0, 0xFF, 0xEE } 15 | 16 | void setup() { 17 | 18 | // Open serial communications and wait for port to open: 19 | Serial.begin(115200); 20 | while (!Serial) { ; } // wait for serial port to connect. Needed for native USB port only 21 | Serial.println(); 22 | 23 | // You can use Ethernet.init(pin) to configure the CS pin 24 | //Ethernet.init(10); // Most Arduino shields (default if unspecified) 25 | //Ethernet.init(5); // MKR ETH shield 26 | //Ethernet.init(0); // Teensy 2.0 27 | //Ethernet.init(20); // Teensy++ 2.0 28 | //Ethernet.init(15); // ESP8266 with Adafruit Featherwing Ethernet 29 | //Ethernet.init(33); // ESP32 with Adafruit Featherwing Ethernet 30 | 31 | Serial.print(F("Ethernet connection ... ")); 32 | byte mac [] = MAC_ADDRESS; 33 | if (Ethernet.begin(mac) == 0) { 34 | Serial.println(F("failed. (Reset to retry.)")); 35 | while (true) { ; }; // Hang 36 | } else { 37 | Serial.print(F("got DHCP IP: ")); 38 | Serial.println(Ethernet.localIP()); 39 | } 40 | // give the Ethernet shield a second to initialize: 41 | delay(1000); 42 | 43 | // OK, we're online... So the part above here is what you swap in before the waitForSync() in the other examples... 44 | 45 | 46 | // Wait for ezTime to get its time synchronized 47 | waitForSync(); 48 | 49 | Serial.println(); 50 | Serial.println("UTC: " + UTC.dateTime()); 51 | 52 | Timezone myTZ; 53 | 54 | // Provide official timezone names 55 | // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 56 | myTZ.setLocation(F("Pacific/Auckland")); 57 | Serial.print(F("New Zealand: ")); 58 | Serial.println(myTZ.dateTime()); 59 | 60 | // Wait a little bit to not trigger DDoS protection on server 61 | // See https://github.com/ropg/ezTime#timezonedropnl 62 | delay(5000); 63 | 64 | // Or country codes for countries that do not span multiple timezones 65 | myTZ.setLocation(F("de")); 66 | Serial.print(F("Germany: ")); 67 | Serial.println(myTZ.dateTime()); 68 | 69 | // Same as above 70 | delay(5000); 71 | 72 | // See if local time can be obtained (does not work in countries that span multiple timezones) 73 | Serial.print(F("Local (GeoIP): ")); 74 | if (myTZ.setLocation()) { 75 | Serial.println(myTZ.dateTime()); 76 | } else { 77 | Serial.println(errorString()); 78 | } 79 | 80 | Serial.println(); 81 | Serial.println(F("Now ezTime will show an NTP sync every 60 seconds")); 82 | 83 | // Set NTP polling interval to 60 seconds. Way too often, but good for demonstration purposes. 84 | setInterval(60); 85 | 86 | // Make ezTime show us what it is doing 87 | setDebug(INFO); 88 | 89 | } 90 | 91 | void loop() { 92 | 93 | events(); 94 | 95 | } 96 | -------------------------------------------------------------------------------- /examples/EventsAndOrdinalTime/EventsAndOrdinalTime.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This sketch prints a message at noon UTC, every second Tuesday of the month. 3 | * Not very useful, but demonstrates events and ordinal time. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | void setup() { 10 | 11 | Serial.begin(115200); 12 | while (!Serial) { ; } // wait for Serial port to connect. Needed for native USB port only 13 | WiFi.begin("your-ssid", "your-password"); 14 | 15 | waitForSync(); 16 | 17 | // Set the event to trigger for the first time 18 | setEvent( itIsTheSecondTuesday, nextSecondTuesday() ); 19 | 20 | } 21 | 22 | void loop() { 23 | 24 | events(); 25 | 26 | } 27 | 28 | void itIsTheSecondTuesday() { 29 | Serial.print(F("It's the second Tuesday: ")); 30 | Serial.println(UTC.dateTime()); 31 | 32 | // The event then sets a new event for the next time 33 | setEvent( itIsTheSecondTuesday, nextSecondTuesday() ); 34 | } 35 | 36 | time_t nextSecondTuesday() { 37 | 38 | int8_t m = UTC.month(); 39 | int16_t y = UTC.year(); 40 | time_t t = 0; 41 | 42 | while (t <= UTC.now()) { 43 | // Try in current month first, if that has passed, loop once more for next month 44 | t = makeOrdinalTime(12, 0, 0, SECOND, TUESDAY, m, y); 45 | m++; 46 | if (m == 13) { 47 | m = 1; 48 | y++; 49 | } 50 | } 51 | return t; 52 | } 53 | -------------------------------------------------------------------------------- /examples/NoNetwork/NoNetwork.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This example also does something useful with "#define EZTIME_NETWORK_ENABLE" at the 3 | * start of ezTime.h commented out. It will start the time at the time the code was 4 | * compiled. You have to set your local timezone information by hand in the 5 | * LOCALTZ_POSIX define. (The string contains the names for your TZ in standard and 6 | * Daylight Saving Time, as well as the starting and ending point for DST and the 7 | * offset to UTC. 8 | * 9 | * If you do not want to look up the posix string you can simply provide a name and 10 | * the current UTC offset in hours _west_ of UTC, like "PDT+7" 11 | */ 12 | 13 | 14 | #include 15 | 16 | #define LOCALTZ_POSIX "CET-1CEST,M3.4.0/2,M10.4.0/3" // Time in Berlin 17 | 18 | Timezone local; 19 | Timezone pacific; 20 | 21 | void setup() { 22 | 23 | Serial.begin(115200); 24 | while (!Serial) { ; } // wait for Serial port to connect. Needed for native USB port only 25 | Serial.println(); 26 | 27 | local.setPosix(LOCALTZ_POSIX); 28 | local.setTime(compileTime()); 29 | Serial.print(F("Local time : ")); 30 | Serial.println(local.dateTime()); 31 | 32 | pacific.setPosix(F("PST+8PDT,M3.2.0/2,M11.1.0/2")); 33 | Serial.print(F("Pacific time : ")); 34 | Serial.println(pacific.dateTime()); 35 | 36 | Serial.print(F("UTC : ")); 37 | Serial.println(UTC.dateTime()); 38 | 39 | } 40 | 41 | void loop() { 42 | } 43 | -------------------------------------------------------------------------------- /examples/TimeFormats/TimeFormats.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void setup() { 5 | 6 | Serial.begin(115200); 7 | 8 | WiFi.begin("your-ssid", "your-password"); 9 | while (!Serial) { ; } // wait for Serial port to connect. Needed for native USB port only 10 | waitForSync(); 11 | 12 | Serial.println(); 13 | Serial.println("Time in various internet standard formats ..."); 14 | Serial.println(); 15 | Serial.println("ATOM: " + dateTime(ATOM)); 16 | Serial.println("COOKIE: " + dateTime(COOKIE)); 17 | Serial.println("IS8601: " + dateTime(ISO8601)); 18 | Serial.println("RFC822: " + dateTime(RFC822)); 19 | Serial.println("RFC850: " + dateTime(RFC850)); 20 | Serial.println("RFC1036: " + dateTime(RFC1036)); 21 | Serial.println("RFC1123: " + dateTime(RFC1123)); 22 | Serial.println("RFC2822: " + dateTime(RFC2822)); 23 | Serial.println("RFC3339: " + dateTime(RFC3339)); 24 | Serial.println("RFC3339_EXT: " + dateTime(RFC3339_EXT)); 25 | Serial.println("RSS: " + dateTime(RSS)); 26 | Serial.println("W3C: " + dateTime(W3C)); 27 | Serial.println(); 28 | Serial.println(" ... and any other format, like \"" + dateTime("l ~t~h~e jS ~o~f F Y, g:i A") + "\""); 29 | } 30 | 31 | void loop() { 32 | events(); 33 | } 34 | -------------------------------------------------------------------------------- /examples/Timezones/Timezones.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void setup() { 5 | 6 | Serial.begin(115200); 7 | while (!Serial) { ; } // wait for Serial port to connect. Needed for native USB port only 8 | WiFi.begin("your-ssid", "your-password"); 9 | 10 | // Uncomment the line below to see what it does behind the scenes 11 | // setDebug(INFO); 12 | 13 | waitForSync(); 14 | 15 | Serial.println(); 16 | Serial.println("UTC: " + UTC.dateTime()); 17 | 18 | Timezone myTZ; 19 | 20 | // Provide official timezone names 21 | // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones 22 | myTZ.setLocation(F("Pacific/Auckland")); 23 | Serial.print(F("New Zealand: ")); 24 | Serial.println(myTZ.dateTime()); 25 | 26 | // Wait a little bit to not trigger DDoS protection on server 27 | // See https://github.com/ropg/ezTime#timezonedropnl 28 | delay(5000); 29 | 30 | // Or country codes for countries that do not span multiple timezones 31 | myTZ.setLocation(F("de")); 32 | Serial.print(F("Germany: ")); 33 | Serial.println(myTZ.dateTime()); 34 | 35 | // Same as above 36 | delay(5000); 37 | 38 | // See if local time can be obtained (does not work in countries that span multiple timezones) 39 | Serial.print(F("Local (GeoIP): ")); 40 | if (myTZ.setLocation()) { 41 | Serial.println(myTZ.dateTime()); 42 | } else { 43 | Serial.println(errorString()); 44 | } 45 | 46 | } 47 | 48 | void loop() { 49 | events(); 50 | } 51 | -------------------------------------------------------------------------------- /examples/milliseconds/milliseconds.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void setup() { 5 | 6 | Serial.begin(115200); 7 | while (!Serial) { ; } // wait for Serial port to connect. Needed for native USB port only 8 | WiFi.begin("your-ssid", "your-password"); 9 | 10 | setInterval(60); 11 | waitForSync(); 12 | 13 | Serial.println(); 14 | 15 | for (int n = 0; n < 10; n++) { 16 | Serial.println(UTC.dateTime("l, d-M-y H:i:s.v T")); 17 | } 18 | 19 | Serial.println(); 20 | Serial.println("Those milliseconds between the first and the last line ..."); 21 | Serial.println(); 22 | Serial.println(" ... most of that is spent sending to the serial port."); 23 | 24 | Serial.println(); 25 | Serial.println(); 26 | Serial.println(); 27 | Serial.println("And ezTime is not making those milliseconds up either."); 28 | Serial.println(); 29 | Serial.println(" ... Stick around as we do an NTP request every minute."); 30 | setDebug(INFO); 31 | 32 | } 33 | 34 | void loop() { 35 | events(); 36 | } 37 | -------------------------------------------------------------------------------- /images/Arduino-Due.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/Arduino-Due.jpg -------------------------------------------------------------------------------- /images/Arduino-MKR-1000.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/Arduino-MKR-1000.jpg -------------------------------------------------------------------------------- /images/Arduino-Micro.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/Arduino-Micro.jpg -------------------------------------------------------------------------------- /images/Arduino-Nano.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/Arduino-Nano.jpg -------------------------------------------------------------------------------- /images/ESP8266.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/ESP8266.jpg -------------------------------------------------------------------------------- /images/M5Stack-debug.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/M5Stack-debug.jpg -------------------------------------------------------------------------------- /images/M5Stack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/M5Stack.jpg -------------------------------------------------------------------------------- /images/Teensy-3.2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/Teensy-3.2.jpg -------------------------------------------------------------------------------- /images/Uno-with-Ethernet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/Uno-with-Ethernet.jpg -------------------------------------------------------------------------------- /images/esp-ntp-clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/esp-ntp-clock.jpg -------------------------------------------------------------------------------- /images/moving-clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/moving-clock.gif -------------------------------------------------------------------------------- /images/setting-clock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/setting-clock.jpg -------------------------------------------------------------------------------- /images/ticker-and-ntpclock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/ticker-and-ntpclock.jpg -------------------------------------------------------------------------------- /images/timezones.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ropg/ezTime/7b3c8aa020be818ac149e0762543ac5e81ccfabe/images/timezones.gif -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ezTime KEYWORD2 2 | error KEYWORD2 3 | errorString KEYWORD2 4 | setDebug KEYWORD2 5 | timeStatus KEYWORD2 6 | now KEYWORD2 7 | events KEYWORD2 8 | deleteEvent KEYWORD2 9 | breakTime KEYWORD2 10 | makeTime KEYWORD2 11 | makeOrdinalTime KEYWORD2 12 | compileTime KEYWORD2 13 | monthString KEYWORD2 14 | dayString KEYWORD2 15 | secondChanged KEYWORD2 16 | minuteChanged KEYWORD2 17 | queryNTP KEYWORD2 18 | updateNTP KEYWORD2 19 | lastNtpUpdateTime KEYWORD2 20 | setServer KEYWORD2 21 | setInterval KEYWORD2 22 | waitForSync KEYWORD2 23 | urlEncode KEYWORD2 24 | zeropad KEYWORD2 25 | 26 | # Timezone object 27 | 28 | Timezone KEYWORD1 29 | UTC KEYWORD3 30 | DefaultTZ KEYWORD3 31 | setPosix KEYWORD2 32 | getPosix KEYWORD2 33 | tzTime KEYWORD2 34 | setEvent KEYWORD2 35 | setDefault KEYWORD2 36 | isDST KEYWORD2 37 | getTimezoneName KEYWORD2 38 | getOffset KEYWORD2 39 | now KEYWORD2 40 | setTime KEYWORD2 41 | dateTime KEYWORD2 42 | hour KEYWORD2 43 | minute KEYWORD2 44 | second KEYWORD2 45 | ms KEYWORD2 46 | day KEYWORD2 47 | weekday KEYWORD2 48 | month KEYWORD2 49 | year KEYWORD2 50 | dayOfYear KEYWORD2 51 | weekISO KEYWORD2 52 | yearISO KEYWORD2 53 | militaryTZ KEYWORD2 54 | setLocation KEYWORD2 55 | setCache KEYWORD2 56 | clearCache KEYWORD2 57 | getOlson KEYWORD2 58 | 59 | # TimeLib compatibility 60 | 61 | numberOfSeconds KEYWORD2 62 | numberOfMinutes KEYWORD2 63 | numberOfHours KEYWORD2 64 | dayOfWeek KEYWORD2 65 | elapsedDays KEYWORD2 66 | elapsedSecsToday KEYWORD2 67 | previousMidnight KEYWORD2 68 | nextMidnight KEYWORD2 69 | elapsedSecsThisWeek KEYWORD2 70 | previousSunday KEYWORD2 71 | nextSunday KEYWORD2 72 | minutesToTime_t KEYWORD2 73 | hoursToTime_t KEYWORD2 74 | daysToTime_t KEYWORD2 75 | weeksToTime_t KEYWORD2 76 | hourFormat12 KEYWORD2 77 | isAM KEYWORD2 78 | isPM KEYWORD2 79 | monthStr KEYWORD2 80 | monthShortStr KEYWORD2 81 | dayStr KEYWORD2 82 | dayShortStr KEYWORD2 83 | SECS_PER_MIN LITERAL1 84 | SECS_PER_HOUR LITERAL1 85 | DAYS_PER_WEEK LITERAL1 86 | SECS_PER_WEEK LITERAL1 87 | SECS_PER_YEAR LITERAL1 88 | SECS_YR_2000 LITERAL1 89 | 90 | # Errors 91 | 92 | NO_ERROR LITERAL1 93 | LAST_ERROR LITERAL1 94 | NO_NETWORK LITERAL1 95 | TIMEOUT LITERAL1 96 | CONNECT_FAILED LITERAL1 97 | DATA_NOT_FOUND LITERAL1 98 | LOCKED_TO_UTC LITERAL1 99 | NO_CACHE_SET LITERAL1 100 | CACHE_TOO_SMALL LITERAL1 101 | 102 | # Debug levels 103 | 104 | NONE LITERAL1 105 | ERROR LITERAL1 106 | INFO LITERAL1 107 | DEBUG LITERAL1 108 | 109 | # time indicators 110 | 111 | TIME_NOW LITERAL1 112 | LAST_READ LITERAL1 113 | LOCAL_TIME LITERAL1 114 | UTC_TIME LITERAL1 115 | 116 | # Date and time formats 117 | 118 | ISO8601_YWD LITERAL1 119 | ATOM LITERAL1 120 | COOKIE LITERAL1 121 | ISO8601 LITERAL1 122 | RFC822 LITERAL1 123 | RFC850 LITERAL1 124 | RFC1036 LITERAL1 125 | RFC1123 LITERAL1 126 | RFC2822 LITERAL1 127 | RFC3339 LITERAL1 128 | RFC3339_EXT LITERAL1 129 | RSS LITERAL1 130 | W3C LITERAL1 131 | 132 | 133 | #defines to make code more readable 134 | 135 | SUNDAY LITERAL1 136 | MONDAY LITERAL1 137 | TUESDAY LITERAL1 138 | WEDNESDAY LITERAL1 139 | THURSDAY LITERAL1 140 | FRIDAY LITERAL1 141 | SATURDAY LITERAL1 142 | 143 | JANUARY LITERAL1 144 | FEBRUARY LITERAL1 145 | MARCH LITERAL1 146 | APRIL LITERAL1 147 | MAY LITERAL1 148 | JUNE LITERAL1 149 | JULY LITERAL1 150 | AUGUST LITERAL1 151 | SEPTEMBER LITERAL1 152 | OCTOBER LITERAL1 153 | NOVEMBER LITERAL1 154 | DECEMBER LITERAL1 155 | 156 | FIRST LITERAL1 157 | SECOND LITERAL1 158 | THIRD LITERAL1 159 | FOURTH LITERAL1 160 | LAST LITERAL1 161 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ezTime", 3 | "description": "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.", 4 | "keywords": "time date ntp timezone events milliseconds", 5 | "authors": { 6 | "name": "Rop Gonggrijp", 7 | "url": "https://github.com/ropg", 8 | "maintainer": true 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ropg/ezTime" 13 | }, 14 | "version": "0.8.3", 15 | "framework": "arduino", 16 | "platforms": "*", 17 | "build": { 18 | "libArchive": false 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ezTime 2 | version=0.8.3 3 | author=Rop Gonggrijp 4 | maintainer=Rop Gonggrijp 5 | 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. 6 | paragraph=See more on https://github.com/ropg/ezTime 7 | category=Timing 8 | url=https://github.com/ropg/ezTime 9 | architectures=* 10 | includes=ezTime.h 11 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # timezoned - The Timezone Daemon 2 | 3 | >If you do not plan to run your own timezone information server, you do not need anything in this directory... 4 | 5 | This is a brutally ugly hack that serves timezone information via UDP port 2342. To use it, try the following on a unix machine with PHP installed: 6 | 7 | * Create a user called 'timezoned' 8 | 9 | * Copy the 'update' and 'server' scripts to this user's homedir, and change the #! line at the beginning of the server script to point to the PHP binary on the system 10 | 11 | * Log in or su to the timezoned user, make the 'update' script executable and run it. 12 | 13 | * Make the 'server' script executable and run it. (Make sure the server accepts packets on 2342 UDP.) 14 | 15 | * Test by running `nc -u 2342` on some other system and then typing a zone (like "Europe/London") followed by Ctrl-D. You should get the POSIX information for that zone. 16 | 17 | * If that works, you may (on a FreeBSD machine) use the 'timezoned' script by placing it in /usr/local/etc/rc.d to start the server automatically. On other systems, you'll have to figure out how to start it automatically when the server reboots. 18 | 19 | * Run update script and then restart the server periodically to stay up on timezone updates. -------------------------------------------------------------------------------- /server/server: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/php 2 | 3 | trim($matches[1]), "posix" => trim($matches[2]))); 17 | } 18 | } 19 | fclose($file); 20 | } 21 | 22 | // Read zone1970.tab 23 | if ($file = fopen("/home/timezoned/download/zone1970.tab", "r")) { 24 | while(!feof($file)) { 25 | $line = fgets($file); 26 | if ($line[0] != "#") { 27 | $columns = explode("\t", $line); 28 | if (count($columns) >= 3) { 29 | $countries = explode(",", $columns[0]); 30 | for ($n = 0; $n < count($countries); $n++) { 31 | $country = trim($countries[$n]); 32 | $insert_at = -1; 33 | $posix = ""; 34 | for ($m = 0; $m < count($tz); $m++) { 35 | if (trim($tz[$m]["olsen"]) == trim($columns[2])) { 36 | $posix = $tz[$m]["posix"]; 37 | if (!isset($tz[$m]["country"])) { 38 | $insert_at = $m; 39 | } 40 | } 41 | } 42 | if ($insert_at == -1) { 43 | $insert_at = count($tz); 44 | array_push($tz, array()); 45 | } 46 | $tz[$insert_at]["country"] = $countries[$n]; 47 | $tz[$insert_at]["coordinates"] = $columns[1]; 48 | $tz[$insert_at]["olsen"] = trim($columns[2]); 49 | if ($posix != "") $tz[$insert_at]["posix"] = $posix; 50 | if (isset ($columns[3])) $tz[$insert_at]["comments"] = $columns[3]; 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | echo "Data read \n"; 58 | 59 | //Create a UDP socket 60 | 61 | if(!($sock = socket_create(AF_INET, SOCK_DGRAM, 0))) 62 | { 63 | $errorcode = socket_last_error(); 64 | $errormsg = socket_strerror($errorcode); 65 | 66 | die("Couldn't create socket: [$errorcode] $errormsg\n"); 67 | } 68 | 69 | echo "Socket created \n"; 70 | 71 | // Bind the source address 72 | if( !socket_bind($sock, "0.0.0.0" , 2342) ) 73 | { 74 | $errorcode = socket_last_error(); 75 | $errormsg = socket_strerror($errorcode); 76 | 77 | die("Could not bind socket : [$errorcode] $errormsg\n"); 78 | } 79 | 80 | echo "Socket bind OK \n"; 81 | 82 | $last_ask = array(); 83 | 84 | //Process packets. This loop can handle multiple clients 85 | while(1) 86 | { 87 | //Receive packet 88 | $r = socket_recvfrom($sock, $packet, 512, 0, $remote_ip, $remote_port); 89 | 90 | // dDoS/flood protection 91 | if (isset($last_ask[$remote_ip]) && $last_ask[$remote_ip] > time() - 3) continue; 92 | $last_ask[$remote_ip] = time(); 93 | 94 | $parts = explode("#", $packet, 2); 95 | $query = $parts[0]; 96 | $version = $parts[1]; 97 | $process = strtoupper(str_replace(" ", "_", $query)); 98 | 99 | $logstart = date("D, d M Y H:i:s") . "Z -- $remote_ip:$remote_port --"; 100 | 101 | // GeoIP ? 102 | if ($process == "GEOIP") { 103 | if (preg_match("/: ([A-Z][A-Z]),/", exec("geoiplookup " . $remote_ip), $matches)) { 104 | $process = $matches[1]; 105 | } else { 106 | echo "$logstart ERR GeoIP Lookup Failed\n"; 107 | socket_sendto($sock, "ERROR GeoIP Lookup Failed", 100 , 0 , $remote_ip , $remote_port); 108 | continue; 109 | } 110 | } 111 | 112 | if ($process == "UK") $process = "GB"; 113 | if ($process == "DE") $process = "EUROPE/BERLIN"; 114 | 115 | 116 | // If a two-letter country-code was provided 117 | if (preg_match('/^[A-Z][A-Z]$/', $process)) { 118 | // Convert to name of timezone if the country happens to have only one timezone 119 | $num_matches = 0; 120 | for ($m = 0; $m < count($tz); $m++) { 121 | if ($tz[$m]["country"] == $process) { 122 | $num_matches++; 123 | $posix = $tz[$m]["posix"]; 124 | $olsen = $tz[$m]["olsen"]; 125 | } 126 | } 127 | switch ($num_matches) { 128 | case 0: 129 | echo "$logstart ERR COUNTRY NOT FOUND: $query\n"; 130 | socket_sendto($sock, "ERROR Country Not Found", 100 , 0 , $remote_ip , $remote_port); 131 | break; 132 | case 1: 133 | echo "$logstart OK $query -> $olsen $posix\n"; 134 | socket_sendto($sock, "OK " . $olsen . " " . $posix , 100 , 0 , $remote_ip , $remote_port); 135 | break; 136 | default: 137 | echo "$logstart ERR MULTIPLE TIMEZONES: $query\n"; 138 | socket_sendto($sock, "ERROR Country Spans Multiple Timezones", 100 , 0 , $remote_ip , $remote_port); 139 | break; 140 | // 141 | } 142 | } else { 143 | $num_matches = 0; 144 | for ($m = 0; $m < count($tz); $m++) { 145 | if (strpos(strtoupper($tz[$m]["olsen"]), $process) !== false) { 146 | $num_matches++; 147 | $posix = $tz[$m]["posix"]; 148 | $olsen = $tz[$m]["olsen"]; 149 | 150 | // Ireland has negative Summer Time as Winter time which messes things up 151 | // See https://github.com/ropg/ezTime/issues/65 if you must know. 152 | if ($olsen == "Europe/Dublin") $posix = "GMT0IST,M3.5.0/1,M10.5.0"; 153 | 154 | echo "$logstart OK $query -> $olsen $posix\n"; 155 | socket_sendto($sock, "OK " . $olsen . " " . $posix , 100 , 0 , $remote_ip , $remote_port); 156 | break; 157 | } 158 | } 159 | if (!$num_matches) { 160 | echo "$logstart ERR TIMEZONE NOT FOUND: $query\n"; 161 | socket_sendto($sock, "ERROR Timezone Not Found", 100 , 0 , $remote_ip , $remote_port); 162 | } 163 | } 164 | } 165 | 166 | socket_close($sock); 167 | -------------------------------------------------------------------------------- /server/timezoned: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # PROVIDE: timezoned 4 | # REQUIRE: LOGIN cleanvar sshd 5 | 6 | . /etc/rc.subr 7 | 8 | name=timezoned 9 | rcvar=timezoned_enable 10 | 11 | : ${timezoned_enable="NO"} 12 | : ${timezoned_pidfile="/var/run/timezoned/timezoned.pid"} 13 | : ${timezoned_user="timezoned"} 14 | : ${timezoned_group="timezoned"} 15 | 16 | command="/usr/sbin/daemon" 17 | command_interpreter="/usr/local/bin/php" 18 | command_args="-c -f -r -P ${timezoned_pidfile} -r /home/timezoned/server" 19 | 20 | load_rc_config $name 21 | 22 | run_rc_command "$1" 23 | -------------------------------------------------------------------------------- /server/update: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf ~timezoned/download 4 | mkdir ~timezoned/download 5 | 6 | rm -rf ~timezoned/zoneinfo 7 | mkdir ~timezoned/zoneinfo 8 | 9 | cd ~timezoned/download 10 | 11 | wget ftp://ftp.iana.org/tz/tzdata-latest.tar.gz 12 | tar zxvf tzdata-latest.tar.gz 13 | rm tzdata-latest.tar.gz 14 | for i in `ls`; do zic -d ~timezoned/zoneinfo $i;done 15 | 16 | rm ~timezoned/posixinfo 17 | 18 | cd ~timezoned/zoneinfo 19 | for i in `find *|grep /` 20 | do 21 | if [ -f $i ]; then 22 | echo -n $i >> ~timezoned/posixinfo 23 | echo -n " " >> ~timezoned/posixinfo 24 | tail -1 $i >> ~timezoned/posixinfo 25 | fi 26 | done 27 | -------------------------------------------------------------------------------- /src/ezTime.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #ifdef EZTIME_NETWORK_ENABLE 6 | #ifdef EZTIME_CACHE_NVS 7 | #include // For timezone lookup cache 8 | #endif 9 | #ifdef EZTIME_CACHE_EEPROM 10 | #include 11 | #endif 12 | #if defined(ESP8266) 13 | #include 14 | #include 15 | #elif defined(ARDUINO_SAMD_MKR1000) 16 | #include 17 | #include 18 | #include 19 | #elif defined(EZTIME_ETHERNET) 20 | #include 21 | #include 22 | #include 23 | #elif defined(EZTIME_WIFIESP) 24 | #include 25 | #include 26 | #else 27 | #include 28 | #include 29 | #endif 30 | #endif 31 | 32 | #if defined(EZTIME_MAX_DEBUGLEVEL_NONE) 33 | #define err(args...) "" 34 | #define errln(args...) "" 35 | #define info(args...) "" 36 | #define infoln(args...) "" 37 | #define debug(args...) "" 38 | #define debugln(args...) "" 39 | #elif defined(EZTIME_MAX_DEBUGLEVEL_ERROR) 40 | #define err(args...) if (_debug_level >= ERROR) _debug_device->print(args) 41 | #define errln(args...) if (_debug_level >= ERROR) _debug_device->println(args) 42 | #define info(args...) "" 43 | #define infoln(args...) "" 44 | #define debug(args...) "" 45 | #define debugln(args...) "" 46 | #elif defined(EZTIME_MAX_DEBUGLEVEL_INFO) 47 | #define err(args...) if (_debug_level >= ERROR) _debug_device->print(args) 48 | #define errln(args...) if (_debug_level >= ERROR) _debug_device->println(args) 49 | #define info(args...) if (_debug_level >= INFO) _debug_device->print(args) 50 | #define infoln(args...) if (_debug_level >= INFO) _debug_device->println(args) 51 | #define debug(args...) "" 52 | #define debugln(args...) "" 53 | #else // nothing specified compiles everything in. 54 | #define err(args...) if (_debug_level >= ERROR) _debug_device->print(args) 55 | #define errln(args...) if (_debug_level >= ERROR) _debug_device->println(args) 56 | #define info(args...) if (_debug_level >= INFO) _debug_device->print(args) 57 | #define infoln(args...) if (_debug_level >= INFO) _debug_device->println(args) 58 | #define debug(args...) if (_debug_level >= DEBUG) _debug_device->print(args) 59 | #define debugln(args...) if (_debug_level >= DEBUG) _debug_device->println(args) 60 | #endif 61 | 62 | 63 | const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 64 | 65 | // The private things go in an anonymous namespace 66 | namespace { 67 | 68 | ezError_t _last_error = NO_ERROR; 69 | String _server_error = ""; 70 | ezDebugLevel_t _debug_level = NONE; 71 | Print *_debug_device = (Print *)&Serial; 72 | ezEvent_t _events[MAX_EVENTS]; 73 | time_t _last_sync_time = 0; 74 | time_t _last_read_t = 0; 75 | uint32_t _last_sync_millis = 0; 76 | uint16_t _last_read_ms; 77 | timeStatus_t _time_status; 78 | bool _initialised = false; 79 | #ifdef EZTIME_NETWORK_ENABLE 80 | uint16_t _ntp_interval = NTP_INTERVAL; 81 | String _ntp_server = NTP_SERVER; 82 | #endif 83 | 84 | void triggerError(const ezError_t err) { 85 | _last_error = err; 86 | if (_last_error) { 87 | err(F("ERROR: ")); 88 | errln(ezt::errorString(err)); 89 | } 90 | } 91 | 92 | String debugLevelString(const ezDebugLevel_t level) { 93 | switch (level) { 94 | case NONE: return F("NONE"); 95 | case ERROR: return F("ERROR"); 96 | case INFO: return F("INFO"); 97 | default: return F("DEBUG"); 98 | } 99 | } 100 | 101 | time_t nowUTC(const bool update_last_read = true) { 102 | time_t t; 103 | uint32_t m = millis(); 104 | t = _last_sync_time + ((m - _last_sync_millis) / 1000); 105 | if (update_last_read) { 106 | _last_read_t = t; 107 | _last_read_ms = (m - _last_sync_millis) % 1000; 108 | } 109 | return t; 110 | } 111 | 112 | } 113 | 114 | 115 | 116 | namespace ezt { 117 | 118 | ////////// Error handing 119 | 120 | String errorString(const ezError_t err /* = LAST_ERROR */) { 121 | switch (err) { 122 | case NO_ERROR: return F("OK"); 123 | case LAST_ERROR: return errorString(_last_error); 124 | case NO_NETWORK: return F("No network"); 125 | case TIMEOUT: return F("Timeout"); 126 | case CONNECT_FAILED: return F("Connect Failed"); 127 | case DATA_NOT_FOUND: return F("Data not found"); 128 | case LOCKED_TO_UTC: return F("Locked to UTC"); 129 | case NO_CACHE_SET: return F("No cache set"); 130 | case CACHE_TOO_SMALL: return F("Cache too small"); 131 | case TOO_MANY_EVENTS: return F("Too many events"); 132 | case INVALID_DATA: return F("Invalid data received from NTP server"); 133 | case SERVER_ERROR: return _server_error; 134 | default: return F("Unkown error"); 135 | } 136 | } 137 | 138 | ezError_t error(const bool reset /* = false */) { 139 | ezError_t tmp = _last_error; 140 | if (reset) _last_error = NO_ERROR; 141 | return tmp; 142 | } 143 | 144 | void setDebug(const ezDebugLevel_t level) { 145 | setDebug(level, *_debug_device); 146 | } 147 | 148 | void setDebug(const ezDebugLevel_t level, Print &device) { 149 | _debug_level = level; 150 | _debug_device = &device; 151 | info(F("\r\nezTime debug level set to ")); 152 | infoln(debugLevelString(level)); 153 | } 154 | 155 | 156 | // The include below includes the dayStr, dayShortStr, monthStr and monthShortStr from the appropriate language file 157 | // in the /src/lang subdirectory. 158 | 159 | 160 | #ifdef EZTIME_LANGUAGE 161 | #define XSTR(x) #x 162 | #define STR(x) XSTR(x) 163 | #include STR(lang/EZTIME_LANGUAGE) 164 | #else 165 | #include "lang/EN" 166 | #endif 167 | 168 | // 169 | 170 | timeStatus_t timeStatus() { return _time_status; } 171 | 172 | void events() { 173 | if (!_initialised) { 174 | for (uint8_t n = 0; n < MAX_EVENTS; n++) _events[n] = { 0, NULL }; 175 | #ifdef EZTIME_NETWORK_ENABLE 176 | if (_ntp_interval) updateNTP(); // Start the cycle of updateNTP running and then setting an event for its next run 177 | #endif 178 | _initialised = true; 179 | } 180 | // See if any events are due 181 | for (uint8_t n = 0; n < MAX_EVENTS; n++) { 182 | if (_events[n].function && nowUTC(false) >= _events[n].time) { 183 | debug(F("Running event (#")); debug(n + 1); debug(F(") set for ")); debugln(UTC.dateTime(_events[n].time)); 184 | void (*tmp)() = _events[n].function; 185 | _events[n] = { 0, NULL }; // reset the event 186 | (tmp)(); // execute the function 187 | } 188 | } 189 | yield(); 190 | } 191 | 192 | void deleteEvent(const uint8_t event_handle) { 193 | if (event_handle && event_handle <= MAX_EVENTS) { 194 | debug(F("Deleted event (#")); debug(event_handle); debug(F("), set for ")); debugln(UTC.dateTime(_events[event_handle - 1].time)); 195 | _events[event_handle - 1] = { 0, NULL }; 196 | } 197 | } 198 | 199 | void deleteEvent(void (*function)()) { 200 | for (uint8_t n = 0; n< MAX_EVENTS; n++) { 201 | if (_events[n].function == function) { 202 | debug(F("Deleted event (#")); debug(n + 1); debug(F("), set for ")); debugln(UTC.dateTime(_events[n].time)); 203 | _events[n] = { 0, NULL }; 204 | } 205 | } 206 | } 207 | 208 | void breakTime(const time_t timeInput, tmElements_t &tm){ 209 | // break the given time_t into time components 210 | // this is a more compact version of the C library localtime function 211 | // note that year is offset from 1970 !!! 212 | 213 | uint8_t year; 214 | uint8_t month, monthLength; 215 | uint32_t time; 216 | unsigned long days; 217 | 218 | time = (uint32_t)timeInput; 219 | tm.Second = time % 60; 220 | time /= 60; // now it is minutes 221 | tm.Minute = time % 60; 222 | time /= 60; // now it is hours 223 | tm.Hour = time % 24; 224 | time /= 24; // now it is days 225 | tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 226 | 227 | year = 0; 228 | days = 0; 229 | while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { 230 | year++; 231 | } 232 | tm.Year = year; // year is offset from 1970 233 | 234 | days -= LEAP_YEAR(year) ? 366 : 365; 235 | time -= days; // now it is days in this year, starting at 0 236 | 237 | days=0; 238 | month=0; 239 | monthLength=0; 240 | for (month=0; month<12; month++) { 241 | if (month==1) { // february 242 | if (LEAP_YEAR(year)) { 243 | monthLength=29; 244 | } else { 245 | monthLength=28; 246 | } 247 | } else { 248 | monthLength = monthDays[month]; 249 | } 250 | 251 | if (time >= monthLength) { 252 | time -= monthLength; 253 | } else { 254 | break; 255 | } 256 | } 257 | tm.Month = month + 1; // jan is month 1 258 | tm.Day = time + 1; // day of month 259 | } 260 | 261 | time_t makeTime(const uint8_t hour, const uint8_t minute, const uint8_t second, const uint8_t day, const uint8_t month, const uint16_t year) { 262 | tmElements_t tm; 263 | tm.Hour = hour; 264 | tm.Minute = minute; 265 | tm.Second = second; 266 | tm.Day = day; 267 | tm.Month = month; 268 | if (year > 68) { // time_t cannot reach beyond 68 + 1970 anyway, so if bigger user means actual years 269 | tm.Year = year - 1970; 270 | } else { 271 | tm.Year = year; 272 | } 273 | return makeTime(tm); 274 | } 275 | 276 | time_t makeTime(tmElements_t &tm){ 277 | // assemble time elements into time_t 278 | // note year argument is offset from 1970 (see macros in time.h to convert to other formats) 279 | // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 280 | 281 | int i; 282 | uint32_t seconds; 283 | 284 | // seconds from 1970 till 1 jan 00:00:00 of the given year 285 | seconds= tm.Year * SECS_PER_DAY * 365UL; 286 | 287 | for (i = 0; i < tm.Year; i++) { 288 | if (LEAP_YEAR(i)) { 289 | seconds += SECS_PER_DAY; // add extra days for leap years 290 | } 291 | } 292 | 293 | // add days for this year, months start from 1 294 | for (i = 1; i < tm.Month; i++) { 295 | if ( (i == 2) && LEAP_YEAR(tm.Year)) { 296 | seconds += SECS_PER_DAY * 29UL; 297 | } else { 298 | seconds += SECS_PER_DAY * (uint32_t)monthDays[i-1]; //monthDay array starts from 0 299 | } 300 | } 301 | 302 | seconds+= (tm.Day-1) * SECS_PER_DAY; 303 | seconds+= tm.Hour * 3600UL; 304 | seconds+= tm.Minute * 60UL; 305 | seconds+= tm.Second; 306 | 307 | return (time_t)seconds; 308 | } 309 | 310 | // makeOrdinalTime allows you to resolve "second thursday in September in 2018" into a number of seconds since 1970 311 | // (Very useful for the timezone calculations that ezTime does internally) 312 | // If ordinal is 0 or 5 it is taken to mean "the last $wday in $month" 313 | time_t makeOrdinalTime(const uint8_t hour, const uint8_t minute, uint8_t const second, uint8_t ordinal, const uint8_t wday, const uint8_t month, uint16_t year) { 314 | if (year <= 68 ) year = 1970 + year; // fix user intent 315 | uint8_t m = month; 316 | uint8_t w = ordinal; 317 | if (w == 5) { 318 | ordinal = 0; 319 | w = 0; 320 | } 321 | if (w == 0) { // is this a "Last week" rule? 322 | if (++m > 12) { // yes, for "Last", go to the next month 323 | m = 1; 324 | ++year; 325 | } 326 | w = 1; // and treat as first week of next month, subtract 7 days later 327 | } 328 | time_t t = makeTime(hour, minute, second, 1, m, year); 329 | // add offset from the first of the month to weekday, and offset for the given week 330 | t += ( (wday - UTC.weekday(t) + 7) % 7 + (w - 1) * 7 ) * SECS_PER_DAY; 331 | // back up a week if this is a "Last" rule 332 | if (ordinal == 0) t -= 7 * SECS_PER_DAY; 333 | return t; 334 | } 335 | 336 | String zeropad(const uint32_t number, const uint8_t length) { 337 | String out; 338 | out.reserve(length); 339 | out = String(number); 340 | while (out.length() < length) out = "0" + out; 341 | return out; 342 | } 343 | 344 | time_t compileTime(const String compile_date /* = __DATE__ */, const String compile_time /* = __TIME__ */) { 345 | 346 | uint8_t hrs = compile_time.substring(0,2).toInt(); 347 | uint8_t min = compile_time.substring(3,5).toInt(); 348 | uint8_t sec = compile_time.substring(6).toInt(); 349 | uint8_t day = compile_date.substring(4,6).toInt(); 350 | int16_t year = compile_date.substring(7).toInt(); 351 | String iterate_month; 352 | for (uint8_t month = 1; month < 13; month++) { 353 | iterate_month = monthStr(month); 354 | if ( iterate_month.substring(0,3) == compile_date.substring(0,3) ) { 355 | return makeTime(hrs, min, sec, day, month, year); 356 | } 357 | } 358 | return 0; 359 | } 360 | 361 | bool secondChanged() { 362 | time_t t = nowUTC(false); 363 | if (_last_read_t != t) return true; 364 | return false; 365 | } 366 | 367 | bool minuteChanged() { 368 | time_t t = nowUTC(false); 369 | if (_last_read_t / 60 != t / 60) return true; 370 | return false; 371 | } 372 | 373 | 374 | #ifdef EZTIME_NETWORK_ENABLE 375 | 376 | void updateNTP() { 377 | deleteEvent(updateNTP); // Delete any events pointing here, in case called manually 378 | time_t t; 379 | unsigned long measured_at; 380 | if (queryNTP(_ntp_server, t, measured_at)) { 381 | int32_t correction = ( (t - _last_sync_time) * 1000 ) - ( measured_at - _last_sync_millis ); 382 | _last_sync_time = t; 383 | _last_sync_millis = measured_at; 384 | _last_read_ms = ( millis() - measured_at) % 1000; 385 | info(F("Received time: ")); 386 | info(UTC.dateTime(t, F("l, d-M-y H:i:s.v T"))); 387 | if (_time_status != timeNotSet) { 388 | info(F(" (internal clock was ")); 389 | if (!correction) { 390 | infoln(F("spot on)")); 391 | } else { 392 | info(String(abs(correction))); 393 | if (correction > 0) { 394 | infoln(F(" ms fast)")); 395 | } else { 396 | infoln(F(" ms slow)")); 397 | } 398 | } 399 | } else { 400 | infoln(""); 401 | } 402 | if (_ntp_interval) UTC.setEvent(updateNTP, t + _ntp_interval); 403 | _time_status = timeSet; 404 | } else { 405 | if ( nowUTC(false) > _last_sync_time + _ntp_interval + NTP_STALE_AFTER ) { 406 | _time_status = timeNeedsSync; 407 | } 408 | UTC.setEvent(updateNTP, nowUTC(false) + NTP_RETRY); 409 | } 410 | } 411 | 412 | // This is a nice self-contained NTP routine if you need one: feel free to use it. 413 | // It gives you the seconds since 1970 (unix epoch) and the millis() on your system when 414 | // that happened (by deducting fractional seconds and estimated network latency). 415 | bool queryNTP(const String server, time_t &t, unsigned long &measured_at) { 416 | info(F("Querying ")); 417 | info(server); 418 | info(F(" ... ")); 419 | 420 | #ifndef EZTIME_ETHERNET 421 | if (WiFi.status() != WL_CONNECTED) { triggerError(NO_NETWORK); return false; } 422 | #ifndef EZTIME_WIFIESP 423 | WiFiUDP udp; 424 | #else 425 | WiFiEspUDP udp; 426 | #endif 427 | #else 428 | EthernetUDP udp; 429 | #endif 430 | 431 | // Send NTP packet 432 | byte buffer[NTP_PACKET_SIZE]; 433 | memset(buffer, 0, NTP_PACKET_SIZE); 434 | buffer[0] = 0b11100011; // LI, Version, Mode 435 | buffer[1] = 0; // Stratum, or type of clock 436 | buffer[2] = 9; // Polling Interval (9 = 2^9 secs = ~9 mins, close to our 10 min default) 437 | buffer[3] = 0xEC; // Peer Clock Precision 438 | // 8 bytes of zero for Root Delay & Root Dispersion 439 | buffer[12] = 'X'; // "kiss code", see RFC5905 440 | buffer[13] = 'E'; // (codes starting with 'X' are not interpreted) 441 | buffer[14] = 'Z'; 442 | buffer[15] = 'T'; 443 | 444 | udp.flush(); 445 | udp.begin(NTP_LOCAL_PORT); 446 | unsigned long started = millis(); 447 | udp.beginPacket(server.c_str(), 123); //NTP requests are to port 123 448 | udp.write(buffer, NTP_PACKET_SIZE); 449 | udp.endPacket(); 450 | 451 | // Wait for packet or return false with timed out 452 | while (!udp.parsePacket()) { 453 | delay (1); 454 | if (millis() - started > NTP_TIMEOUT) { 455 | udp.stop(); 456 | triggerError(TIMEOUT); 457 | return false; 458 | } 459 | } 460 | udp.read(buffer, NTP_PACKET_SIZE); 461 | udp.stop(); // On AVR there's only very limited sockets, we want to free them when done. 462 | 463 | //print out received packet for debug 464 | int i; 465 | debug(F("Received data:")); 466 | for (i = 0; i < NTP_PACKET_SIZE; i++) { 467 | if ((i % 4) == 0) { 468 | debugln(); 469 | debug(String(i) + ": "); 470 | } 471 | debug(buffer[i], HEX); 472 | debug(F(", ")); 473 | } 474 | debugln(); 475 | 476 | //prepare timestamps 477 | uint32_t highWord, lowWord; 478 | highWord = ( buffer[16] << 8 | buffer[17] ) & 0x0000FFFF; 479 | lowWord = ( buffer[18] << 8 | buffer[19] ) & 0x0000FFFF; 480 | uint32_t reftsSec = highWord << 16 | lowWord; // reference timestamp seconds 481 | 482 | highWord = ( buffer[32] << 8 | buffer[33] ) & 0x0000FFFF; 483 | lowWord = ( buffer[34] << 8 | buffer[35] ) & 0x0000FFFF; 484 | uint32_t rcvtsSec = highWord << 16 | lowWord; // receive timestamp seconds 485 | 486 | highWord = ( buffer[40] << 8 | buffer[41] ) & 0x0000FFFF; 487 | lowWord = ( buffer[42] << 8 | buffer[43] ) & 0x0000FFFF; 488 | uint32_t secsSince1900 = highWord << 16 | lowWord; // transmit timestamp seconds 489 | 490 | highWord = ( buffer[44] << 8 | buffer[45] ) & 0x0000FFFF; 491 | lowWord = ( buffer[46] << 8 | buffer[47] ) & 0x0000FFFF; 492 | uint32_t fraction = highWord << 16 | lowWord; // transmit timestamp fractions 493 | 494 | //check if received data makes sense 495 | //buffer[1] = stratum - should be 1..15 for valid reply 496 | //also checking that all timestamps are non-zero and receive timestamp seconds are <= transmit timestamp seconds 497 | if ((buffer[1] < 1) or (buffer[1] > 15) or (reftsSec == 0) or (rcvtsSec == 0) or (rcvtsSec > secsSince1900)) { 498 | // we got invalid packet 499 | triggerError(INVALID_DATA); 500 | return false; 501 | } 502 | 503 | // Set the t and measured_at variables that were passed by reference 504 | uint32_t done = millis(); 505 | info(F("success (round trip ")); info(done - started); infoln(F(" ms)")); 506 | t = secsSince1900 - 2208988800UL; // Subtract 70 years to get seconds since 1970 507 | uint16_t ms = fraction / 4294967UL; // Turn 32 bit fraction into ms by dividing by 2^32 / 1000 508 | measured_at = done - ((done - started) / 2) - ms; // Assume symmetric network latency and return when we think the whole second was. 509 | 510 | return true; 511 | } 512 | 513 | void setInterval(const uint16_t seconds /* = 0 */) { 514 | deleteEvent(updateNTP); 515 | _ntp_interval = seconds; 516 | if (seconds) UTC.setEvent(updateNTP, nowUTC(false) + _ntp_interval); 517 | } 518 | 519 | void setServer(const String ntp_server /* = NTP_SERVER */) { _ntp_server = ntp_server; } 520 | 521 | bool waitForSync(const uint16_t timeout /* = 0 */) { 522 | 523 | unsigned long start = millis(); 524 | 525 | #if !defined(EZTIME_ETHERNET) 526 | if (WiFi.status() != WL_CONNECTED) { 527 | info(F("Waiting for WiFi ... ")); 528 | while (WiFi.status() != WL_CONNECTED) { 529 | if ( timeout && (millis() - start) / 1000 > timeout ) { triggerError(TIMEOUT); return false;}; 530 | events(); 531 | delay(25); 532 | } 533 | infoln(F("connected")); 534 | } 535 | #endif 536 | 537 | if (_time_status != timeSet) { 538 | infoln(F("Waiting for time sync")); 539 | while (_time_status != timeSet) { 540 | if ( timeout && (millis() - start) / 1000 > timeout ) { triggerError(TIMEOUT); return false;}; 541 | delay(250); 542 | events(); 543 | } 544 | infoln(F("Time is in sync")); 545 | } 546 | return true; 547 | } 548 | 549 | time_t lastNtpUpdateTime() { return _last_sync_time; } 550 | 551 | #endif // EZTIME_NETWORK_ENABLE 552 | 553 | } 554 | 555 | 556 | // 557 | // Timezone class 558 | // 559 | 560 | Timezone::Timezone(const bool locked_to_UTC /* = false */) { 561 | _locked_to_UTC = locked_to_UTC; 562 | _posix = "UTC"; 563 | #ifdef EZTIME_NETWORK_ENABLE 564 | #ifdef EZTIME_CACHE_EEPROM 565 | _cache_month = 0; 566 | _eeprom_address = -1; 567 | #endif 568 | #ifdef EZTIME_CACHE_NVS 569 | _cache_month = 0; 570 | _nvs_name = ""; 571 | _nvs_key = ""; 572 | #endif 573 | _olson = ""; 574 | #endif 575 | } 576 | 577 | bool Timezone::setPosix(const String posix) { 578 | if (_locked_to_UTC) { triggerError(LOCKED_TO_UTC); return false; } 579 | _posix = posix; 580 | #ifdef EZTIME_NETWORK_ENABLE 581 | _olson = ""; 582 | #endif 583 | return true; 584 | } 585 | 586 | time_t Timezone::now() { return tzTime(); } 587 | 588 | time_t Timezone::tzTime(time_t t /* = TIME_NOW */, ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 589 | if (_locked_to_UTC) return nowUTC(); // just saving some time and memory 590 | String tzname; 591 | bool is_dst; 592 | int16_t offset; 593 | return tzTime(t, local_or_utc, tzname, is_dst, offset); 594 | } 595 | 596 | time_t Timezone::tzTime(time_t t, ezLocalOrUTC_t local_or_utc, String &tzname, bool &is_dst, int16_t &offset) { 597 | 598 | if (t == TIME_NOW) { 599 | t = nowUTC(); 600 | local_or_utc = UTC_TIME; 601 | } else if (t == LAST_READ) { 602 | t = _last_read_t; 603 | local_or_utc = UTC_TIME; 604 | } 605 | 606 | int8_t offset_hr = 0; 607 | uint8_t offset_min = 0; 608 | int8_t dst_shift_hr = 1; 609 | uint8_t dst_shift_min = 0; 610 | 611 | uint8_t start_month = 0, start_week = 0, start_dow = 0, start_time_hr = 2, start_time_min = 0; 612 | uint8_t end_month = 0, end_week = 0, end_dow = 0, end_time_hr = 2, end_time_min = 0; 613 | 614 | enum posix_state_e {STD_NAME, OFFSET_HR, OFFSET_MIN, DST_NAME, DST_SHIFT_HR, DST_SHIFT_MIN, START_MONTH, START_WEEK, START_DOW, START_TIME_HR, START_TIME_MIN, END_MONTH, END_WEEK, END_DOW, END_TIME_HR, END_TIME_MIN}; 615 | posix_state_e state = STD_NAME; 616 | 617 | bool ignore_nums = false; 618 | char c = 1; // Dummy value to get while(newchar) started 619 | uint8_t strpos = 0; 620 | uint8_t stdname_end = _posix.length() - 1; 621 | uint8_t dstname_begin = _posix.length(); 622 | uint8_t dstname_end = _posix.length(); 623 | 624 | while (strpos < _posix.length()) { 625 | c = (char)_posix[strpos]; 626 | 627 | // Do not replace the code below with switch statement: evaluation of state that 628 | // changes while this runs. (Only works because this state can only go forward.) 629 | 630 | if (c && state == STD_NAME) { 631 | if (c == '<') ignore_nums = true; 632 | if (c == '>') ignore_nums = false; 633 | if (!ignore_nums && (isDigit(c) || c == '-' || c == '+')) { 634 | state = OFFSET_HR; 635 | stdname_end = strpos - 1; 636 | } 637 | } 638 | if (c && state == OFFSET_HR) { 639 | if (c == '+') { 640 | // Ignore the plus 641 | } else if (c == ':') { 642 | state = OFFSET_MIN; 643 | c = 0; 644 | } else if (c != '-' && !isDigit(c)) { 645 | state = DST_NAME; 646 | dstname_begin = strpos; 647 | } else { 648 | if (!offset_hr) offset_hr = atoi(_posix.c_str() + strpos); 649 | } 650 | } 651 | if (c && state == OFFSET_MIN) { 652 | if (!isDigit(c)) { 653 | state = DST_NAME; 654 | dstname_begin = strpos; 655 | ignore_nums = false; 656 | } else { 657 | if (!offset_min) offset_min = atoi(_posix.c_str() + strpos); 658 | } 659 | } 660 | if (c && state == DST_NAME) { 661 | if (c == '<') ignore_nums = true; 662 | if (c == '>') ignore_nums = false; 663 | if (c == ',') { 664 | state = START_MONTH; 665 | c = 0; 666 | dstname_end = strpos - 1; 667 | } else if (!ignore_nums && (c == '-' || isDigit(c))) { 668 | state = DST_SHIFT_HR; 669 | dstname_end = strpos - 1; 670 | } 671 | } 672 | if (c && state == DST_SHIFT_HR) { 673 | if (c == ':') { 674 | state = DST_SHIFT_MIN; 675 | c = 0; 676 | } else if (c == ',') { 677 | state = START_MONTH; 678 | c = 0; 679 | } else if (dst_shift_hr == 1) dst_shift_hr = atoi(_posix.c_str() + strpos); 680 | } 681 | if (c && state == DST_SHIFT_MIN) { 682 | if (c == ',') { 683 | state = START_MONTH; 684 | c = 0; 685 | } else if (!dst_shift_min) dst_shift_min = atoi(_posix.c_str() + strpos); 686 | } 687 | if (c && state == START_MONTH) { 688 | if (c == '.') { 689 | state = START_WEEK; 690 | c = 0; 691 | } else if (c != 'M' && !start_month) start_month = atoi(_posix.c_str() + strpos); 692 | } 693 | if (c && state == START_WEEK) { 694 | if (c == '.') { 695 | state = START_DOW; 696 | c = 0; 697 | } else start_week = c - '0'; 698 | } 699 | if (c && state == START_DOW) { 700 | if (c == '/') { 701 | state = START_TIME_HR; 702 | c = 0; 703 | } else if (c == ',') { 704 | state = END_MONTH; 705 | c = 0; 706 | } else start_dow = c - '0'; 707 | } 708 | if (c && state == START_TIME_HR) { 709 | if (c == ':') { 710 | state = START_TIME_MIN; 711 | c = 0; 712 | } else if (c == ',') { 713 | state = END_MONTH; 714 | c = 0; 715 | } else if (start_time_hr == 2) start_time_hr = atoi(_posix.c_str() + strpos); 716 | } 717 | if (c && state == START_TIME_MIN) { 718 | if (c == ',') { 719 | state = END_MONTH; 720 | c = 0; 721 | } else if (!start_time_min) start_time_min = atoi(_posix.c_str() + strpos); 722 | } 723 | if (c && state == END_MONTH) { 724 | if (c == '.') { 725 | state = END_WEEK; 726 | c = 0; 727 | } else if (c != 'M') if (!end_month) end_month = atoi(_posix.c_str() + strpos); 728 | } 729 | if (c && state == END_WEEK) { 730 | if (c == '.') { 731 | state = END_DOW; 732 | c = 0; 733 | } else end_week = c - '0'; 734 | } 735 | if (c && state == END_DOW) { 736 | if (c == '/') { 737 | state = END_TIME_HR; 738 | c = 0; 739 | } else end_dow = c - '0'; 740 | } 741 | if (c && state == END_TIME_HR) { 742 | if (c == ':') { 743 | state = END_TIME_MIN; 744 | c = 0; 745 | } else if (end_time_hr == 2) end_time_hr = atoi(_posix.c_str() + strpos); 746 | } 747 | if (c && state == END_TIME_MIN) { 748 | if (!end_time_min) end_time_min = atoi(_posix.c_str() + strpos); 749 | } 750 | strpos++; 751 | } 752 | 753 | int16_t std_offset = (offset_hr < 0) ? offset_hr * 60 - offset_min : offset_hr * 60 + offset_min; 754 | 755 | tzname = _posix.substring(0, stdname_end + 1); // Overwritten with dstname later if needed 756 | if (!start_month) { 757 | if (tzname == "UTC" && std_offset) tzname = "???"; 758 | is_dst = false; 759 | offset = std_offset; 760 | } else { 761 | int16_t dst_offset = std_offset - dst_shift_hr * 60 - dst_shift_min; 762 | // to find the year 763 | tmElements_t tm; 764 | ezt::breakTime(t, tm); 765 | 766 | // in local time 767 | time_t dst_start = ezt::makeOrdinalTime(start_time_hr, start_time_min, 0, start_week, start_dow + 1, start_month, tm.Year + 1970); 768 | time_t dst_end = ezt::makeOrdinalTime(end_time_hr, end_time_min, 0, end_week, end_dow + 1, end_month, tm.Year + 1970); 769 | 770 | if (local_or_utc == UTC_TIME) { 771 | dst_start += std_offset * 60LL; 772 | dst_end += dst_offset * 60LL; 773 | } 774 | 775 | if (dst_end > dst_start) { 776 | is_dst = (t >= dst_start && t < dst_end); // northern hemisphere 777 | } else { 778 | is_dst = !(t >= dst_end && t < dst_start); // southern hemisphere 779 | } 780 | 781 | if (is_dst) { 782 | offset = dst_offset; 783 | tzname = _posix.substring(dstname_begin, dstname_end + 1); 784 | } else { 785 | offset = std_offset; 786 | } 787 | } 788 | 789 | if (local_or_utc == LOCAL_TIME) { 790 | return t + offset * 60LL; 791 | } else { 792 | return t - offset * 60LL; 793 | } 794 | } 795 | 796 | String Timezone::getPosix() { return _posix; } 797 | 798 | #ifdef EZTIME_NETWORK_ENABLE 799 | 800 | bool Timezone::setLocation(const String location /* = "GeoIP" */) { 801 | 802 | info(F("Timezone lookup for: ")); 803 | info(location); 804 | info(F(" ... ")); 805 | if (_locked_to_UTC) { triggerError(LOCKED_TO_UTC); return false; } 806 | 807 | #ifndef EZTIME_ETHERNET 808 | if (WiFi.status() != WL_CONNECTED) { triggerError(NO_NETWORK); return false; } 809 | #ifndef EZTIME_WIFIESP 810 | WiFiUDP udp; 811 | #else 812 | WiFiEspUDP udp; 813 | #endif 814 | #else 815 | EthernetUDP udp; 816 | #endif 817 | 818 | udp.flush(); 819 | udp.begin(TIMEZONED_LOCAL_PORT); 820 | unsigned long started = millis(); 821 | udp.beginPacket(TIMEZONED_REMOTE_HOST, TIMEZONED_REMOTE_PORT); 822 | udp.write((const uint8_t*)location.c_str(), location.length()); 823 | udp.endPacket(); 824 | 825 | // Wait for packet or return false with timed out 826 | while (!udp.parsePacket()) { 827 | delay (1); 828 | if (millis() - started > TIMEZONED_TIMEOUT) { 829 | udp.stop(); 830 | triggerError(TIMEOUT); 831 | return false; 832 | } 833 | } 834 | // Stick result in String recv 835 | String recv; 836 | recv.reserve(60); 837 | while (udp.available()) recv += (char)udp.read(); 838 | udp.stop(); 839 | info(F("(round-trip ")); 840 | info(millis() - started); 841 | info(F(" ms) ")); 842 | if (recv.substring(0,6) == "ERROR ") { 843 | _server_error = recv.substring(6); 844 | error (SERVER_ERROR); 845 | return false; 846 | } 847 | if (recv.substring(0,3) == "OK ") { 848 | _olson = recv.substring(3, recv.indexOf(" ", 4)); 849 | _posix = recv.substring(recv.indexOf(" ", 4) + 1); 850 | infoln(F("success.")); 851 | info(F(" Olson: ")); infoln(_olson); 852 | info(F(" Posix: ")); infoln(_posix); 853 | #if defined(EZTIME_CACHE_EEPROM) || defined(EZTIME_CACHE_NVS) 854 | String tzinfo = _olson + " " + _posix; 855 | writeCache(tzinfo); // caution, byref to save memory, tzinfo mangled afterwards 856 | #endif 857 | return true; 858 | } 859 | error (DATA_NOT_FOUND); 860 | return false; 861 | } 862 | 863 | 864 | String Timezone::getOlson() { 865 | return _olson; 866 | } 867 | 868 | String Timezone::getOlsen() { 869 | return _olson; 870 | } 871 | 872 | 873 | #if defined(EZTIME_CACHE_EEPROM) || defined(EZTIME_CACHE_NVS) 874 | 875 | #if defined(ESP32) || defined(ESP8266) 876 | #define eepromBegin() EEPROM.begin(4096) 877 | #define eepromEnd() EEPROM.end() 878 | #define eepromLength() (4096) 879 | #else 880 | #define eepromBegin() "" 881 | #define eepromEnd() "" 882 | #define eepromLength() EEPROM.length() 883 | #endif 884 | 885 | #ifdef EZTIME_CACHE_EEPROM 886 | bool Timezone::setCache(const int16_t address) { 887 | eepromBegin(); 888 | if (address + EEPROM_CACHE_LEN > eepromLength()) { triggerError(CACHE_TOO_SMALL); return false; } 889 | _eeprom_address = address; 890 | eepromEnd(); 891 | return setCache(); 892 | } 893 | #endif 894 | 895 | #ifdef EZTIME_CACHE_NVS 896 | bool Timezone::setCache(const String name, const String key) { 897 | _nvs_name = name; 898 | _nvs_key = key; 899 | return setCache(); 900 | } 901 | #endif 902 | 903 | bool Timezone::setCache() { 904 | String olson, posix; 905 | uint8_t months_since_jan_2018; 906 | if (readCache(olson, posix, months_since_jan_2018)) { 907 | setPosix(posix); 908 | _olson = olson; 909 | _cache_month = months_since_jan_2018; 910 | if ( (year() - 2018) * 12 + month(LAST_READ) - months_since_jan_2018 > MAX_CACHE_AGE_MONTHS) { 911 | infoln(F("Cache stale, getting fresh")); 912 | setLocation(olson); 913 | } 914 | return true; 915 | } 916 | return false; 917 | } 918 | 919 | void Timezone::clearCache(const bool delete_section /* = false */) { 920 | 921 | #ifdef EZTIME_CACHE_EEPROM 922 | eepromBegin(); 923 | if (_eeprom_address < 0) { triggerError(NO_CACHE_SET); return; } 924 | for (int16_t n = _eeprom_address; n < _eeprom_address + EEPROM_CACHE_LEN; n++) EEPROM.write(n, 0); 925 | eepromEnd(); 926 | #endif 927 | 928 | #ifdef EZTIME_CACHE_NVS 929 | if (_nvs_name == "" || _nvs_key == "") { triggerError(NO_CACHE_SET); return; } 930 | Preferences prefs; 931 | prefs.begin(_nvs_name.c_str(), false); 932 | if (delete_section) { 933 | prefs.clear(); 934 | } else { 935 | prefs.remove(_nvs_key.c_str()); 936 | } 937 | prefs.end(); 938 | #endif 939 | } 940 | 941 | bool Timezone::writeCache(String &str) { 942 | uint8_t months_since_jan_2018 = 0; 943 | if (year() >= 2018) months_since_jan_2018 = (year(LAST_READ) - 2018) * 12 + month(LAST_READ) - 1; 944 | 945 | #ifdef EZTIME_CACHE_EEPROM 946 | if (_eeprom_address < 0) return false; 947 | 948 | info(F("Caching timezone data ")); 949 | if (str.length() > MAX_CACHE_PAYLOAD) { triggerError(CACHE_TOO_SMALL); return false; } 950 | 951 | uint16_t last_byte = _eeprom_address + EEPROM_CACHE_LEN - 1; 952 | uint16_t addr = _eeprom_address; 953 | 954 | eepromBegin(); 955 | 956 | // First byte is cache age, in months since 2018 957 | EEPROM.write(addr++, months_since_jan_2018); 958 | 959 | // Second byte is length of payload 960 | EEPROM.write(addr++, str.length()); 961 | 962 | // Followed by payload, compressed. Every 4 bytes to three by encoding only 6 bits, ASCII all-caps 963 | str.toUpperCase(); 964 | uint8_t store = 0; 965 | for (uint8_t n = 0; n < str.length(); n++) { 966 | unsigned char c = str.charAt(n) - 32; 967 | if ( c > 63) c = 0; 968 | switch (n % 4) { 969 | case 0: 970 | store = c << 2; //all of 1st 971 | break; 972 | case 1: 973 | store |= c >> 4; //high two of 2nd 974 | EEPROM.write(addr++, store); 975 | store = c << 4; //low four of 2nd 976 | break; 977 | case 2: 978 | store |= c >> 2; //high four of 3rd 979 | EEPROM.write(addr++, store); 980 | store = c << 6; //low two of third 981 | break; 982 | case 3: 983 | store |= c; //all of 4th 984 | EEPROM.write(addr++, store); 985 | store = 0; 986 | } 987 | } 988 | if (store) EEPROM.write(addr++, store); 989 | 990 | // Fill rest of cache (except last byte) with zeroes 991 | for (; addr < last_byte; addr++) EEPROM.write(addr, 0); 992 | 993 | // Add all bytes in cache % 256 and add 42, that is the checksum written to last byte. 994 | // The 42 is because then checksum of all zeroes then isn't zero. 995 | uint8_t checksum = 0; 996 | for (uint16_t n = _eeprom_address; n < last_byte; n++) checksum += EEPROM.read(n); 997 | checksum += 42; 998 | EEPROM.write(last_byte, checksum); 999 | eepromEnd(); 1000 | infoln(); 1001 | return true; 1002 | #endif 1003 | 1004 | #ifdef EZTIME_CACHE_NVS 1005 | if (_nvs_name == "" || _nvs_key == "") return false; 1006 | infoln(F("Caching timezone data")); 1007 | Preferences prefs; 1008 | prefs.begin(_nvs_name.c_str(), false); 1009 | String tmp = String(months_since_jan_2018) + " " + str; 1010 | prefs.putString(_nvs_key.c_str(), tmp); 1011 | prefs.end(); 1012 | return true; 1013 | #endif 1014 | } 1015 | 1016 | 1017 | bool Timezone::readCache(String &olson, String &posix, uint8_t &months_since_jan_2018) { 1018 | 1019 | #ifdef EZTIME_CACHE_EEPROM 1020 | if (_eeprom_address < 0) { triggerError(NO_CACHE_SET); return false; } 1021 | eepromBegin(); 1022 | uint16_t last_byte = _eeprom_address + EEPROM_CACHE_LEN - 1; 1023 | 1024 | for (uint16_t n = _eeprom_address; n <= last_byte; n++) { 1025 | debug(n); 1026 | debug(F(" ")); 1027 | debugln(EEPROM.read(n), HEX); 1028 | } 1029 | 1030 | // return false if checksum incorrect 1031 | uint8_t checksum = 0; 1032 | for (uint16_t n = _eeprom_address; n < last_byte; n++) checksum += EEPROM.read(n); 1033 | checksum += 42; 1034 | if (checksum != EEPROM.read(last_byte)) { eepromEnd(); return false; } 1035 | debugln(F("Checksum OK")); 1036 | 1037 | // Return false if length impossible 1038 | uint8_t len = EEPROM.read(_eeprom_address + 1); 1039 | debug("Length: "); debugln(len); 1040 | if (len > MAX_CACHE_PAYLOAD) { eepromEnd(); return false; } 1041 | 1042 | // OK, we're gonna decompress 1043 | olson.reserve(len + 3); // Everything goes in olson first. Decompression might overshoot 3 1044 | months_since_jan_2018 = EEPROM.read(_eeprom_address); 1045 | 1046 | for (uint8_t n = 0; n < EEPROM_CACHE_LEN - 3; n++) { 1047 | uint16_t addr = n + _eeprom_address + 2; 1048 | uint8_t c = EEPROM.read(addr); 1049 | uint8_t p = EEPROM.read(addr - 1); // previous byte 1050 | switch (n % 3) { 1051 | case 0: 1052 | olson += (char)( ((c & 0b11111100) >> 2) + 32 ); 1053 | break; 1054 | case 1: 1055 | olson += (char)( ((p & 0b00000011) << 4) + ((c & 0b11110000) >> 4) + 32 ); 1056 | break; 1057 | case 2: 1058 | olson += (char)( ((p & 0b00001111) << 2) + ((c & 0b11000000) >> 6) + 32 ); 1059 | olson += (char)( (c & 0b00111111) + 32 ); 1060 | } 1061 | if (olson.length() >= len) break; 1062 | } 1063 | 1064 | uint8_t first_space = olson.indexOf(' '); 1065 | posix = olson.substring(first_space + 1, len); 1066 | olson = olson.substring(0, first_space); 1067 | 1068 | // Restore case of olson (best effort) 1069 | String olson_lowercase = olson; 1070 | olson_lowercase.toLowerCase(); 1071 | for (uint8_t n = 1; n < olson.length(); n++) { 1072 | unsigned char p = olson.charAt(n - 1); // previous character 1073 | if (p != '_' && p != '/' && p != '-') { 1074 | olson.setCharAt(n, olson_lowercase[n]); 1075 | } 1076 | } 1077 | info(F("Cache read. Olson: ")); info(olson); info (F(" Posix: ")); infoln(posix); 1078 | eepromEnd(); 1079 | return true; 1080 | #endif 1081 | 1082 | #ifdef EZTIME_CACHE_NVS 1083 | if (_nvs_name == "" || _nvs_key == "") { triggerError(NO_CACHE_SET); return false; } 1084 | 1085 | Preferences prefs; 1086 | prefs.begin(_nvs_name.c_str(), true); 1087 | String read_string = prefs.getString(_nvs_key.c_str()); 1088 | read_string.trim(); 1089 | prefs.end(); 1090 | if (read_string == "") return false; 1091 | 1092 | uint8_t first_space = read_string.indexOf(' '); 1093 | uint8_t second_space = read_string.indexOf(' ', first_space + 1); 1094 | if (first_space && second_space) { 1095 | months_since_jan_2018 = read_string.toInt(); 1096 | posix = read_string.substring(second_space + 1); 1097 | olson = read_string.substring(first_space + 1, second_space); 1098 | info(F("Cache read. Olson: ")); info(olson); info (F(" Posix: ")); infoln(posix); 1099 | return true; 1100 | } 1101 | return false; 1102 | #endif 1103 | } 1104 | 1105 | #endif // defined(EZTIME_CACHE_EEPROM) || defined(EZTIME_CACHE_NVS) 1106 | 1107 | 1108 | #endif // EZTIME_NETWORK_ENABLE 1109 | 1110 | 1111 | void Timezone::setDefault() { 1112 | defaultTZ = this; 1113 | debug(F("Default timezone set to ")); debug(_olson); debug(F(" "));debugln(_posix); 1114 | } 1115 | 1116 | bool Timezone::isDST(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1117 | String tzname; 1118 | bool is_dst; 1119 | int16_t offset; 1120 | t = tzTime(t, local_or_utc, tzname, is_dst, offset); 1121 | return is_dst; 1122 | } 1123 | 1124 | String Timezone::getTimezoneName(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1125 | String tzname; 1126 | bool is_dst; 1127 | int16_t offset; 1128 | t = tzTime(t, local_or_utc, tzname, is_dst, offset); 1129 | return tzname; 1130 | } 1131 | 1132 | int16_t Timezone::getOffset(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1133 | String tzname; 1134 | bool is_dst; 1135 | int16_t offset; 1136 | t = tzTime(t, local_or_utc, tzname, is_dst, offset); 1137 | return offset; 1138 | } 1139 | 1140 | 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) { 1141 | time_t t = ezt::makeTime(hr, min, sec, day, mnth, yr); 1142 | return setEvent(function, t); 1143 | } 1144 | 1145 | uint8_t Timezone::setEvent(void (*function)(), time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1146 | t = tzTime(t, local_or_utc); 1147 | for (uint8_t n = 0; n < MAX_EVENTS; n++) { 1148 | if (!_events[n].function) { 1149 | _events[n].function = function; 1150 | _events[n].time = t; 1151 | debug(F("Set event (#")); debug(n + 1); debug(F(") to trigger on: ")); debugln(UTC.dateTime(t)); 1152 | return n + 1; 1153 | } 1154 | } 1155 | triggerError(TOO_MANY_EVENTS); 1156 | return 0; 1157 | } 1158 | 1159 | void Timezone::setTime(const time_t t, const uint16_t ms /* = 0 */) { 1160 | int16_t offset; 1161 | offset = getOffset(t); 1162 | _last_sync_time = t + offset * 60; 1163 | _last_sync_millis = millis() - ms; 1164 | _time_status = timeSet; 1165 | } 1166 | 1167 | 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) { 1168 | tmElements_t tm; 1169 | // year can be given as full four digit year or two digts (2010 or 10 for 2010); 1170 | // it is converted to years since 1970 1171 | if( yr > 99) { 1172 | yr = yr - 1970; 1173 | } else { 1174 | yr += 30; 1175 | } 1176 | tm.Year = yr; 1177 | tm.Month = mnth; 1178 | tm.Day = day; 1179 | tm.Hour = hr; 1180 | tm.Minute = min; 1181 | tm.Second = sec; 1182 | setTime(ezt::makeTime(tm)); 1183 | } 1184 | 1185 | String Timezone::dateTime(const String format /* = DEFAULT_TIMEFORMAT */) { 1186 | return dateTime(TIME_NOW, format); 1187 | } 1188 | 1189 | String Timezone::dateTime(const time_t t, const String format /* = DEFAULT_TIMEFORMAT */) { 1190 | return dateTime(t, LOCAL_TIME, format); 1191 | } 1192 | 1193 | String Timezone::dateTime(time_t t, const ezLocalOrUTC_t local_or_utc, const String format /* = DEFAULT_TIMEFORMAT */) { 1194 | 1195 | String tzname; 1196 | bool is_dst; 1197 | int16_t offset; 1198 | 1199 | if (t == TIME_NOW || t == LAST_READ || local_or_utc == UTC_TIME) { 1200 | // in these cases we actually want tzTime to translate the time for us 1201 | // back in to this timezone's time as well as grab the timezone info 1202 | // from the stored POSIX data 1203 | t = tzTime(t, UTC_TIME, tzname, is_dst, offset); 1204 | } else { 1205 | // when receiving a local time we don't want to translate the timestamp 1206 | // but rather use tzTime to just parse the info about the timezone from 1207 | // the stored POSIX data 1208 | tzTime(t, LOCAL_TIME, tzname, is_dst, offset); 1209 | } 1210 | 1211 | String tmpstr; 1212 | uint8_t tmpint8; 1213 | String out = ""; 1214 | 1215 | tmElements_t tm; 1216 | ezt::breakTime(t, tm); 1217 | 1218 | int8_t hour12 = tm.Hour % 12; 1219 | if (hour12 == 0) hour12 = 12; 1220 | 1221 | int32_t o; 1222 | 1223 | bool escape_char = false; 1224 | 1225 | for (uint8_t n = 0; n < format.length(); n++) { 1226 | 1227 | char c = format.charAt(n); 1228 | 1229 | if (escape_char) { 1230 | out += String(c); 1231 | escape_char = false; 1232 | } else { 1233 | 1234 | switch (c) { 1235 | 1236 | case '\\': // Escape character, ignore this one, and let next through as literal character 1237 | case '~': // Same but easier without all the double escaping 1238 | escape_char = true; 1239 | break; 1240 | case 'd': // Day of the month, 2 digits with leading zeros 1241 | out += ezt::zeropad(tm.Day, 2); 1242 | break; 1243 | case 'D': // A textual representation of a day, usually two or three letters 1244 | out += ezt::dayShortStr(tm.Wday); 1245 | break; 1246 | case 'j': // Day of the month without leading zeros 1247 | out += String(tm.Day); 1248 | break; 1249 | case 'l': // (lowercase L) A full textual representation of the day of the week 1250 | out += ezt::dayStr(tm.Wday); 1251 | break; 1252 | case 'N': // ISO-8601 numeric representation of the day of the week. ( 1 = Monday, 7 = Sunday ) 1253 | tmpint8 = tm.Wday - 1; 1254 | if (tmpint8 == 0) tmpint8 = 7; 1255 | out += String(tmpint8); 1256 | break; 1257 | case 'S': // English ordinal suffix for the day of the month, 2 characters (st, nd, rd, th) 1258 | switch (tm.Day) { 1259 | case 1: 1260 | case 21: 1261 | case 31: 1262 | out += F("st"); break; 1263 | case 2: 1264 | case 22: 1265 | out += F("nd"); break; 1266 | case 3: 1267 | case 23: 1268 | out += F("rd"); break; 1269 | default: 1270 | out += F("th"); break; 1271 | } 1272 | break; 1273 | case 'w': // Numeric representation of the day of the week ( 0 = Sunday ) 1274 | out += String(tm.Wday); 1275 | break; 1276 | case 'F': // A full textual representation of a month, such as January or March 1277 | out += ezt::monthStr(tm.Month); 1278 | break; 1279 | case 'm': // Numeric representation of a month, with leading zeros 1280 | out += ezt::zeropad(tm.Month, 2); 1281 | break; 1282 | case 'M': // A short textual representation of a month, usually three letters 1283 | out += ezt::monthShortStr(tm.Month); 1284 | break; 1285 | case 'n': // Numeric representation of a month, without leading zeros 1286 | out += String(tm.Month); 1287 | break; 1288 | case 't': // Number of days in the given month 1289 | out += String(monthDays[tm.Month - 1]); 1290 | break; 1291 | case 'Y': // A full numeric representation of a year, 4 digits 1292 | out += String(tm.Year + 1970); 1293 | break; 1294 | case 'y': // A two digit representation of a year 1295 | out += ezt::zeropad((tm.Year + 1970) % 100, 2); 1296 | break; 1297 | case 'a': // am or pm 1298 | out += (tm.Hour < 12) ? F("am") : F("pm"); 1299 | break; 1300 | case 'A': // AM or PM 1301 | out += (tm.Hour < 12) ? F("AM") : F("PM"); 1302 | break; 1303 | case 'g': // 12-hour format of an hour without leading zeros 1304 | out += String(hour12); 1305 | break; 1306 | case 'G': // 24-hour format of an hour without leading zeros 1307 | out += String(tm.Hour); 1308 | break; 1309 | case 'h': // 12-hour format of an hour with leading zeros 1310 | out += ezt::zeropad(hour12, 2); 1311 | break; 1312 | case 'H': // 24-hour format of an hour with leading zeros 1313 | out += ezt::zeropad(tm.Hour, 2); 1314 | break; 1315 | case 'i': // Minutes with leading zeros 1316 | out += ezt::zeropad(tm.Minute, 2); 1317 | break; 1318 | case 's': // Seconds with leading zeros 1319 | out += ezt::zeropad(tm.Second, 2); 1320 | break; 1321 | case 'T': // abbreviation for timezone 1322 | out += tzname; 1323 | break; 1324 | case 'v': // milliseconds as three digits 1325 | out += ezt::zeropad(_last_read_ms, 3); 1326 | break; 1327 | #ifdef EZTIME_NETWORK_ENABLE 1328 | case 'e': // Timezone identifier (Olson) 1329 | out += getOlson(); 1330 | break; 1331 | #endif 1332 | case 'O': // Difference to Greenwich time (GMT) in hours and minutes written together (+0200) 1333 | case 'P': // Difference to Greenwich time (GMT) in hours and minutes written with colon (+02:00) 1334 | o = offset; 1335 | out += (o < 0) ? "+" : "-"; // reversed from our offset 1336 | if (o < 0) o = 0 - o; 1337 | out += ezt::zeropad(o / 60, 2); 1338 | out += (c == 'P') ? ":" : ""; 1339 | out += ezt::zeropad(o % 60, 2); 1340 | break; 1341 | case 'Z': //Timezone offset in seconds. West of UTC is negative, east of UTC is positive. 1342 | out += String(0 - offset * 60); 1343 | break; 1344 | case 'z': 1345 | out += String(dayOfYear(t)); // The day of the year (starting from 0) 1346 | break; 1347 | case 'W': 1348 | out += ezt::zeropad(weekISO(t), 2); // ISO-8601 week number of year, weeks starting on Monday 1349 | break; 1350 | case 'X': 1351 | out += String(yearISO(t)); // ISO-8601 year-week notation year, see https://en.wikipedia.org/wiki/ISO_week_date 1352 | break; 1353 | case 'B': 1354 | out += militaryTZ(t); 1355 | break; 1356 | default: 1357 | out += String(c); 1358 | 1359 | } 1360 | } 1361 | } 1362 | 1363 | return out; 1364 | } 1365 | 1366 | String Timezone::militaryTZ(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1367 | t = tzTime(t, local_or_utc); 1368 | int16_t o = getOffset(t); 1369 | if (o % 60) return "?"; // If it's not a whole hour from UTC, it's not a timezone with a military letter code 1370 | o = o / 60; 1371 | if (o > 0) return String((char)('M' + o)); 1372 | if (o < 0 && o >= -9) return String((char)('A' - o - 1)); // Minus a negative number == plus 1 1373 | if (o < -9) return String((char)('A' - o)); // Crazy, they're skipping 'J' 1374 | return "Z"; 1375 | } 1376 | 1377 | 1378 | uint8_t Timezone::hour(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1379 | t = tzTime(t, local_or_utc); 1380 | return t / 3600 % 24; 1381 | } 1382 | 1383 | uint8_t Timezone::minute(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1384 | t = tzTime(t, local_or_utc); 1385 | return t / 60 % 60; 1386 | } 1387 | 1388 | uint8_t Timezone::second(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1389 | t = tzTime(t, local_or_utc); 1390 | return t % 60; 1391 | } 1392 | 1393 | uint16_t Timezone::ms(time_t t /*= TIME_NOW */) { 1394 | // Note that here passing anything but TIME_NOW or LAST_READ is pointless 1395 | if (t == TIME_NOW) { nowUTC(); return _last_read_ms; } 1396 | if (t == LAST_READ) return _last_read_ms; 1397 | return 0; 1398 | } 1399 | 1400 | uint8_t Timezone::day(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1401 | t = tzTime(t, local_or_utc); 1402 | tmElements_t tm; 1403 | ezt::breakTime(t, tm); 1404 | return tm.Day; 1405 | } 1406 | 1407 | uint8_t Timezone::weekday(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1408 | t = tzTime(t, local_or_utc); 1409 | tmElements_t tm; 1410 | ezt::breakTime(t, tm); 1411 | return tm.Wday; 1412 | } 1413 | 1414 | uint8_t Timezone::month(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1415 | t = tzTime(t, local_or_utc); 1416 | tmElements_t tm; 1417 | ezt::breakTime(t, tm); 1418 | return tm.Month; 1419 | } 1420 | 1421 | uint16_t Timezone::year(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1422 | t = tzTime(t, local_or_utc); 1423 | tmElements_t tm; 1424 | ezt::breakTime(t, tm); 1425 | return tm.Year + 1970; 1426 | } 1427 | 1428 | uint16_t Timezone::dayOfYear(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1429 | t = tzTime(t, local_or_utc); 1430 | time_t jan_1st = ezt::makeTime(0, 0, 0, 1, 1, year(t)); 1431 | return (t - jan_1st) / SECS_PER_DAY; 1432 | } 1433 | 1434 | uint8_t Timezone::hourFormat12(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1435 | t = tzTime(t, local_or_utc); 1436 | uint8_t h = t / 3600 % 12; 1437 | if (h) return h; 1438 | return 12; 1439 | } 1440 | 1441 | bool Timezone::isAM(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1442 | t = tzTime(t, local_or_utc); 1443 | return (t / 3600 % 24 < 12); 1444 | } 1445 | 1446 | bool Timezone::isPM(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1447 | t = tzTime(t, local_or_utc); 1448 | return (t / 3600 % 24 >= 12); 1449 | } 1450 | 1451 | 1452 | // Now this is where this gets a little obscure. The ISO year can be different from the 1453 | // actual (Gregorian) year. That is: you can be in january and still be in week 53 of past 1454 | // year, _and_ you can be in december and be in week one of the next. The ISO 8601 1455 | // definition for week 01 is the week with the Gregorian year's first Thursday in it. 1456 | // See https://en.wikipedia.org/wiki/ISO_week_date 1457 | // 1458 | #define startISOyear(year...) ezt::makeOrdinalTime(0, 0, 0, FIRST, THURSDAY, JANUARY, year) - 3UL * SECS_PER_DAY; 1459 | uint8_t Timezone::weekISO(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1460 | t = tzTime(t, local_or_utc); 1461 | int16_t yr = year(t); 1462 | time_t this_year = startISOyear(yr); 1463 | time_t prev_year = startISOyear(yr - 1); 1464 | time_t next_year = startISOyear(yr + 1); 1465 | if (t < this_year) this_year = prev_year; 1466 | if (t >= next_year) this_year = next_year; 1467 | return (t - this_year) / ( SECS_PER_DAY * 7UL) + 1; 1468 | } 1469 | 1470 | uint16_t Timezone::yearISO(time_t t /*= TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { 1471 | t = tzTime(t, local_or_utc); 1472 | int16_t yr = year(t); 1473 | time_t this_year = startISOyear(yr); 1474 | time_t next_year = startISOyear(yr + 1); 1475 | if (t < this_year) return yr - 1; 1476 | if (t >= next_year) return yr + 1; 1477 | return yr; 1478 | } 1479 | 1480 | 1481 | Timezone UTC; 1482 | Timezone *defaultTZ = &UTC; 1483 | 1484 | namespace ezt { 1485 | // All bounce-throughs to defaultTZ 1486 | String dateTime(const String format /* = DEFAULT_TIMEFORMAT */) { return (defaultTZ->dateTime(format)); } 1487 | String dateTime(time_t t, const String format /* = DEFAULT_TIMEFORMAT */) { return (defaultTZ->dateTime(t, format)); } 1488 | String dateTime(time_t t, const ezLocalOrUTC_t local_or_utc, const String format /* = DEFAULT_TIMEFORMAT */) { return (defaultTZ->dateTime(t, local_or_utc, format)); } 1489 | uint8_t day(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->day(t, local_or_utc)); } 1490 | uint16_t dayOfYear(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->dayOfYear(t, local_or_utc)); } 1491 | int16_t getOffset(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->getOffset(t, local_or_utc)); } 1492 | String getTimezoneName(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->getTimezoneName(t, local_or_utc)); } 1493 | uint8_t hour(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->hour(t, local_or_utc)); } 1494 | uint8_t hourFormat12(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->hourFormat12(t, local_or_utc)); } 1495 | bool isAM(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->isAM(t, local_or_utc)); } 1496 | bool isDST(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->isDST(t, local_or_utc)); } 1497 | bool isPM(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->isPM(t, local_or_utc)); } 1498 | String militaryTZ(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->militaryTZ(t, local_or_utc)); } 1499 | uint8_t minute(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->minute(t, local_or_utc)); } 1500 | uint8_t month(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->month(t, local_or_utc)); } 1501 | uint16_t ms(time_t t /* = TIME_NOW */) { return (defaultTZ->ms(t)); } 1502 | time_t now() { return (defaultTZ->now()); } 1503 | uint8_t second(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->second(t, local_or_utc)); } 1504 | 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) { return (defaultTZ->setEvent(function,hr, min, sec, day, mnth, yr)); } 1505 | uint8_t setEvent(void (*function)(), time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->setEvent(function, t, local_or_utc)); } 1506 | 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); } 1507 | void setTime(time_t t) { defaultTZ->setTime(t); } 1508 | uint8_t weekISO(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->weekISO(t, local_or_utc)); } 1509 | uint8_t weekday(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->weekday(t, local_or_utc)); } 1510 | uint16_t year(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->year(t, local_or_utc)); } 1511 | uint16_t yearISO(time_t t /* = TIME_NOW */, const ezLocalOrUTC_t local_or_utc /* = LOCAL_TIME */) { return (defaultTZ->yearISO(t, local_or_utc)); } 1512 | } 1513 | -------------------------------------------------------------------------------- /src/ezTime.h: -------------------------------------------------------------------------------- 1 | /* Extensive API documentation is at https://github.com/ropg/ezTime */ 2 | 3 | #ifndef _EZTIME_H_ 4 | #ifdef __cplusplus 5 | #define _EZTIME_H_ 6 | 7 | //Sets the language for the names of Months and Days. See the src/lang directory for supported languages 8 | #define EZTIME_LANGUAGE EN 9 | 10 | // Compiles in NTP updating, timezoned fetching and caching 11 | #define EZTIME_NETWORK_ENABLE 12 | 13 | // Arduino Ethernet shields 14 | // #define EZTIME_ETHERNET 15 | 16 | // Arduino board with ESP8266 shield 17 | // #define EZTIME_WIFIESP 18 | 19 | // Uncomment one of the below to only put only messages up to a certain level in the compiled code 20 | // (You still need to turn them on with setDebug(someLevel) to see them) 21 | // #define EZTIME_MAX_DEBUGLEVEL_NONE 22 | // #define EZTIME_MAX_DEBUGLEVEL_ERROR 23 | // #define EZTIME_MAX_DEBUGLEVEL_INFO 24 | 25 | // Cache mechanism, either EEPROM or NVS, not both. (See README) 26 | #define EZTIME_CACHE_EEPROM 27 | // #define EZTIME_CACHE_NVS 28 | 29 | // Uncomment if you want to access ezTime functions only after "ezt." 30 | // (to avoid naming conflicts in bigger projects, e.g.) 31 | // #define EZTIME_EZT_NAMESPACE 32 | 33 | 34 | // Warranty void if edited below this point... 35 | 36 | 37 | 38 | #if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc 39 | typedef unsigned long time_t; 40 | #endif 41 | 42 | #include 43 | #ifndef __AVR__ 44 | #include // for __time_t_defined, but avr libc lacks sys/types.h 45 | #endif 46 | 47 | #if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc 48 | typedef unsigned long time_t; 49 | #endif 50 | 51 | 52 | extern "C++" { 53 | 54 | ////////// Error handing 55 | 56 | typedef enum { 57 | NO_ERROR, 58 | LAST_ERROR, // Pseudo-error: replaced by last error 59 | NO_NETWORK, 60 | TIMEOUT, 61 | CONNECT_FAILED, 62 | DATA_NOT_FOUND, 63 | LOCKED_TO_UTC, 64 | NO_CACHE_SET, 65 | CACHE_TOO_SMALL, 66 | TOO_MANY_EVENTS, 67 | INVALID_DATA, 68 | SERVER_ERROR 69 | } ezError_t; 70 | 71 | typedef enum { 72 | NONE, 73 | ERROR, 74 | INFO, 75 | DEBUG 76 | } ezDebugLevel_t; 77 | 78 | typedef enum { 79 | LOCAL_TIME, 80 | UTC_TIME 81 | } ezLocalOrUTC_t; 82 | 83 | // Defines that can make your code more readable. For example, if you are looking for the first 84 | // Thursday in a year, you could write: time.makeOrdinalTime(0, 0, 0, JANUARY, FIRST, THURSDAY, year) 85 | 86 | #define SUNDAY 1 87 | #define MONDAY 2 88 | #define TUESDAY 3 89 | #define WEDNESDAY 4 90 | #define THURSDAY 5 91 | #define FRIDAY 6 92 | #define SATURDAY 7 93 | 94 | #define JANUARY 1 95 | #define FEBRUARY 2 96 | #define MARCH 3 97 | #define APRIL 4 98 | #define MAY 5 99 | #define JUNE 6 100 | #define JULY 7 101 | #define AUGUST 8 102 | #define SEPTEMBER 9 103 | #define OCTOBER 10 104 | #define NOVEMBER 11 105 | #define DECEMBER 12 106 | 107 | #define FIRST 1 108 | #define SECOND 2 109 | #define THIRD 3 110 | #define FOURTH 4 111 | #define LAST 5 112 | 113 | //////////////////////// 114 | 115 | 116 | #define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) 117 | #define SECS_PER_DAY (86400UL) 118 | 119 | typedef struct { 120 | uint8_t Second; 121 | uint8_t Minute; 122 | uint8_t Hour; 123 | uint8_t Wday; // day of week, sunday is day 1 124 | uint8_t Day; 125 | uint8_t Month; 126 | uint8_t Year; // offset from 1970; 127 | } tmElements_t; 128 | 129 | typedef enum { 130 | timeNotSet, 131 | timeNeedsSync, 132 | timeSet 133 | } timeStatus_t; 134 | 135 | typedef struct { 136 | time_t time; 137 | void (*function)(); 138 | } ezEvent_t; 139 | 140 | #define MAX_EVENTS 8 141 | 142 | #define TIME_NOW (int32_t)0x7FFFFFFF // Two special-meaning time_t values ... 143 | #define LAST_READ (int32_t)0x7FFFFFFE // (So yes, ezTime might malfunction two seconds before everything else...) 144 | 145 | #define NTP_PACKET_SIZE 48 146 | #define NTP_LOCAL_PORT 4242 147 | #define NTP_SERVER "pool.ntp.org" 148 | #define NTP_TIMEOUT 1500 // milliseconds 149 | #define NTP_INTERVAL 1801 // default update interval in seconds 150 | #define NTP_RETRY 20 // Retry after this many seconds on failed NTP 151 | #define NTP_STALE_AFTER 3602 // If update due for this many seconds, set timeStatus to timeNeedsSync 152 | 153 | #define TIMEZONED_REMOTE_HOST "timezoned.rop.nl" 154 | #define TIMEZONED_REMOTE_PORT 2342 155 | #define TIMEZONED_LOCAL_PORT 2342 156 | #define TIMEZONED_TIMEOUT 2000 // milliseconds 157 | 158 | #define EEPROM_CACHE_LEN 50 159 | #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 160 | #define MAX_CACHE_AGE_MONTHS 6 161 | 162 | // Various date-time formats 163 | #define ATOM "Y-m-d\\TH:i:sP" 164 | #define COOKIE "l, d-M-Y H:i:s T" 165 | #define ISO8601 "Y-m-d\\TH:i:sO" 166 | #define RFC822 "D, d M y H:i:s O" 167 | #define RFC850 COOKIE 168 | #define RFC1036 RFC822 169 | #define RFC1123 RFC822 170 | #define RFC2822 RFC822 171 | #define RFC3339 ATOM 172 | #define RFC3339_EXT "Y-m-d\\TH:i:s.vP" 173 | #define RSS RFC822 174 | #define W3C ATOM 175 | #define ISO8601_YWD "X-\\WW-N" 176 | #define DEFAULT_TIMEFORMAT COOKIE 177 | 178 | namespace ezt { 179 | void breakTime(const time_t time, tmElements_t &tm); 180 | time_t compileTime(const String compile_date = __DATE__, const String compile_time = __TIME__); 181 | String dayShortStr(const uint8_t month); 182 | String dayStr(const uint8_t month); 183 | void deleteEvent(const uint8_t event_handle); 184 | void deleteEvent(void (*function)()); 185 | ezError_t error(const bool reset = false); 186 | String errorString(const ezError_t err = LAST_ERROR); 187 | void events(); 188 | time_t makeOrdinalTime(const uint8_t hour, const uint8_t minute, const uint8_t second, uint8_t ordinal, const uint8_t wday, const uint8_t month, uint16_t year); 189 | time_t makeTime(const uint8_t hour, const uint8_t minute, const uint8_t second, const uint8_t day, const uint8_t month, const uint16_t year); 190 | time_t makeTime(tmElements_t &tm); 191 | bool minuteChanged(); 192 | String monthShortStr(const uint8_t month); 193 | String monthStr(const uint8_t month); 194 | bool secondChanged(); 195 | void setDebug(const ezDebugLevel_t level); 196 | void setDebug(const ezDebugLevel_t level, Print &device); 197 | timeStatus_t timeStatus(); 198 | String urlEncode(const String str); 199 | String zeropad(const uint32_t number, const uint8_t length); 200 | 201 | #ifdef EZTIME_NETWORK_ENABLE 202 | bool queryNTP(const String server, time_t &t, unsigned long &measured_at); 203 | void setInterval(const uint16_t seconds = 0); 204 | void setServer(const String ntp_server = NTP_SERVER); 205 | void updateNTP(); 206 | bool waitForSync(const uint16_t timeout = 0); 207 | time_t lastNtpUpdateTime(); 208 | #endif 209 | } 210 | 211 | // 212 | // T i m e z o n e c l a s s 213 | // 214 | 215 | class Timezone { 216 | 217 | public: 218 | Timezone(const bool locked_to_UTC = false); 219 | String dateTime(const String format = DEFAULT_TIMEFORMAT); 220 | String dateTime(time_t t, const String format = DEFAULT_TIMEFORMAT); 221 | String dateTime(time_t t, const ezLocalOrUTC_t local_or_utc, const String format = DEFAULT_TIMEFORMAT); 222 | uint8_t day(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 223 | uint16_t dayOfYear(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 224 | int16_t getOffset(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 225 | String getPosix(); 226 | String getTimezoneName(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 227 | uint8_t hour(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 228 | uint8_t hourFormat12(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 229 | bool isAM(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 230 | bool isDST(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 231 | bool isPM(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 232 | String militaryTZ(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 233 | uint8_t minute(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 234 | uint8_t month(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 235 | uint16_t ms(time_t t = TIME_NOW); 236 | time_t now(); 237 | uint8_t second(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 238 | void setDefault(); 239 | 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); 240 | uint8_t setEvent(void (*function)(), time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 241 | bool setPosix(const String posix); 242 | void setTime(const time_t t, const uint16_t ms = 0); 243 | 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); 244 | time_t tzTime(time_t t = TIME_NOW, ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 245 | time_t tzTime(time_t t, ezLocalOrUTC_t local_or_utc, String &tzname, bool &is_dst, int16_t &offset); 246 | uint8_t weekISO(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 247 | uint8_t weekday(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 248 | uint16_t year(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 249 | uint16_t yearISO(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 250 | private: 251 | String _posix, _olson; 252 | bool _locked_to_UTC; 253 | 254 | #ifdef EZTIME_NETWORK_ENABLE 255 | public: 256 | bool setLocation(const String location = "GeoIP"); 257 | String getOlson(); 258 | String getOlsen(); 259 | #ifdef EZTIME_CACHE_EEPROM 260 | public: 261 | bool setCache(const int16_t address); 262 | private: 263 | int16_t _eeprom_address; 264 | #endif 265 | #ifdef EZTIME_CACHE_NVS 266 | public: 267 | bool setCache(const String name, const String key); 268 | private: 269 | String _nvs_name, _nvs_key; 270 | #endif 271 | #if defined(EZTIME_CACHE_EEPROM) || defined(EZTIME_CACHE_NVS) 272 | public: 273 | void clearCache(const bool delete_section = false); 274 | private: 275 | bool setCache(); 276 | bool writeCache(String &str); 277 | bool readCache(String &olson, String &posix, uint8_t &months_since_jan_2018); 278 | uint8_t _cache_month; 279 | #endif 280 | #endif 281 | 282 | }; 283 | 284 | extern Timezone UTC; 285 | extern Timezone *defaultTZ; 286 | 287 | namespace ezt { 288 | // These bounce through to same-named methods in defaultTZ 289 | String dateTime(const String format = DEFAULT_TIMEFORMAT); 290 | String dateTime(time_t t, const String format = DEFAULT_TIMEFORMAT); 291 | String dateTime(time_t t, const ezLocalOrUTC_t local_or_utc, const String format = DEFAULT_TIMEFORMAT); 292 | uint8_t day(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 293 | uint16_t dayOfYear(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 294 | int16_t getOffset(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 295 | String getTimezoneName(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 296 | uint8_t hour(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 297 | uint8_t hourFormat12(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 298 | bool isAM(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 299 | bool isDST(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 300 | bool isPM(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 301 | String militaryTZ(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 302 | uint8_t minute(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 303 | uint8_t month(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 304 | uint16_t ms(time_t t = TIME_NOW); 305 | time_t now(); 306 | uint8_t second(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 307 | 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); 308 | uint8_t setEvent(void (*function)(), time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 309 | 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); 310 | void setTime(time_t t); 311 | uint8_t weekISO(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 312 | uint8_t weekday(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 313 | uint16_t year(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 314 | uint16_t yearISO(time_t t = TIME_NOW, const ezLocalOrUTC_t local_or_utc = LOCAL_TIME); 315 | } 316 | 317 | #ifndef EZTIME_EZT_NAMESPACE 318 | using namespace ezt; 319 | #endif 320 | 321 | 322 | // The following defines all copied from the original Time lib to keep existing code working 323 | 324 | /* Useful Constants */ 325 | #define SECS_PER_MIN (60UL) 326 | #define SECS_PER_HOUR (3600UL) 327 | #define DAYS_PER_WEEK (7UL) 328 | #define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK) 329 | #define SECS_PER_YEAR (SECS_PER_WEEK * 52UL) 330 | #define SECS_YR_2000 (946684800UL) // the time at the start of y2k 331 | 332 | /* Useful Macros for getting elapsed time */ 333 | #define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) 334 | #define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) 335 | #define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) 336 | #define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday 337 | #define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 338 | #define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight 339 | 340 | // The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 341 | // Always set the correct time before settting alarms 342 | #define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day 343 | #define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day 344 | #define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 345 | #define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time 346 | #define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time 347 | 348 | /* Useful Macros for converting elapsed time to a time_t */ 349 | #define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) 350 | #define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) 351 | #define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 352 | #define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) 353 | 354 | 355 | } // extern "C++" 356 | #endif // __cplusplus 357 | #endif //_EZTIME_H_ 358 | -------------------------------------------------------------------------------- /src/lang/CA: -------------------------------------------------------------------------------- 1 | String monthStr(const uint8_t month) { 2 | switch(month) { 3 | case 1: return F("gener"); 4 | case 2: return F("febrer"); 5 | case 3: return F("març"); 6 | case 4: return F("abril"); 7 | case 5: return F("maig"); 8 | case 6: return F("juny"); 9 | case 7: return F("juliol"); 10 | case 8: return F("agost"); 11 | case 9: return F("setembre"); 12 | case 10: return F("octubre"); 13 | case 11: return F("novembre"); 14 | case 12: return F("desembre"); 15 | } 16 | return ""; 17 | } 18 | 19 | String monthShortStr(const uint8_t month) { return monthStr(month).substring(0,3); } 20 | 21 | String dayStr(const uint8_t day) { 22 | switch(day) { 23 | case 1: return F("diumenge"); 24 | case 2: return F("dilluns"); 25 | case 3: return F("dimarts"); 26 | case 4: return F("dimecres"); 27 | case 5: return F("dijous"); 28 | case 6: return F("divendres"); 29 | case 7: return F("dissabte"); 30 | } 31 | return ""; 32 | } 33 | 34 | String dayShortStr(const uint8_t day) { 35 | switch(day) { 36 | case 1: return F("Dg"); 37 | case 2: return F("Dl"); 38 | case 3: return F("Dt"); 39 | case 4: return F("Dc"); 40 | case 5: return F("Dj"); 41 | case 6: return F("Dv"); 42 | case 7: return F("Ds"); 43 | } 44 | return ""; 45 | } 46 | -------------------------------------------------------------------------------- /src/lang/DE: -------------------------------------------------------------------------------- 1 | String monthStr(const uint8_t month) { 2 | switch(month) { 3 | case 1: return F("Januar"); 4 | case 2: return F("Februar"); 5 | case 3: return F("März"); 6 | case 4: return F("April"); 7 | case 5: return F("Mai"); 8 | case 6: return F("Juni"); 9 | case 7: return F("Juli"); 10 | case 8: return F("August"); 11 | case 9: return F("September"); 12 | case 10: return F("Oktober"); 13 | case 11: return F("November"); 14 | case 12: return F("Dezember"); 15 | } 16 | return ""; 17 | } 18 | 19 | String monthShortStr(const uint8_t month) { return monthStr(month).substring(0,3); } 20 | 21 | String dayStr(const uint8_t day) { 22 | switch(day) { 23 | case 1: return F("Sonntag"); 24 | case 2: return F("Montag"); 25 | case 3: return F("Dienstag"); 26 | case 4: return F("Mittwoch"); 27 | case 5: return F("Donnerstag"); 28 | case 6: return F("Freitag"); 29 | case 7: return F("Samstag"); 30 | } 31 | return ""; 32 | } 33 | 34 | String dayShortStr(const uint8_t day) { return dayStr(day).substring(0,2); } 35 | -------------------------------------------------------------------------------- /src/lang/EN: -------------------------------------------------------------------------------- 1 | String monthStr(const uint8_t month) { 2 | switch(month) { 3 | case 1: return F("January"); 4 | case 2: return F("February"); 5 | case 3: return F("March"); 6 | case 4: return F("April"); 7 | case 5: return F("May"); 8 | case 6: return F("June"); 9 | case 7: return F("July"); 10 | case 8: return F("August"); 11 | case 9: return F("September"); 12 | case 10: return F("October"); 13 | case 11: return F("November"); 14 | case 12: return F("December"); 15 | } 16 | return ""; 17 | } 18 | 19 | String monthShortStr(const uint8_t month) { return monthStr(month).substring(0,3); } 20 | 21 | String dayStr(const uint8_t day) { 22 | switch(day) { 23 | case 1: return F("Sunday"); 24 | case 2: return F("Monday"); 25 | case 3: return F("Tuesday"); 26 | case 4: return F("Wednesday"); 27 | case 5: return F("Thursday"); 28 | case 6: return F("Friday"); 29 | case 7: return F("Saturday"); 30 | } 31 | return ""; 32 | } 33 | 34 | String dayShortStr(const uint8_t day) { return dayStr(day).substring(0,3); } 35 | -------------------------------------------------------------------------------- /src/lang/ES: -------------------------------------------------------------------------------- 1 | String monthStr(const uint8_t month) { 2 | switch(month) { 3 | case 1: return F("enero"); 4 | case 2: return F("febrero"); 5 | case 3: return F("marzo"); 6 | case 4: return F("abril"); 7 | case 5: return F("mayo"); 8 | case 6: return F("junio"); 9 | case 7: return F("julio"); 10 | case 8: return F("agosto"); 11 | case 9: return F("septiembre"); 12 | case 10: return F("octubre"); 13 | case 11: return F("noviembre"); 14 | case 12: return F("diciembre"); 15 | } 16 | return ""; 17 | } 18 | 19 | String monthShortStr(const uint8_t month) { return monthStr(month).substring(0,3); } 20 | 21 | String dayStr(const uint8_t day) { 22 | switch(day) { 23 | case 1: return F("domingo"); 24 | case 2: return F("lunes"); 25 | case 3: return F("martes"); 26 | case 4: return F("miercoles"); 27 | case 5: return F("jueves"); 28 | case 6: return F("viernes"); 29 | case 7: return F("sabado"); 30 | } 31 | return ""; 32 | } 33 | 34 | String dayShortStr(const uint8_t day) { 35 | switch(day) { 36 | case 1: return F("D"); 37 | case 2: return F("L"); 38 | case 3: return F("M"); 39 | case 4: return F("X"); 40 | case 5: return F("J"); 41 | case 6: return F("V"); 42 | case 7: return F("S"); 43 | } 44 | return ""; 45 | } 46 | -------------------------------------------------------------------------------- /src/lang/FR: -------------------------------------------------------------------------------- 1 | String monthStr(const uint8_t month) { 2 | switch(month) { 3 | case 1: return F("janvier"); 4 | case 2: return F("fevrier"); 5 | case 3: return F("mars"); 6 | case 4: return F("avril"); 7 | case 5: return F("mai"); 8 | case 6: return F("juin"); 9 | case 7: return F("juillet"); 10 | case 8: return F("aout"); 11 | case 9: return F("septembre"); 12 | case 10: return F("octobre"); 13 | case 11: return F("novembre"); 14 | case 12: return F("decembre"); 15 | } 16 | return ""; 17 | } 18 | 19 | String monthShortStr(const uint8_t month) { 20 | switch(month) { 21 | case 6: return F("jun"); 22 | case 7: return F("jul"); 23 | } 24 | return monthStr(month).substring(0,3); 25 | } 26 | 27 | String dayStr(const uint8_t day) { 28 | switch(day) { 29 | case 1: return F("dimanche"); 30 | case 2: return F("lundi"); 31 | case 3: return F("mardi"); 32 | case 4: return F("mercredi"); 33 | case 5: return F("jeudi"); 34 | case 6: return F("vendredi"); 35 | case 7: return F("samedi"); 36 | } 37 | return ""; 38 | } 39 | 40 | String dayShortStr(const uint8_t day) { return dayStr(day).substring(0,3); } 41 | -------------------------------------------------------------------------------- /src/lang/IT: -------------------------------------------------------------------------------- 1 | String monthStr(const uint8_t month) { 2 | switch(month) { 3 | case 1: return F("Gennaio"); 4 | case 2: return F("Febbraio"); 5 | case 3: return F("Marzo"); 6 | case 4: return F("Aprile"); 7 | case 5: return F("Maggio"); 8 | case 6: return F("Giugno"); 9 | case 7: return F("Luglio"); 10 | case 8: return F("Agosto"); 11 | case 9: return F("Settembre"); 12 | case 10: return F("Ottobre"); 13 | case 11: return F("Novembre"); 14 | case 12: return F("Dicembre"); 15 | } 16 | return ""; 17 | } 18 | 19 | String monthShortStr(const uint8_t month) { return monthStr(month).substring(0,3); } 20 | 21 | String dayStr(const uint8_t day) { 22 | switch(day) { 23 | case 1: return F("Domenica"); 24 | case 2: return F("Lunedì"); 25 | case 3: return F("Martedì"); 26 | case 4: return F("Mercoledì"); 27 | case 5: return F("Giovedì"); 28 | case 6: return F("Venerdì"); 29 | case 7: return F("Sabato"); 30 | } 31 | return ""; 32 | } 33 | 34 | String dayShortStr(const uint8_t day) { return dayStr(day).substring(0,3); } 35 | -------------------------------------------------------------------------------- /src/lang/NL: -------------------------------------------------------------------------------- 1 | String monthStr(const uint8_t month) { 2 | switch(month) { 3 | case 1: return F("januari"); 4 | case 2: return F("februari"); 5 | case 3: return F("maart"); 6 | case 4: return F("april"); 7 | case 5: return F("mei"); 8 | case 6: return F("juni"); 9 | case 7: return F("juli"); 10 | case 8: return F("augustus"); 11 | case 9: return F("september"); 12 | case 10: return F("oktober"); 13 | case 11: return F("november"); 14 | case 12: return F("december"); 15 | } 16 | return ""; 17 | } 18 | 19 | String monthShortStr(const uint8_t month) { return monthStr(month).substring(0,3); } 20 | 21 | String dayStr(const uint8_t day) { 22 | switch(day) { 23 | case 1: return F("zondag"); 24 | case 2: return F("maandag"); 25 | case 3: return F("dinsdag"); 26 | case 4: return F("woensdag"); 27 | case 5: return F("donderdag"); 28 | case 6: return F("vrijdag"); 29 | case 7: return F("zaterdag"); 30 | } 31 | return ""; 32 | } 33 | 34 | String dayShortStr(const uint8_t day) { return dayStr(day).substring(0,2); } 35 | --------------------------------------------------------------------------------