Timezone.cpp
#include &Timezone.h>
/*----------------------------------------------------------------------*
* Arduino Timezone Library *
* Jack Christensen Mar 2012 *
* *
* Arduino Timezone Library Copyright (C) 2018 by Jack Christensen and *
* licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html *
*----------------------------------------------------------------------*/
#include "Timezone.h"
#ifdef __AVR__
#include <avr/eeprom.h>
#endif
/*----------------------------------------------------------------------*
* Create a Timezone object from the given time change rules. *
*----------------------------------------------------------------------*/
Timezone::
Timezone
( TimeChangeRule dstStart, TimeChangeRule stdStart)
: m_dst(dstStart), m_std(stdStart)
{
initTimeChanges();
}
/*----------------------------------------------------------------------*
* Create a Timezone object for a zone that does not observe *
* daylight time. *
*----------------------------------------------------------------------*/
Timezone::
Timezone
( TimeChangeRule stdTime )
: m_dst(stdTime), m_std(stdTime)
{
initTimeChanges();
}
#ifdef __AVR__
/*----------------------------------------------------------------------*
* Create a Timezone object from time change rules stored in EEPROM *
* at the given address. *
*----------------------------------------------------------------------*/
Timezone::
Timezone
( int address )
{
readRules( address );
}
#endif
/*----------------------------------------------------------------------*
* Convert the given UTC time to local time, standard or *
* daylight time, as appropriate. *
*----------------------------------------------------------------------*/
time_t Timezone::
toLocal
( time_t utc)
{ // RECALCULATE TIME CHANGE POINTS IF NEEDED
if( year( utc ) != year( m_dstUTC ) )
calcTimeChanges( year( utc ) );
if( utcIsDST( utc ) )
return( utc + m_dst.offset * SECS_PER_MIN );
else
return( utc + m_std.offset * SECS_PER_MIN );
}
/*----------------------------------------------------------------------*
* Convert the given UTC time to local time, standard or *
* daylight time, as appropriate, and return a pointer to the time *
* change rule used to do the conversion. The caller must take care *
* not to alter this rule. *
*----------------------------------------------------------------------*/
time_t Timezone::
toLocal
( time_t utc, TimeChangeRule **tcr)
{ // RECALCULATE TIME CHANGE POINTS IF NEEDED
if( year( utc ) != year( m_dstUTC) )
calcTimeChanges( year( utc ) );
if( utcIsDST( utc ) )
{
*tcr = &m_dst;
return utc + m_dst.offset * SECS_PER_MIN;
}
else
{
*tcr = &m_std;
return utc + m_std.offset * SECS_PER_MIN;
}
}
/*----------------------------------------------------------------------*
* Convert given local time to UTC time
*
* WARNING:
* This function is provided for completeness, but should seldom be
* needed and should be used sparingly and carefully.
*
* Ambiguous situations occur after the Standard-to-DST and
* DST-to-Standard time transitions. When changing to DST, there is
* one hour of local time that does not exist, since the clock moves
* forward one hour. Similarly, when changing to standard time, there
* is one hour of local times that occur twice since the clock moves
* back one hour.
*
* This function does not test whether it is passed an erroneous time
* value during the Local -> DST transition that does not exist
* If passed such a time, an incorrect UTC time value will be returned
*
* If passed a local time value during the DST -> Local transition
* that occurs twice, it will be treated as the earlier time, i.e.
* the time that occurs before the transistion.
* *
* Calling this function with local times during a transition interval
* should be avoided!
*----------------------------------------------------------------------*/
time_t Timezone::
toUTC
( time_t local)
{ // RECALCULATE TIME CHANGE POINTS IF NEEDED
if( year( local ) != year( m_dstLoc ) )
calcTimeChanges(year(local));
if( locIsDST( local ))
return local - m_dst.offset * SECS_PER_MIN;
else
return local - m_std.offset * SECS_PER_MIN;
}
/*----------------------------------------------------------------------*
* Determine whether the given UTC time_t is within the DST interval *
* or the Standard time interval. *
*----------------------------------------------------------------------*/
bool Timezone::
utcIsDST
( time_t utc )
{ // RECALCULATE TIME CHANGE POINTS IF NEEDED
if( year( utc ) != year( m_dstUTC ))
calcTimeChanges( year( utc ));
if( m_stdUTC == m_dstUTC ) // DAYLIGHT TIME NOT OBSERVED IN THIS TZ
return( false );
else if( m_stdUTC > m_dstUTC) // NORTHERN HEMISPHERE
return( utc >= m_dstUTC && utc < m_stdUTC );
else // SOUTHERN HEMISPHERE
return( !(utc >= m_stdUTC && utc < m_dstUTC ) );
}
/*----------------------------------------------------------------------*
* Determine whether the given Local time_t is within the DST interval *
* or the Standard time interval. *
*----------------------------------------------------------------------*/
bool Timezone::
locIsDST
( time_t local )
{ // RECALCULATE TIME CHANGE POINTS IF NEEDED
if( year( local ) != year( m_dstLoc ))
calcTimeChanges( year( local ) );
if( m_stdUTC == m_dstUTC ) // DAYLIGHT TIME NOT OBSERVED IN THIS TZ
return( false );
else if( m_stdLoc > m_dstLoc ) // NORTHERN HEMISPHERE
return( (local >= m_dstLoc && local < m_stdLoc) );
else // SOUTHERN HEMISPHERE
return( !(local >= m_stdLoc && local < m_dstLoc) );
}
/*----------------------------------------------------------------------*
* Calculate the DST and standard time change points for the given *
* given year as local and UTC time_t values. *
*----------------------------------------------------------------------*/
void Timezone::
calcTimeChanges
( int yr )
{
m_dstLoc = toTime_t( m_dst, yr);
m_stdLoc = toTime_t( m_std, yr);
m_dstUTC = m_dstLoc - m_std.offset * SECS_PER_MIN;
m_stdUTC = m_stdLoc - m_dst.offset * SECS_PER_MIN;
}
/*----------------------------------------------------------------------*
* Initialize the DST and standard time change points. *
*----------------------------------------------------------------------*/
void Timezone::
initTimeChanges
()
{
m_dstLoc = 0;
m_stdLoc = 0;
m_dstUTC = 0;
m_stdUTC = 0;
}
/*----------------------------------------------------------------------*
* Convert the given time change rule to a time_t value *
* for the given year. *
*----------------------------------------------------------------------*/
time_t Timezone::
toTime_t
( TimeChangeRule r, int yr )
{
uint8_t m = r.month; // TEMP COPIES OF R.MONTH AND R.WEEK
uint8_t w = r.week;
if( w == 0 ) // IS THIS A "lAST WEEK" RULE?
{
if( ++m > 12) // YES, FOR "lAST", GO TO THE NEXT MONTH
{
m = 1;
++yr;
}
w = 1; // AND TREAT AS 1st WEEK OF NEXT MONTH, SUBTRACT 7 DAYS LATER
}
// CALCULATE FIRST DAY OF MONTH, OR FOR "lAST" RULES, FIRST DAY OF NEXT MONTH
tmElements_t tm;
tm.Hour = r.hour;
tm.Minute = 0;
tm.Second = 0;
tm.Day = 1;
tm.Month = m;
tm.Year = yr - 1970;
time_t t = makeTime( tm );
// ADD OFFSET FROM FIRST OF MONTH TO R.DOW, AND OFFSET FOR GIVEN WEEK
t += ( (r.dow - weekday(t) + 7) % 7 + (w - 1) * 7 ) * SECS_PER_DAY;
// BACK UP A WEEK IF THIS IS A "lAST" RULE
if( r.week == 0)
t -= 7 * SECS_PER_DAY;
return( t );
}
/*----------------------------------------------------------------------*
* Read or update the daylight and standard time rules from RAM. *
*----------------------------------------------------------------------*/
void Timezone::
setRules
( TimeChangeRule dstStart, TimeChangeRule stdStart )
{
m_dst = dstStart;
m_std = stdStart;
initTimeChanges(); // FORCE calcTimeChanges() AT NEXT CONVERSION CALL
}
#ifdef __AVR__
/*----------------------------------------------------------------------*
* Read the daylight and standard time rules from EEPROM at *
* the given address. *
*----------------------------------------------------------------------*/
void Timezone::
readRules
( int address )
{
eeprom_read_block( (void*)&m_dst, (void*)address, sizeof( m_dst ));
address += sizeof( m_dst );
eeprom_read_block( (void*)&m_std, (void*)address, sizeof( m_std ));
initTimeChanges(); // FORCE calcTimeChanges() AT NEXT CONVERSION CALL
}
/*----------------------------------------------------------------------*
* Write the daylight and standard time rules to EEPROM at *
* the given address. *
*----------------------------------------------------------------------*/
void Timezone::
writeRules
( int address )
{
eeprom_write_block( (void*)&m_dst, (void*)address, sizeof( m_dst ));
address += sizeof( m_dst );
eeprom_write_block( (void*)&m_std, (void*)address, sizeof( m_std ));
}
#endif