Subversion Repositories Projects

Rev

Go to most recent revision | Blame | Last modification | View Log | RSS feed

/*****************************************************************************
 *   Copyright (C) 2013 Oliver Gemesi                                        *
 *                                                                           *
 *   This program is free software; you can redistribute it and/or modify    *
 *   it under the terms of the GNU General Public License as published by    *
 *   the Free Software Foundation; either version 2 of the License.          *
 *                                                                           *
 *   This program is distributed in the hope that it will be useful,         *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
 *   GNU General Public License for more details.                            *
 *                                                                           *
 *   You should have received a copy of the GNU General Public License       *
 *   along with this program; if not, write to the                           *
 *   Free Software Foundation, Inc.,                                         *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.               *
 *****************************************************************************/


//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+ xutils.c - erweiterte String-Funktionen, xprintf (Info nach History)
//+            und weitere Hilfsfunktionen wie z.B. UTCdatetime2local()
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

//############################################################################
//# HISTORY  xutils.c
//#
//# 12.04.2014 OG
//# - chg: strncpyat(), strncpyat_P(), _strncpyat() erweitert um Parameter 'sepcharcount'
//#
//# 08.04.2014 OG
//# - add: strncpyat(), strncpyat_P(), _strncpyat()
//#
//# 28.02.2014 OG
//# - add: buffered_sprintf(), buffered_sprintf_P()
//# - add: buffer sprintf_buffer[]
//# - chg: PGMBUFF_SIZE und ARGBUFF_SIZE von 80 auf 40 geaendert
//#
//# 24.06.2013 OG
//# - add: strrtrim() entfernt Leerzeichen auf der rechten Seite
//#
//# 14.05.2013 OG
//# - chg: Kommentarkopf von UTCdatetime2local() aktualisiert
//#
//# 05.05.2013 OG
//# - chg: UTCdatetime2local() auf Config.timezone/summertime umgestellt
//# - add: include eeprom.h
//#
//# 04.05.2013 OG
//# - chg: umbenannt zu xutils.c
//#
//# 03.05.2013 OG
//# - add: UTCdatetime2local()
//# - fix: _xvsnprintf() Darstellung kleiner negativer Nachkommazahlen (-1<z<0)
//#
//# 29.04.2013 OG
//# - chg: Doku zu xprintf Ergaenzt bei 'bekannte Einschraenkungen'
//# - chg: includes reduziert auf das Notwendige
//#
//# 28.04.2013 OG - NEU
//############################################################################


//############################################################################
//# xprintf
//#
//# Diese Variante von printf ist angepasst auf das PKT:
//#
//#  - Unterstuetzung von Festkomma-Integer Anzeige
//#  - Overflow-Anzeige durch '*' wenn eine Zahl die Format-Maske sprengt
//#  - progmen wird optional unterstuetz fuer den format-String als auch fuer
//#     String Argumente
//#  - strikte Einhaltung von Laengen einer Format-Maske
//#    (ggf. wird die Ausgabe gekuerzt)
//#
//# In diesem Source sind nur die Basis-xprintf zum Erzeugen von formatierten
//# Strings. Die direkten Ausgabefunktionen auf den Screen sind in lcd.c
//# als lcdx_printf_at() und lcdx_printf_at_P().
//#
//# FORMAT ANGABEN:
//#
//#   %d: dezimal int signed (Rechtsbuendige Ausgabe)   (Wandlung via itoa)
//#       "%d"    arg: 1234 -> "1234"
//#       "%5d"   arg: 1234 -> " 1234"
//#       "%5.2d" arg: 1234 -> "   12.34"
//#       "%05d"  arg: 123  -> "00123"
//#       "%3.2d" arg: -13  -> " -0.13"
//#
//#   %u: dezimal int unsigned (Rechtsbuendige Ausgabe) (Wandlung via utoa)
//#       wie %d jedoch mittels utoa
//#
//#   %h: hex int unsigned    -> Hex-Zahl     Rechtsbuendig, Laenge wird unterstuetzt z.B. "%4h"
//#   %o: octal int unsigned  -> Octal-Zahl   Rechtsbuendig, Laenge wird unterstuetzt z.B. "%2o"
//#   %b: binary int unsigned -> Binaer-Zahl  Rechtsbuendig, Laenge wird unterstuetzt z.B. "%8b"
//#
//#   %ld, %lu, %lh, %lo, %lb:
//#       wie die obigen jedoch fuer long-zahlen (via ltoa, ultoa)
//#       nur wenn define USE_XPRINTF_LONG gesetzt ist!
//#       Bespiele: "%ld", "%5.6ld" usw.
//#
//#   %s: String aus RAM      -> Linksbuendig, Laenge wird unterstuetzt (z.B. "%8s")
//#   %S: String aus progmem  -> Linksbuendig, Laenge wird unterstuetzt (z.B. "%8s")
//#
//#   %c: einzelnes char      -> Linksbuendig, Laenge wird unterstuetzt (z.B. "%8s")
//#   %%: Ausgabe von "%"
//#
//#
//# BEISPIELE:
//#
//#  vorhanden in: osd/osdata.c, lcd/lcd.c
//#
//#
//# BEKANNTE EINSCHRAENKUNGEN:
//#
//#  1. padding '0' mit negativen Zahlen wenn padding aktiv ist ergibt z.B. "00-1.5"
//#  2. der Entwickler muss auf eine korrekte Format-Maske achten. Andernfalls
//#     kommt es zu nicht vorhersehbaren Ausgaben.
//#  3. nicht in ISR's verwenden - dazu muss ggf. eine Anpassung durchgefuert werden
//#
//# KOMPILIER OPTIONEN
//#
//#  define: USE_XPRINTF_LONG
//#    Unterstuetzung von long int / unsigned long int ('l' Modifier in Maske)
//     Wird in der main.h gesetzt
//############################################################################

