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.
#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;