DateTime
The DateTime class provides a simple way to keep track of the milliseconds since UNIX epoch for an application. The class initialises itself with the current date/time from timeapi.org (using HttpClient). It then uses the Arduino application timer function to simulate a realtime clock.
DateTime.h
The header file that defines the interface for the class.
#ifndef SPT_NET_DATETIME_H #define SPT_NET_DATETIME_H #if defined( ARDUINO ) #include "../StandardCplusplus/iostream" #include "../StandardCplusplus/string" #else #include <iostream> #include <string> #endif namespace spt { namespace net { /** * @brief Represents current date/time. Seeds initially (and daily) * from a network time service, and uses internal timer to emulate * a real-time clock. */ class DateTime { public: /// Default constructor. Use {@link #singleton} in general. DateTime(); /// Return the current date/time in ISO 8601 format const std::string currentTime(); /// Return the current date in ISO 8601 format const std::string date(); /// Return the milli seconds since UNIX epoch. int64_t currentTimeMillis(); /// Return a singleton instance to use. This is the preferred way /// of using this class. static DateTime& singleton(); private: void init(); const std::string serverTime(); bool isLeapYear( int16_t year ) const; int64_t epochMilliSeconds( const std::string& iso8601 ) const; std::string isoTime( int64_t epoch ) const; private: static const int64_t milliSecondsPerHour; static const int64_t minEpoch; uint32_t startTimeMillis; uint32_t lastUpdateMillis; int64_t milliSecondsSinceEpoch; }; /// Serialise the date-time to the output stream. inline std::ostream& operator << ( std::ostream& os, DateTime& dt ) { os << dt.currentTime(); return os; } /// Append the current time to the specified string inline std::string& operator += ( std::string& str, DateTime& dt ) { return str.append( dt.currentTime() ); } } } #endif // SPT_NET_DATETIME_H
DateTime.cpp
The implementation of the date-time class.
#include "DateTime.h" #include "SHttpClient.h" #if defined( ARDUINO ) #include "Arduino.h" #include "../StandardCplusplus/cstdlib" #include "../StandardCplusplus/sstream" #else #include <cstdlib> #include <sstream> #endif using std::string; using spt::net::DateTime; const int64_t DateTime::minEpoch = int64_t( 1430704319000 ); const int64_t DateTime::milliSecondsPerHour = int64_t( 3600000 ); DateTime::DateTime() : startTimeMillis( millis() ), lastUpdateMillis( startTimeMillis ), milliSecondsSinceEpoch( 0 ) { init(); } const string DateTime::currentTime() { return isoTime( currentTimeMillis() ); } const string DateTime::date() { const string& dt = currentTime(); return dt.substr( 0, dt.find( "T" ) ); } int64_t DateTime::currentTimeMillis() { uint32_t time = millis(); if ( ( time - startTimeMillis ) > uint64_t( 86400000 ) ) init(); else { milliSecondsSinceEpoch += time - lastUpdateMillis; lastUpdateMillis = time; } return milliSecondsSinceEpoch; } DateTime& DateTime::singleton() { static DateTime dt; return dt; } void DateTime::init() { const string& iso = serverTime(); milliSecondsSinceEpoch = epochMilliSeconds( iso ); if ( milliSecondsSinceEpoch < minEpoch ) { uint32_t diff = uint32_t( minEpoch - milliSecondsSinceEpoch ); std::cout << "minEpoch greater than server time. Difference " << diff << std::endl; milliSecondsSinceEpoch = minEpoch; } uint32_t time = millis(); milliSecondsSinceEpoch += time - lastUpdateMillis; startTimeMillis = time; lastUpdateMillis = time; } const string DateTime::serverTime() { using spt::net::HttpClient; using spt::net::HttpRequest; static string server( "www.timeapi.org" ); static string uri( "/utc/now" ); static string userAgentKey( "User-Agent" ); static string userAgentValue( "QSense" ); HttpClient::Ptr client = HttpClient::create(); if ( client->connect( server ) ) { std::cout << F( "Connected to " ) << server << std::endl; HttpRequest request( uri ); request.setHeader( userAgentKey, userAgentValue ); uint16_t responseCode = client->get( request ); std::cout << F( "Server returned HTTP response code: " ) << responseCode << std::endl; if ( responseCode == 200 && client->connected() ) return client->readBody(); } else { std::cout << F( "Connection to " ) << server << F( " failed" ) << std::endl; } return string(); } bool DateTime::isLeapYear( int16_t year ) const { bool result = false; if ( ( year % 400 ) == 0 ) result = true; else if ( ( year % 100 ) == 0 ) result = false; else if ( ( year % 4 ) == 0 ) result = true; return result; } int64_t DateTime::epochMilliSeconds( const string& date ) const { const int16_t year = std::atoi( date.substr( 0, 4 ).c_str() ); const int16_t month = std::atoi( date.substr( 5, 2 ).c_str() ); const int16_t day = std::atoi( date.substr( 8, 2 ).c_str() ); const int16_t hour = std::atoi( date.substr( 11, 2 ).c_str() ); const int16_t minute = std::atoi( date.substr( 14, 2 ).c_str() ); const int16_t second = std::atoi( date.substr( 17, 2 ).c_str() ); const int16_t dst = std::atoi( date.substr( 20, 2 ).c_str() ); const int16_t millis = 0; int64_t epoch = millis; epoch += second * int64_t( 1000 ); epoch += minute * int64_t( 60000 ); epoch += hour * milliSecondsPerHour; epoch += ( day - 1 ) * 24 * milliSecondsPerHour; const int8_t isLeap = isLeapYear( year ); for ( int i = 1; i < month; ++i ) { switch ( i ) { case 2: epoch += ( (isLeap) ? 29 : 28 ) * 24 * milliSecondsPerHour; break; case 4: case 6: case 9: case 11: epoch += 30 * 24 * milliSecondsPerHour; break; default: epoch += 31 * 24 * milliSecondsPerHour; } } for ( int i = 1970; i < year; ++i ) { if ( isLeapYear( i ) ) epoch += 366 * 24 * milliSecondsPerHour; else epoch += 365 * 24 * milliSecondsPerHour; } if ( dst ) epoch -= dst * milliSecondsPerHour; return epoch; } string DateTime::isoTime( int64_t epoch ) const { const int millis = epoch % int64_t( 1000 ); epoch /= int64_t( 1000 ); const int second = epoch % 60; epoch /= 60; const int minute = epoch % 60; epoch /= 60; const int hour = epoch % 24; epoch /= 24; int year = 1970; { int32_t days = 0; while ( ( days += ( isLeapYear( year ) ) ? 366 : 365 ) <= epoch ) ++year; days -= ( isLeapYear( year ) ) ? 366 : 365; epoch -= days; } uint8_t isLeap = isLeapYear( year ); int month = 1; for ( ; month < 13; ++month ) { int8_t length = 0; switch ( month ) { case 2: length = isLeap ? 29 : 28; break; case 4: case 6: case 9: case 11: length = 30; break; default: length = 31; } if ( epoch >= length ) epoch -= length; else break; } const int day = epoch + 1; std::stringstream ss; ss << year << '-'; if ( month < 10 ) ss << 0; ss << month << '-'; if ( day < 10 ) ss << 0; ss << day << 'T'; if ( hour < 10 ) ss << 0; ss << hour << ':'; if ( minute < 10 ) ss << 0; ss << minute << ':'; if ( second < 10 ) ss << 0; ss << second << '.'; if ( millis < 10 ) ss << "00"; else if ( millis < 100 ) ss << 0; ss << millis << 'Z'; return ss.str(); }
The recommended way to use the DateTime class is to use the singleton instance. Since Arduino is single-threaded there is rarely a need to create multiple instances of it. Usage is as simple as the following:
std::cout << F( "Current time: " ) << spt::net::DateTime::singleton() << std::endl;