#define USE_XPRINTF_LONG  // Unterstuetzung von long integer - Achtung! Muss fuer PKT gesetzt sein
                          // da in Sourcen verwendet!


#include <avr/pgmspace.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>

#include "../main.h"
#include "../timer/timer.h"
#include "../eeprom/eeprom.h"


#define PGMBUFF_SIZE 40     // max. 40 chars bei format-String aus progmem
#define ARGBUFF_SIZE 40     // buffer fuer xprintf Parameter (strings, itoa, utoa) (fuer ltoa ggf. anpassen)


#define SPRINTF_BUFFER_SIZE  40                  // max. 40 Chars fuer den Buffer fuer xsnprintf(), xsnprintf_P()
char sprintf_buffer[SPRINTF_BUFFER_SIZE];


//----------------------
// xprintf parser data
//----------------------
typedef struct
{
    uint8_t  parse;     // true / false
    char cmd;           // char: d u h o b s S c %
    uint8_t  prelen;    // Vorkomma
    uint8_t  declen;    // decimal (Nachkomma)
    uint8_t  point;     // true / false
    uint8_t  uselong;   // true / false
    uint8_t  mask;      // true / false
    char pad;           // ' ' oder '0'
} xprintf_t;


//----------------------
// Buffers
//----------------------
char cmddigit[7];
char argbuff[ARGBUFF_SIZE];
char pgmbuff[PGMBUFF_SIZE];


//####################################################################################


