1 /* _PDCLIB_timesub( const time_t *, int_fast32_t, const struct state *, struct tm * )
2 
3    This file is part of the Public Domain C Library (PDCLib).
4    Permission is granted to use, modify, and / or redistribute at will.
5 */
6 
7 #ifndef REGTEST
8 
9 #include "pdclib/_PDCLIB_tzcode.h"
10 
11 /* Return the number of leap years through the end of the given year
12    where, to make the math easy, the answer for year zero is defined as zero.
13 */
leaps_thru_end_of_nonneg(int y)14 static int leaps_thru_end_of_nonneg( int y )
15 {
16     return y / 4 - y / 100 + y / 400;
17 }
18 
leaps_thru_end_of(const int y)19 static int leaps_thru_end_of( const int y )
20 {
21     return ( y < 0
22         ? -1 - leaps_thru_end_of_nonneg( -1 - y )
23         : leaps_thru_end_of_nonneg( y ) );
24 }
25 
_PDCLIB_timesub(const time_t * timep,int_fast32_t offset,const struct state * sp,struct tm * tmp)26 struct tm * _PDCLIB_timesub( const time_t * timep, int_fast32_t offset, const struct state * sp, struct tm * tmp )
27 {
28     const struct lsinfo * lp;
29     time_t                tdays;
30     int                   idays;  /* unsigned would be so 2003 */
31     int_fast64_t          rem;
32     int                   y;
33     const int *           ip;
34     int_fast64_t          corr;
35     bool                  hit;
36     int                   i;
37 
38     corr = 0;
39     hit = false;
40     i = ( sp == NULL ) ? 0 : sp->leapcnt;
41 
42     while ( --i >= 0 )
43     {
44         lp = &sp->lsis[ i ];
45         if ( *timep >= lp->trans )
46         {
47             corr = lp->corr;
48             hit = ( *timep == lp->trans && ( i == 0 ? 0 : lp[ -1 ].corr ) < corr );
49             break;
50         }
51     }
52 
53     y = EPOCH_YEAR;
54     tdays = *timep / SECSPERDAY;
55     rem = *timep % SECSPERDAY;
56 
57     while ( tdays < 0 || tdays >= year_lengths[ _PDCLIB_is_leap( y ) ] )
58     {
59         int    newy;
60         time_t tdelta;
61         int    idelta;
62         int    leapdays;
63 
64         tdelta = tdays / DAYSPERLYEAR;
65 
66         if ( ! ( ( ! _PDCLIB_TYPE_SIGNED( time_t ) || _PDCLIB_INT_MIN <= tdelta ) && tdelta <= _PDCLIB_INT_MAX ) )
67         {
68             goto out_of_range;
69         }
70 
71         idelta = tdelta;
72 
73         if ( idelta == 0 )
74         {
75             idelta = ( tdays < 0 ) ? -1 : 1;
76         }
77 
78         newy = y;
79 
80         if ( _PDCLIB_increment_overflow( &newy, idelta ) )
81         {
82             goto out_of_range;
83         }
84 
85         leapdays = leaps_thru_end_of( newy - 1 ) - leaps_thru_end_of( y - 1 );
86         tdays -= ( (time_t)newy - y ) * DAYSPERNYEAR;
87         tdays -= leapdays;
88         y = newy;
89     }
90 
91     /* Given the range, we can now fearlessly cast... */
92     idays = tdays;
93     rem += offset - corr;
94 
95     while ( rem < 0 )
96     {
97         rem += SECSPERDAY;
98         --idays;
99     }
100 
101     while ( rem >= SECSPERDAY )
102     {
103         rem -= SECSPERDAY;
104         ++idays;
105     }
106 
107     while ( idays < 0 )
108     {
109         if ( _PDCLIB_increment_overflow( &y, -1 ) )
110         {
111             goto out_of_range;
112         }
113 
114         idays += year_lengths[ _PDCLIB_is_leap( y ) ];
115     }
116 
117     while ( idays >= year_lengths[ _PDCLIB_is_leap( y ) ] )
118     {
119         idays -= year_lengths[ _PDCLIB_is_leap( y ) ];
120 
121         if ( _PDCLIB_increment_overflow( &y, 1 ) )
122         {
123             goto out_of_range;
124         }
125     }
126 
127     tmp->tm_year = y;
128 
129     if ( _PDCLIB_increment_overflow( &tmp->tm_year, -TM_YEAR_BASE ) )
130     {
131         goto out_of_range;
132     }
133 
134     tmp->tm_yday = idays;
135     /* The "extra" mods below avoid overflow problems. */
136     tmp->tm_wday = EPOCH_WDAY +
137         ( ( y - EPOCH_YEAR ) % DAYSPERWEEK ) *
138         ( DAYSPERNYEAR % DAYSPERWEEK ) +
139         leaps_thru_end_of( y - 1 ) -
140         leaps_thru_end_of( EPOCH_YEAR - 1 ) +
141         idays;
142     tmp->tm_wday %= DAYSPERWEEK;
143 
144     if ( tmp->tm_wday < 0 )
145     {
146         tmp->tm_wday += DAYSPERWEEK;
147     }
148 
149     tmp->tm_hour = (int)( rem / SECSPERHOUR );
150     rem %= SECSPERHOUR;
151     tmp->tm_min = (int)( rem / SECSPERMIN );
152 
153     /* A positive leap second requires a special
154        representation. This uses "... ??:59:60" et seq.
155     */
156     tmp->tm_sec = (int) ( rem % SECSPERMIN ) + hit;
157     ip = mon_lengths[ _PDCLIB_is_leap( y ) ];
158 
159     for ( tmp->tm_mon = 0; idays >= ip[ tmp->tm_mon ]; ++( tmp->tm_mon ) )
160     {
161         idays -= ip[ tmp->tm_mon ];
162     }
163 
164     tmp->tm_mday = (int)( idays + 1 );
165     tmp->tm_isdst = 0;
166 #ifdef TM_GMTOFF
167     tmp->TM_GMTOFF = offset;
168 #endif /* defined TM_GMTOFF */
169     return tmp;
170 
171  out_of_range:
172     *_PDCLIB_errno_func() = _PDCLIB_EOVERFLOW;
173     return NULL;
174 }
175 
176 #endif
177 
178 #ifdef TEST
179 
180 #include "_PDCLIB_test.h"
181 
main(void)182 int main( void )
183 {
184 #ifndef REGTEST
185 #endif
186 
187     return TEST_RESULTS;
188 }
189 
190 #endif
191