Files
RockOS/rocklibc/src/_tzcode/_PDCLIB_timesub.c

191 lines
4.3 KiB
C
Raw Normal View History

/* _PDCLIB_timesub( const time_t *, int_fast32_t, const struct state *, struct tm * )
This file is part of the Public Domain C Library (PDCLib).
Permission is granted to use, modify, and / or redistribute at will.
*/
#ifndef REGTEST
#include "pdclib/_PDCLIB_tzcode.h"
/* Return the number of leap years through the end of the given year
where, to make the math easy, the answer for year zero is defined as zero.
*/
static int leaps_thru_end_of_nonneg( int y )
{
return y / 4 - y / 100 + y / 400;
}
static int leaps_thru_end_of( const int y )
{
return ( y < 0
? -1 - leaps_thru_end_of_nonneg( -1 - y )
: leaps_thru_end_of_nonneg( y ) );
}
struct tm * _PDCLIB_timesub( const time_t * timep, int_fast32_t offset, const struct state * sp, struct tm * tmp )
{
const struct lsinfo * lp;
time_t tdays;
int idays; /* unsigned would be so 2003 */
int_fast64_t rem;
int y;
const int * ip;
int_fast64_t corr;
bool hit;
int i;
corr = 0;
hit = false;
i = ( sp == NULL ) ? 0 : sp->leapcnt;
while ( --i >= 0 )
{
lp = &sp->lsis[ i ];
if ( *timep >= lp->trans )
{
corr = lp->corr;
hit = ( *timep == lp->trans && ( i == 0 ? 0 : lp[ -1 ].corr ) < corr );
break;
}
}
y = EPOCH_YEAR;
tdays = *timep / SECSPERDAY;
rem = *timep % SECSPERDAY;
while ( tdays < 0 || tdays >= year_lengths[ _PDCLIB_is_leap( y ) ] )
{
int newy;
time_t tdelta;
int idelta;
int leapdays;
tdelta = tdays / DAYSPERLYEAR;
if ( ! ( ( ! _PDCLIB_TYPE_SIGNED( time_t ) || _PDCLIB_INT_MIN <= tdelta ) && tdelta <= _PDCLIB_INT_MAX ) )
{
goto out_of_range;
}
idelta = tdelta;
if ( idelta == 0 )
{
idelta = ( tdays < 0 ) ? -1 : 1;
}
newy = y;
if ( _PDCLIB_increment_overflow( &newy, idelta ) )
{
goto out_of_range;
}
leapdays = leaps_thru_end_of( newy - 1 ) - leaps_thru_end_of( y - 1 );
tdays -= ( (time_t)newy - y ) * DAYSPERNYEAR;
tdays -= leapdays;
y = newy;
}
/* Given the range, we can now fearlessly cast... */
idays = tdays;
rem += offset - corr;
while ( rem < 0 )
{
rem += SECSPERDAY;
--idays;
}
while ( rem >= SECSPERDAY )
{
rem -= SECSPERDAY;
++idays;
}
while ( idays < 0 )
{
if ( _PDCLIB_increment_overflow( &y, -1 ) )
{
goto out_of_range;
}
idays += year_lengths[ _PDCLIB_is_leap( y ) ];
}
while ( idays >= year_lengths[ _PDCLIB_is_leap( y ) ] )
{
idays -= year_lengths[ _PDCLIB_is_leap( y ) ];
if ( _PDCLIB_increment_overflow( &y, 1 ) )
{
goto out_of_range;
}
}
tmp->tm_year = y;
if ( _PDCLIB_increment_overflow( &tmp->tm_year, -TM_YEAR_BASE ) )
{
goto out_of_range;
}
tmp->tm_yday = idays;
/* The "extra" mods below avoid overflow problems. */
tmp->tm_wday = EPOCH_WDAY +
( ( y - EPOCH_YEAR ) % DAYSPERWEEK ) *
( DAYSPERNYEAR % DAYSPERWEEK ) +
leaps_thru_end_of( y - 1 ) -
leaps_thru_end_of( EPOCH_YEAR - 1 ) +
idays;
tmp->tm_wday %= DAYSPERWEEK;
if ( tmp->tm_wday < 0 )
{
tmp->tm_wday += DAYSPERWEEK;
}
tmp->tm_hour = (int)( rem / SECSPERHOUR );
rem %= SECSPERHOUR;
tmp->tm_min = (int)( rem / SECSPERMIN );
/* A positive leap second requires a special
representation. This uses "... ??:59:60" et seq.
*/
tmp->tm_sec = (int) ( rem % SECSPERMIN ) + hit;
ip = mon_lengths[ _PDCLIB_is_leap( y ) ];
for ( tmp->tm_mon = 0; idays >= ip[ tmp->tm_mon ]; ++( tmp->tm_mon ) )
{
idays -= ip[ tmp->tm_mon ];
}
tmp->tm_mday = (int)( idays + 1 );
tmp->tm_isdst = 0;
#ifdef TM_GMTOFF
tmp->TM_GMTOFF = offset;
#endif /* defined TM_GMTOFF */
return tmp;
out_of_range:
*_PDCLIB_errno_func() = _PDCLIB_EOVERFLOW;
return NULL;
}
#endif
#ifdef TEST
#include "_PDCLIB_test.h"
int main( void )
{
#ifndef REGTEST
#endif
return TEST_RESULTS;
}
#endif