//---------------------------------------------------------------------
// Basisfunktion von xprintf
// Doku: siehe oben
//---------------------------------------------------------------------
void _xvsnprintf( uint8_t useprogmem, char *buffer, uint8_t n, const char *format, va_list ap )
{
    const char  *p_fmt;                         // pointer auf den format-String
    char        *p_dst;                         // pointer auf den destination buffer
    const char  *p_str;                         // pointer auf einen arg-String wenn ein String ausgegeben werden soll
    char        *p_cmddigit = 0;                // pointer auf den Digit-Buffer fuer eine Maske (Laenge/Nachkommastellen)
    const char  *p_fmtbuff;                     // pointer auf den format-Buffer (fuer progmem)
    const char  *p_argbuff;                     // pointer auf den argbuffer
    uint8_t     fmtlen, arglen, overflow;
    uint8_t     i,j,dec;
    uint8_t     dstcnt;
    uint8_t     minus;
    xprintf_t   cmd;                            // parser Daten

    p_fmtbuff = format;

    if( useprogmem )                            // format von progmem in's RAM kopieren
    {
        strncpy_P( pgmbuff, format, PGMBUFF_SIZE);
        pgmbuff[PGMBUFF_SIZE-1] = 0;
        p_fmtbuff = pgmbuff;
    }

    cmd.parse = false;
    p_fmt     = p_fmtbuff-1;                    // -1 -> wird am Anfang der Schleife korrigiert
    p_dst     = buffer;
    dstcnt    = 0;

    do
    {
        if( dstcnt >= n )                       // max. Anzahl von Zeichen fuer Ziel 'buffer' erreicht?
            break;

        p_fmt++;                                // naechstes Zeichen von format

        //########################
        //# 1. PARSE
        //########################

        //------------------------
        // START: parse cmd
        //------------------------
        if( cmd.parse == false && *p_fmt == '%' )
        {
            memset( &cmd, 0, sizeof(xprintf_t) );       // init
            cmd.parse  = true;
            cmd.pad    = ' ';
            p_cmddigit = cmddigit;
            continue;
        }

        //------------------------
        // NO parse: copy char
        //------------------------
        if( cmd.parse == false )
        {
            *p_dst = *p_fmt;
            p_dst++;
            dstcnt++;
            continue;
        }

        //------------------------
        // set: pad (eine '0' ganz am Anfang der Formatmaske)
        //------------------------
        if( cmd.parse == true && *p_fmt == '0' && p_cmddigit == cmddigit )
        {
            cmd.pad = '0';
            continue;
        }

        //------------------------
        // set: vor/nach-kommastellen
        //------------------------
        if( cmd.parse == true && *p_fmt >= '0' && *p_fmt <= '9' )
        {
            *p_cmddigit = *p_fmt;
            p_cmddigit++;
            continue;
        }

        //------------------------
        // set: point
        //------------------------
        if( cmd.parse == true && *p_fmt == '.' && cmd.point == false )
        {
            cmd.point   = true;
            *p_cmddigit = 0;
            cmd.prelen  = atoi( cmddigit );
            p_cmddigit  = cmddigit;
            continue;
        }

        //------------------------
        // set: uselong
        //------------------------
        #ifdef  USE_XPRINTF_LONG
        if( cmd.parse == true && *p_fmt == 'l' )
        {
            cmd.uselong  = true;
            continue;
        }
        #endif

        //------------------------
        // END: parse cmd
        //------------------------
        if( cmd.parse == true && (*p_fmt == 'd' || *p_fmt == 'u' || *p_fmt == 'x' || *p_fmt == 'b' || *p_fmt == 'o' ||
                                  *p_fmt == 's' || *p_fmt == 'S' || *p_fmt == 'c' || *p_fmt == '%') )
        {
            cmd.cmd     = *p_fmt;
            cmd.parse   = false;

            *p_cmddigit = 0;
            if( cmd.point == false )    cmd.prelen = atoi(cmddigit);
            else                        cmd.declen = atoi(cmddigit);

            if( cmd.point || cmd.prelen>0 )
                cmd.mask  = true;
        }


        //########################
        //# 2. EXECUTE
        //########################

        //------------------------
        // exec cmd: "d,u,x,b,o"
        //------------------------
        if( cmd.cmd == 'd' || cmd.cmd == 'u' || cmd.cmd == 'x' || cmd.cmd == 'b' || cmd.cmd == 'o' )
        {
            if( cmd.uselong )
            {
#ifdef  USE_XPRINTF_LONG
                switch(cmd.cmd)
                {
                    case 'd': ltoa ( va_arg(ap, long)         , argbuff, 10); break;    // LONG dezimal int signed
                    case 'u': ultoa( va_arg(ap, unsigned long), argbuff, 10); break;    // LONG dezimal int unsigned
                    case 'x': ultoa( va_arg(ap, unsigned long), argbuff, 16); break;    // LONG hex int unsigned
                    case 'b': ultoa( va_arg(ap, unsigned long), argbuff,  2); break;    // LONG binary int unsigned
                    case 'o': ultoa( va_arg(ap, unsigned long), argbuff,  8); break;    // LONG octal int unsigned
                }
#endif
            }
            else
            {
                switch(cmd.cmd)
                {
                    case 'd': itoa( va_arg(ap, int)         , argbuff, 10); break;      // dezimal int signed
                    case 'u': utoa( va_arg(ap, unsigned int), argbuff, 10); break;      // dezimal int unsigned
                    case 'x': utoa( va_arg(ap, unsigned int), argbuff, 16); break;      // hex int unsigned
                    case 'b': utoa( va_arg(ap, unsigned int), argbuff,  2); break;      // binary int unsigned
                    case 'o': utoa( va_arg(ap, unsigned int), argbuff,  8); break;      // octal int unsigned
                }
            }

            minus    = (argbuff[0] == '-');
            arglen   = strlen(argbuff);

            fmtlen   = cmd.prelen + cmd.declen + (cmd.point ? 1 : 0);
            arglen   = strlen(argbuff);

            overflow = cmd.mask &&                                                      // Zahl zu gross -> "*" anzeigen statt der Zahl
                         (arglen > cmd.prelen + cmd.declen || cmd.prelen < 1+minus);

            if( overflow )                                                              // overflow: Zahl passt nicht in Maske
            {                                                                           //   -> zeige '*.*'
                for( i=0; (i < fmtlen) && (dstcnt < n); i++)
                {
                    if( cmd.point && i==cmd.prelen )    *p_dst = '.';
                    else                                *p_dst = '*';
                    p_dst++;
                    dstcnt++;
                }
            }
            else                                                                        // else: if( overflow )
            {
                if( !cmd.mask )                                                         // keine Maske: alles von der Zahl ausgeben
                    fmtlen = arglen;

                p_argbuff = argbuff;
                if( minus )                                                             // wenn Zahl negativ: merken und auf dem argbuff nehmen
                {
                    p_argbuff++;
                    arglen--;
                }

                //-----------------
                // die Zahl wird 'Rueckwaerts' uebertragen
                //-----------------
                dec = -1;                                                               // wird am Anfang der Schleife auf 0 gesetzt
                j   = 1;                                                                // zaehler fuer argbuff
                for( i=1; i<=fmtlen; i++ )
                {
                    dec++;                                                              // Zaehler Dizmalstellen

                    if( dstcnt+fmtlen-i <= n )                                          // wenn Zielbuffer nicht ueberschritten
                    {
                        if( cmd.point && (dec == cmd.declen) )                          // Dezimalpunkt setzen
                        {
                            p_dst[fmtlen-i] = '.';
                            continue;
                        }

                        if( j <= arglen )                                               // Ziffer uebertragen aus argbuff
                        {
                            p_dst[fmtlen-i] = p_argbuff[arglen-j];
                            j++;
                            continue;
                        }

                        if( cmd.declen > 0 &&                                           // Nachkomma und 1. Vorkommastelle ggf. auf '0'
                              (dec < cmd.declen || dec == cmd.declen+1) )               //   setzen wenn die Zahl zu klein ist
                        {
                            p_dst[fmtlen-i] = '0';
                            continue;
                        }

                        if( minus && ( (cmd.pad == ' ') ||                              // ggf. Minuszeichen setzen
                                       (cmd.pad != ' ' && i == fmtlen) ) )              //  Minuszeichen bei '0'-padding an erster Stelle setzen
                        {
                            minus = false;
                            p_dst[fmtlen-i] = '-';
                            continue;
                        }

                        p_dst[fmtlen-i] = cmd.pad;                                      // padding ' ' oder '0'
                    }                                                                   // end: if( dstcnt+fmtlen-i <= n )
                }
                p_dst  += fmtlen;
                dstcnt += fmtlen;
            }
            continue;
        }

        //------------------------
        // exec cmd: "s", "S", "c"
        //------------------------
        if( cmd.cmd == 's' || cmd.cmd == 'S' || cmd.cmd == 'c' )
        {
            switch(cmd.cmd)
            {
                case 's':   p_str = va_arg( ap, char *);                                // String aus dem RAM
                            break;

                case 'S':   strncpy_P( argbuff, va_arg( ap, char *), ARGBUFF_SIZE);     // String liegt im progmem -> in's RAM kopieren
                            argbuff[ARGBUFF_SIZE-1] = 0;
                            p_str = argbuff;
                            break;

                case 'c':   argbuff[0] = va_arg( ap, int);                              // einzelnes char
                            argbuff[1] = 0;
                            p_str = argbuff;
                            break;
            }

            fmtlen = cmd.prelen;
            arglen = strlen(p_str);

            if( !cmd.mask )                                     // keine Maske: alles vom String ausgeben
                fmtlen = arglen;

            for( i=0; i<fmtlen; i++)
            {
                if( dstcnt < n )                                // wenn Zielbuffer nicht ueberschritten
                {
                    if( *p_str )                                // char uebertragen
                    {
                        *p_dst = *p_str;
                        p_str++;
                    }
                    else                                        // padding
                    {
                        *p_dst = ' ';
                    }
                    p_dst++;
                }
                dstcnt++;
            }

            continue;
        }

        //------------------------
        // exec cmd: "%"
        //------------------------
        if( cmd.cmd == '%' )
        {
            *p_dst = '%';
            p_dst++;
            dstcnt++;
            continue;
        }

    } while( (dstcnt < n) && *p_fmt );

    *(p_dst + (dstcnt < n ? 0 : -1)) = 0;       // terminierende 0 im Ausgabebuffer setzen
}



