Sans Pareil Technologies, Inc.

Key To Your Business

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.

    1: #ifndef SPT_NET_DATETIME_H
    2: #define SPT_NET_DATETIME_H
    3:
    4: #if defined( ARDUINO )
    5: #include "../StandardCplusplus/iostream"
    6: #include "../StandardCplusplus/string"
    7: #else
    8: #include <iostream>
    9: #include <string>
   10: #endif
   11:
   12: namespace spt
   13: {
   14:   namespace net
   15:   {
   16:     /**
   17:      * @brief Represents current date/time.  Seeds initially (and daily)
   18:      * from a network time service, and uses internal timer to emulate
   19:      * a real-time clock.
   20:      */
   21:     class DateTime
   22:     {
   23:     public:
   24:       /// Default constructor.  Use {@link #singleton} in general.
   25:       DateTime();
   26:
   27:       /// Return the current date/time in ISO 8601 format
   28:       const std::string currentTime();
   29:
   30:       /// Return the current date in ISO 8601 format
   31:       const std::string date();
   32:
   33:       /// Return the milli seconds since UNIX epoch.
   34:       int64_t currentTimeMillis();
   35:
   36:       /// Return a singleton instance to use.  This is the preferred way
   37:       /// of using this class.
   38:       static DateTime& singleton();
   39:
   40:     private:
   41:       void init();
   42:       const std::string serverTime();
   43:       bool isLeapYear( int16_t year ) const;
   44:       int64_t epochMilliSeconds( const std::string& iso8601 ) const;
   45:       std::string isoTime( int64_t epoch ) const;
   46:
   47:     private:
   48:       static const int64_t milliSecondsPerHour;
   49:       static const int64_t minEpoch;
   50:
   51:       uint32_t startTimeMillis;
   52:       uint32_t lastUpdateMillis;
   53:       int64_t milliSecondsSinceEpoch;
   54:     };
   55:
   56:     /// Serialise the date-time to the output stream.
   57:     inline std::ostream& operator << ( std::ostream& os, DateTime& dt )
   58:     {
   59:       os << dt.currentTime();
   60:       return os;
   61:     }
   62:
   63:     /// Append the current time to the specified string
   64:     inline std::string& operator += ( std::string& str, DateTime& dt )
   65:     {
   66:       return str.append( dt.currentTime() );
   67:     }
   68:   }
   69: }
   70:
   71: #endif // SPT_NET_DATETIME_H



DateTime.cpp

