Port of Arduino Timezone library to Mbed for setting daylight saving/summer time

Dependents:   WebTimer DISCO-F746NG_light_control_system_tth

Timezone.cpp

Committer:
hudakz
Date:
2020-11-11
Revision:
0:38a95c82b08c

File content as of revision 0:38a95c82b08c:

/*----------------------------------------------------------------------*
 * 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"
#include "mbed_mktime.h"

/*----------------------------------------------------------------------*
 * 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();
}

/*----------------------------------------------------------------------*
 * Convert the given UTC time to local time, standard or                *
 * daylight time, as appropriate.                                       *
 *----------------------------------------------------------------------*/
time_t Timezone::toLocal(time_t utc)
{
    // recalculate the 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 the 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 the 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 the         *
 * 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 the 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 the 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 the 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 first week of next month, subtract 7 days later
    }

    // calculate first day of the month, or for "Last" rules, first day of the next month
    tm  tmTime;
    tmTime.tm_hour = r.hour;
    tmTime.tm_min = 0;
    tmTime.tm_sec = 0;
    tmTime.tm_mday = 1;
    tmTime.tm_mon = m;
    tmTime.tm_year = yr - 1970;

    time_t  t;
    _rtc_maketime(&tmTime, &t, RTC_FULL_LEAP_YEAR_SUPPORT);

    // add offset from the first of the month to r.dow, and offset for the given week
    tm  tmNow;

    _rtc_localtime(t, &tmNow, RTC_FULL_LEAP_YEAR_SUPPORT);

    t += ((r.dow - tmNow.tm_wday + 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
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int Timezone::year(time_t seconds)
{
    tm  tmNow;

    _rtc_localtime(seconds, &tmNow, RTC_FULL_LEAP_YEAR_SUPPORT);
    return tmNow.tm_year;
}