//---------------------------------------------------------------------
//---------------------------------------------------------------------
void xsnprintf( char *buffer, uint8_t n, const char *format, ... )
{
    va_list ap;

    va_start(ap, format);
    _xvsnprintf( false, buffer, n, format, ap);
    va_end(ap);
}


//---------------------------------------------------------------------
//---------------------------------------------------------------------
void xsnprintf_P( char *buffer, uint8_t n, const char *format, ... )
{
    va_list ap;

    va_start(ap, format);
    _xvsnprintf( true, buffer, n, format, ap);
    va_end(ap);
}



//-----------------------------------------------------------
// buffered_sprintf_P( format, ...)
//
// Ausgabe direkt in einen internen Buffer.
// Der Pointer auf den RAM-Buffer wird zurueckgegeben.
// Abgesichert bzgl. Buffer-Overflow.
//
// Groesse des Buffers: PRINTF_BUFFER_SIZE
//
// Parameter:
//  format     : String aus PROGMEM (siehe: xprintf in utils/xstring.h)
//  ...        : Parameter fuer 'format'
//-----------------------------------------------------------
char * buffered_sprintf( const char *format, ... )
{
    va_list ap;

    va_start( ap, format );
    _xvsnprintf( false, sprintf_buffer, SPRINTF_BUFFER_SIZE, format, ap );
    va_end(ap);
    return sprintf_buffer;
}



