To explain the routine for calculating the ISO week number, we first focus on the simple week number (SWN) defined below. At the end we give the necessary corrections to calculate the ISO week number.
The simple week number we define such that
For an arbitrary day (1..31) of a given month (1..12) and year, the SWN can be calculated by:
SWN( y, m, d ) = 1 + ( DP( y, m ) + d-1 ) / 7
where DP ("Days Passed") is given by:
DP( y, 1 ) = 0
DP( y, m+1 ) = DP( y, m ) + ML( y, m )
and ML ("Month Length") is defined as:
ML( y, 1 ) = 31
ML( y, 2 ) = 28 + LEAP( y )
ML( y, 3 ) = 31
ML( y, 4 ) = 30
ML( y, 5 ) = 31
ML( y, 6 ) = 30
ML( y, 7 ) = 31
ML( y, 8 ) = 31
ML( y, 9 ) = 30
ML( y, 10 ) = 31
ML( y, 11 ) = 30
ML( y, 12 ) = 31
and LEAP( y ) is defined as:
LEAP( y ) = ( y % 4 == 0 ) && ( ( y % 100 != 0 ) || ( y % 400 == 0 ) )
To improve on the full-fledged calculation described above, we first turn our
attention to the DP function. As a first approximation we ignore the exact
month lengths and instead assume a standard month length of 30 days.
With this approximation of DP, a first approximation of SWN can be
calculated by:
SWN1( y, m, d ) = 1 + 4 * (m-1) + ( 2 * (m-1) + d-1 ) / 7
With this formula, the simple week number is calculated correctly for some 240
of the 365/366 days in a year. By adding a rounding term of + 2 we can increase
the number of correctly calculated week numbers to almost 290:
SWN2( y, m, d ) = 1 + 4 * (m-1) + ( 2 * (m-1) + d-1 + 2 ) / 7
However, we can correctly calculate the week number for all 365/366 days in a year by making use of the simple day of the week (SDOW) function, defined as the current day for a given date, assuming that the year starts on a Monday.
For an arbitrary day (1..31) of a given month (1..12) and a year that starts on
a monday, the SDOW can be calculated by:
SDOW( y, m, d ) = ( DP( y, m ) + d-1 ) % 7 ( 0 = Monday, ..., 6 = Sunday )
Using this function, we define the third approximation of SWN as:
SWN3( y, m, d ) = 1 + 4 * (m-1) + ( 2 * (m-1) + d-1 - SDOW( y, m, d ) + 5 ) / 7
By exhaustive experiments, we verified that SWN3 equals SWN for all possible values of y, m, and d.
The calculation of SWN3 still contains an division by 7, which is not a standard
operation on the 6805. We approximate this division with a formula taken from
[BLINN95], which states that for division of an integer i by a number n = 2^k-1,
we can use the following approximation:
i / n = ( i + (n+1)/2 ) * ( (n+2) / (n+1)^2 )
In our case this leads to the fourth approximation for SWN:
SWN4( y, m, d ) = 1 + 4 * (m-1) + ( 2 * (m-1) + d-1 - SDOW( y, m, d ) + 6 ) * 9 / 64
Note that the rounding term has been adjusted, in order to guarantee equivalence between SWN4 and SWN.
Although the division by 64 can be implemented as a series of right shifts,
the 8x8->16 bits MUL operation available on the 6805 offers yet another
possibility of optimization: Instead of multiplying by 9, we can multiply both
numerator and denominator of the 9/64 fraction by 4 and get the division result
directly from the high byte of the multiplication result:
SWN5( y, m, d ) = 1 + 4 * (m-1) + ( 2 * (m-1) + d-1 - SDOW( y, m, d ) + 6 ) * 36 / 256
In [ISO8601], the week number is defined by:
This means that the days before week 1 in a given year are attributed to the last week of the previous year. Also the days that come after the last week of a given year are attributed to the first week of the next year.
If we adapt approximation SWN5 for the simple week number to reflect the
differences between the definitions of both week numbers, we arrive at the
final solution, adopted for the week number wristapp:
ISO_WN( y, m, d )
{
dow = DOW( y, m, d );
dow0101 = DOW( y, 1, 1 );
if ( m == 1 && 3 < dow0101 < 7 - (d-1) )
{
// days before week 1 of the current year have the same week number as
// the last day of the last week of the previous year
dow = dow0101 - 1;
dow0101 = DOW( y-1, 1, 1 );
m = 12;
d = 31;
}
else if ( m == 12 && 30 - (d-1) < DOW( y+1, 1, 1 ) < 4 )
{
// days after the last week of the current year have the same week number as
// the first day of the next year, (i.e. 1)
return 1;
}
return ( DOW( y, 1, 1 ) < 4 ) + 4 * (m-1) + ( 2 * (m-1) + (d-1) + dow0101 - dow + 6 ) * 36 / 256;
}
Note that the simple DOW function has been replaced by the (true) DOW, that is readily available in the Timex Datalink ROM.
| Stout, J.M., | The Netherlands | janstout@bigfoot.com |