The implementation of the date-time class.

    1: #include "DateTime.h"
    2: #include "SHttpClient.h"
    3:
    4: #if defined( ARDUINO )
    5: #include "Arduino.h"
    6: #include "../StandardCplusplus/cstdlib"
    7: #include "../StandardCplusplus/sstream"
    8: #else
    9: #include <cstdlib>
   10: #include <sstream>
   11: #endif
   12:
   13: using std::string;
   14: using spt::net::DateTime;
   15:
   16: const int64_t DateTime::minEpoch = int64_t( 1430704319000 );
   17: const int64_t DateTime::milliSecondsPerHour = int64_t( 3600000 );
   18:
   19:
   20: DateTime::DateTime() : startTimeMillis( millis() ),
   21:   lastUpdateMillis( startTimeMillis ), milliSecondsSinceEpoch( 0 )
   22: {
   23:   init();
   24: }
   25:
   26:
   27: const string DateTime::currentTime()
   28: {
   29:   return isoTime( currentTimeMillis() );
   30: }
   31:
   32:
   33: const string DateTime::date()
   34: {
   35:   const string& dt = currentTime();
   36:   return dt.substr( 0, dt.find( "T" ) );
   37: }
   38:
   39:
   40: int64_t DateTime::currentTimeMillis()
   41: {
   42:   uint32_t time = millis();
   43:
   44:   if ( ( time - startTimeMillis ) > uint64_t( 86400000 ) ) init();
   45:   else
   46:   {
   47:     milliSecondsSinceEpoch += time - lastUpdateMillis;
   48:     lastUpdateMillis = time;
   49:   }
   50:
   51:   return milliSecondsSinceEpoch;
   52: }
   53:
   54:
   55: DateTime& DateTime::singleton()
   56: {
   57:   static DateTime dt;
   58:   return dt;
   59: }
   60:
   61:
   62: void DateTime::init()
   63: {
   64:   const string& iso = serverTime();
   65:   milliSecondsSinceEpoch = epochMilliSeconds( iso );
   66:   if ( milliSecondsSinceEpoch < minEpoch )
   67:   {
   68:     uint32_t diff = uint32_t( minEpoch - milliSecondsSinceEpoch );
   69:     std::cout << "minEpoch greater than server time.  Difference " << diff << std::endl;
   70:     milliSecondsSinceEpoch = minEpoch;
   71:   }
   72:
   73:   uint32_t time = millis();
   74:   milliSecondsSinceEpoch += time - lastUpdateMillis;
   75:   startTimeMillis = time;
   76:   lastUpdateMillis = time;
   77: }
   78:
   79:
   80: const string DateTime::serverTime()
   81: {
   82:   using spt::net::HttpClient;
   83:   using spt::net::HttpRequest;
   84:
   85:   static string server( "www.timeapi.org" );
   86:   static string uri( "/utc/now" );
   87:   static string userAgentKey( "User-Agent" );
   88:   static string userAgentValue( "QSense" );
   89:
   90:   HttpClient::Ptr client = HttpClient::create();
   91:
   92:   if ( client->connect( server ) )
   93:   {
   94:     std::cout << F( "Connected to " ) << server << std::endl;
   95:
   96:     HttpRequest request( uri );
   97:     request.setHeader( userAgentKey, userAgentValue );
   98:
   99:     uint16_t responseCode = client->get( request );
  100:     std::cout << F( "Server returned HTTP response code: " ) << responseCode << std::endl;
  101:
  102:     if ( responseCode == 200 && client->connected() ) return client->readBody();
  103:   }
  104:   else
  105:   {
  106:     std::cout << F( "Connection to " ) << server << F( " failed" ) << std::endl;
  107:   }
  108:
  109:   return string();
  110: }
  111:
  112:
  113: bool DateTime::isLeapYear( int16_t year ) const
  114: {
  115:   bool result = false;
  116:
  117:   if ( ( year % 400 ) == 0 ) result = true;
  118:   else if ( ( year % 100 ) == 0 ) result = false;
  119:   else if ( ( year % 4 ) == 0 ) result = true;
  120:
  121:   return result;
  122: }
  123:
  124:
  125: int64_t DateTime::epochMilliSeconds( const string& date ) const
  126: {
  127:   const int16_t year = std::atoi( date.substr( 0, 4 ).c_str() );
  128:   const int16_t month = std::atoi( date.substr( 5, 2 ).c_str() );
  129:   const int16_t day = std::atoi( date.substr( 8, 2 ).c_str() );
  130:   const int16_t hour = std::atoi( date.substr( 11, 2 ).c_str() );
  131:   const int16_t minute = std::atoi( date.substr( 14, 2 ).c_str() );
  132:   const int16_t second = std::atoi( date.substr( 17, 2 ).c_str() );
  133:   const int16_t dst = std::atoi( date.substr( 20, 2 ).c_str() );
  134:   const int16_t millis = 0;
  135:
  136:   int64_t epoch = millis;
  137:   epoch += second * int64_t( 1000 );
  138:   epoch += minute * int64_t( 60000 );
  139:   epoch += hour * milliSecondsPerHour;
  140:   epoch += ( day - 1 ) * 24 * milliSecondsPerHour;
  141:
  142:   const int8_t isLeap = isLeapYear( year );
  143:
  144:   for ( int i = 1; i < month; ++i )
  145:   {
  146:     switch ( i )
  147:     {
  148:       case 2:
  149:         epoch += ( (isLeap) ? 29 : 28 ) * 24 * milliSecondsPerHour;
  150:         break;
  151:       case 4:
  152:       case 6:
  153:       case 9:
  154:       case 11:
  155:         epoch += 30 * 24 * milliSecondsPerHour;
  156:         break;
  157:       default:
  158:         epoch += 31 * 24 * milliSecondsPerHour;
  159:     }
  160:   }
  161:
  162:   for ( int i = 1970; i < year; ++i )
  163:   {
  164:     if ( isLeapYear( i ) ) epoch += 366 * 24 * milliSecondsPerHour;
  165:     else epoch += 365 * 24 * milliSecondsPerHour;
  166:   }
  167:
  168:   if ( dst ) epoch -= dst * milliSecondsPerHour;
  169:   return epoch;
  170: }
  171:
  172:
  173: string DateTime::isoTime( int64_t epoch ) const
  174: {
  175:   const int millis = epoch % int64_t( 1000 );
  176:   epoch /= int64_t( 1000 );
  177:
  178:   const int second = epoch % 60;
  179:
  180:   epoch /= 60;
  181:   const int minute = epoch % 60;
  182:
  183:   epoch /= 60;
  184:   const int hour = epoch % 24;
  185:   epoch /= 24;
  186:   int year = 1970;
  187:
  188:   {
  189:     int32_t days = 0;
  190:     while ( ( days += ( isLeapYear( year ) ) ? 366 : 365 ) <= epoch ) ++year;
  191:
  192:     days -= ( isLeapYear( year ) ) ? 366 : 365;
  193:     epoch -= days;
  194:   }
  195:
  196:   uint8_t isLeap = isLeapYear( year );
  197:   int month = 1;
  198:
  199:   for ( ; month < 13; ++month )
  200:   {
  201:     int8_t length = 0;
  202:
  203:     switch ( month )
  204:     {
  205:       case 2:
  206:         length = isLeap ? 29 : 28;
  207:         break;
  208:       case 4:
  209:       case 6:
  210:       case 9:
  211:       case 11:
  212:         length = 30;
  213:         break;
  214:       default:
  215:         length = 31;
  216:     }
  217:
  218:     if ( epoch >= length ) epoch -= length;
  219:     else break;
  220:   }
  221:
  222:   const int day = epoch + 1;
  223:   std::stringstream ss;
  224:   ss << year << '-';
  225:
  226:   if ( month < 10 ) ss << 0;
  227:   ss << month << '-';
  228:
  229:   if ( day < 10 ) ss << 0;
  230:   ss << day << 'T';
  231:
  232:   if ( hour < 10 ) ss << 0;
  233:   ss << hour << ':';
  234:
  235:   if ( minute < 10 ) ss << 0;
  236:   ss << minute << ':';
  237:
  238:   if ( second < 10 ) ss << 0;
  239:   ss << second << '.';
  240:
  241:   if ( millis < 10 ) ss << "00";
  242:   else if ( millis < 100 ) ss << 0;
  243:   ss << millis << 'Z';
  244:
  245:   return ss.str();
  246: }