//-----------------------------------------------------------
// buffered_sprintf_P( format, ...)
//
// Ausgabe direkt in einen internen Buffer.
// Der Pointer auf den RAM-Buffer wird zurueckgegeben.
// Abgesichert bzgl. Buffer-Overflow.
//
// Groesse des Buffers: PRINTF_BUFFER_SIZE
//
// Parameter:
//  format     : String aus PROGMEM (siehe: xprintf in utils/xstring.h)
//  ...        : Parameter fuer 'format'
//-----------------------------------------------------------
char * buffered_sprintf_P( const char *format, ... )
{
    va_list ap;

    va_start( ap, format );
    _xvsnprintf( true, sprintf_buffer, SPRINTF_BUFFER_SIZE, format, ap );
    va_end(ap);
    return sprintf_buffer;
}



//--------------------------------------------------------------
// kopiert einen String von src auf dst mit fester Laenge und
// ggf. Space paddings rechts
//
// - fuellt ggf. den dst-String auf size Laenge mit Spaces
// - setzt Terminierung's 0 bei dst auf Position size
//--------------------------------------------------------------
void strncpyfill( char *dst, const char *src, size_t size)
{
    uint8_t i;
    uint8_t pad = false;

    for( i=0; i<size; i++)
    {
        if(*src == 0) pad = true;

        if( pad )   *dst = ' ';
        else        *dst = *src;

        src++;
        dst++;
    }
    dst--;
    *dst = 0;
}



//--------------------------------------------------------------
// entfernt rechte Leerzeichen aus einem String
//--------------------------------------------------------------
void strrtrim( char *dst)
{
    char    *p;

    p = dst + strlen(dst) - 1;

    while( (p != dst) && (*p == ' ') ) p--;

    if( (*p != ' ') && (*p != 0) ) p++;
    *p = 0;
}



//--------------------------------------------------------------
// INTERN - fuer strncpyat(), strncpyat_P()
//--------------------------------------------------------------
void _strncpyat( char *dst, const char *src, size_t size, const char sepchar, uint8_t sepcharcount, uint8_t progmem)
{
    uint8_t i;

    if( progmem )
        strncpy_P( dst, src, size);
    else
        strncpy( dst, src, size);

    for( i=0; i<size; i++)
    {
        if( *dst == 0) return;
        if( *dst == sepchar)
        {
            sepcharcount--;
            if( sepcharcount==0 )
            {
                *dst = 0;
                return;
            }
        }
        dst++;
    }
    dst--;
    *dst = 0;
}


//--------------------------------------------------------------
// strncpyat( dst, src, size, sepchar)
//
// kopiert einen String von 'src 'auf 'dst' mit max. Laenge 'size'
// oder bis 'sepchar' gefunden wird.
//
// src in PROGMEM
//--------------------------------------------------------------
void strncpyat( char *dst, const char *src, size_t size, const char sepchar, uint8_t sepcharcount)
{
    _strncpyat( dst, src, size, sepchar, sepcharcount, false);
}


//--------------------------------------------------------------
// strncpyat_P( dst, src, size, sepchar)
//
// kopiert einen String von 'src 'auf 'dst' mit max. Laenge 'size'
// oder bis 'sepchar' gefunden wird.
//
// src in RAM
//--------------------------------------------------------------
void strncpyat_P( char *dst, const char *src, size_t size, const char sepchar, uint8_t sepcharcount)
{
    _strncpyat( dst, src, size, sepchar, sepcharcount, true);
}



//--------------------------------------------------------------
// UTCdatetime2local( PKTdatetime_t *dtbuffer, PKTdatetime_t dt )
//
// konvertiert die UTC-Time 'dt' in die lokale Zeit und speichert
// dieses in 'dtbuffer' ab.
//
// Parameter:
//
//  dtdst: Pointer Destination (PKTdatetime_t) (Speicher muss alloziiert sein!)
//  dtsrc: Pointer Source (PKTdatetime_t)
//
// Hinweise:
//
//  Schaltjahre (bzw. der 29.02.) werden nicht unterstuetzt
//--------------------------------------------------------------
void UTCdatetime2local( PKTdatetime_t *dtdst, PKTdatetime_t *dtsrc )
{
    int8_t  timeoffset;
    int32_t v;
    int8_t  diff;
    //                    01 02 03 04 05 06 07 08 09 10 11 12    Monat
    int8_t  daymonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};

    //--------------------------
    // timezone: Einstellbereich -12 .. 0 .. 12 (+1 = Berlin)
    // summertime: Einstellung: 0 oder 1 (0=Winterzeit, 1=Sommerzeit)
    //--------------------------
    timeoffset = Config.timezone + Config.summertime;
    //timeoffset = 2;                                   // solange noch nicht in PKT-Config: Berlin, Sommerzeit


    memcpy( dtdst, dtsrc, sizeof(PKTdatetime_t) );      // copy datetime to destination

    //--------------------------
    // Zeitzonenanpassung
    //--------------------------
    if( dtdst->year != 0 && dtdst->month >= 1 && dtdst->month <= 12 )   // nur wenn gueltiges Datum vorhanden
    {
        //--------------------------
        // 1. Sekunden
        //--------------------------
        v = (int32_t)dtdst->seconds;
        v += timeoffset*3600;                           // Stunden korrigieren
        diff = 0;

        if( v > 86400 )                                 // Tagesueberschreitung?    (86400 = 24*60*60 bzw. 24 Stunden)
        {
            v -= 86400;
            diff++;                                     //  inc: Tag
        }
        else if( v < 0 )                                // Tagesunterschreitung?
        {
            v += 86400;
            diff--;                                     //  dec: Tag
        }
        dtdst->seconds = (uint32_t)v;                   // SET: seconds

        //--------------------------
        // 2. Tag
        //--------------------------
        v = (int32_t)dtdst->day;
        v += diff;
        diff = 0;

        if( v > daymonth[dtdst->month-1] )              // Monatsueberschreitung?
        {
            v = 1;                                      // erster Tag des Monats
            diff++;                                     //  inc: Monat
        }
        else if( v < 1 )                                // Monatsunterschreitung?
        {
            if( dtdst->month > 1 )
                v = daymonth[dtdst->month-1-1];         // letzter Tag des vorherigen Monats
            else
                v = 31;                                 // letzter Tag im Dezember des vorherigen Jahres
            diff--;                                     //   dec: Monat
        }
        dtdst->day = (uint8_t)v;                        // SET: day

        //--------------------------
        // 3. Monat
        //--------------------------
        v = (int32_t)dtdst->month;
        v += diff;
        diff = 0;

        if( v > 12 )                                    // Jahresueberschreitung?
        {
            v = 1;
            diff++;                                     //  inc: Jahr
        }
        else if( v < 1 )                                // Jahresunterschreitung?
        {
            v = 12;
            diff--;                                     //  dec: Jahr
        }
        dtdst->month = (uint8_t)v;                      // SET: month

        //--------------------------
        // 4. Jahr
        //--------------------------
        dtdst->year += diff;                            // SET: year
    }
}