I read with disbelief about how most of Microsoft's 30 GB Zune players hung on Dec 31st, due to not being able to handle day number 366 of a leap year, and started thinking about some discussions we've had here over the years about the fastests/tightest/most elegant ways to convert between year-month-day and day number.
The code I wrote to handle this problem was written in Jan 2000, then we had another discussion in 2005, at which time one of the IBM regulars (probably Hack?) pointed me at an IBM report which used very similar ideas.
Anyway, my code from 2005 uses about 10-15 regular instructions, plus two unsigned multiplications, to do ymd2days, so about 10-20 clock cycles:
/* These operations are valid for any date after 1584, * according to the Gregorian (modern) calendar */
#define STARTYEAR 1200 /* Must be divisible by 400! */
/* Table to simplify conversions between mm-dd and day_in_year */ static short daysToMonth[13] = {-1,30,60,91,121,152,183,213,244,274,305,336,366};
unsigned ymd2days(unsigned y, unsigned m, unsigned d) { unsigned days, y100;
/* Start by setting March = month zero: */ mask_t mask = (mask_t) (m -= 3); /* Mask will now be in the (mask_t) -2 to 9 range, a shift by 4 is enough to make it -1 or 0: */ mask >>= 4;
DEC_IF_MASK(y, mask); /* Decrement year if jan/feb */ m += (mask & 12); /* and increment the month: The year starts in March! */ y -= STARTYEAR; y100 = y / 100; /* Centuries for leap year calc */ days = y * 365 + (y >> 2) - y100 + (y100 >> 2); days += (unsigned) daysToMonth[m] + d; return days;
}
The reverse conversion is the fun one: It turns out that the fastest method is to guess the year, then correct it if off by one. :-)
The code below will work on any platform which provides unsigned integers with at least 29 bits of precision, but it will be slightly faster with 30+ bit twos-complement signed ints.
void days2ymd(unsigned days, unsigned *yy, unsigned *mm, unsigned *dd) { unsigned y400, y100, y, m, d, gd; mask_t mask;
/* Start by subtracting any full 400-year cycles: */ y400 = days / 146097; days -= y400 * 146097;
#if 0 /* Very good approximation to the number of years, will be wrong * (too high) 257 times * in a 400-year cycle, i.e. in 0.18% of all calls. */
/* A good compiler will replace this with a scaled reciprocal MUL! */ y = (days + 1) * 400 / 146096; #else /* Useful and faster approximation to the number of years, will be * wrong (too high) 910 times * in a 400-year cycle, i.e. in 0.62% of all calls. * * Requires unsigned values with up to 29 significant bits! */ y = (days * 2871 + 1983) >> 20; /* y = (days * 22967 + 59235) >> 23; */ /* peter (at) horizon.com suggested this! */ #endif
/* Calculate # of centuries: * Since y will be in the 0 to 400 range, the following * approximation can be used instead of a division by 100: * y100 = y / 100; ~ (y * 41) >> 12; */ y100 = (y * 41) >> 12;
/* # of days in those full years */ gd = y * 365 /* Normal years */ + (y >> 2) /* + leap years every 4 years */ - y100 /* - missing century leap years */ + (y100 >> 2); /* + every 400 years */
/* test for the small chance of a wrong year: */ if (gd > days) { y--; /* y will be in the [0-399] range! */ y100 = (y * 41) >> 12; /* The 400-year correction can be skipped! */ gd = y * 365 + (y >> 2) - y100 /* + (y100 >> 2) */; }
/* Calculate the offset into the current year: */ days -= gd; /* Correct for starting date and 400-year cycles: */ y += STARTYEAR + y400 * 400;
#if 1 /* Make a FAST guess at the current month, can be too low: */ m = days >> 5;
mask = (mask_t) daysToMonth[m+1] - (mask_t) days; /* mask will be in the -18 to 18 range, use shift by 5: */ mask >>= 5; /* If the guess was wrong then the mask will be -1, otherwise 0: */
/* Increment month if needed */ INC_IF_MASK(m, mask);
/* The remainder is the day of the month */ d = days - (unsigned) daysToMonth[m];
/* Correct for the March 1 starting point: */ mask = (mask_t) (9 - m) >> 4; m += 3; INC_IF_MASK(y, mask); m -= (unsigned) (mask & 12);
#else /* Based on code from an IBM report: Slightly slower, but * uses no table lookups! */ m = ((days + 31) * 1071); d = (((m & 0x7fff) * 62669) >> 26) + 1; m >>= 15;
mask = (mask_t) (10 - m) >> 4; m += 2; INC_IF_MASK(y, mask); m -= (unsigned) (mask & 12); #endif
*yy = y; *mm = m; *dd = d;
}
This second function uses four integer multiplications, one of them is a reciprocal to avoid division, the rest are all shifts/adds/subs etc.
Running time is on the order of 30-70 cycles, depending upon the speed of integer muls.
Terje
-- - <Terje.Mathi...@hda.hydro.com> "almost all programming can be viewed as an exercise in caching"
+--------------- | I read with disbelief about how most of Microsoft's 30 GB Zune players | hung on Dec 31st, due to not being able to handle day number 366 of a | leap year... +---------------
It's worse that you think. ;-} ;-} See the latest issue of RISKS for the actual failing code:
http://catless.ncl.ac.uk/Risks/25.50.html#subj2.2 Zounds! Zinger: Zune Zapped Zealously with Zero-tolerance David Magda <dma...@ee.ryerson.ca> Thu, 1 Jan 2009 15:26:18 -0500 ... The issue is an infinite loop:
> while (days > 365) { > if (IsLeapYear(year)) { > if (days > 366) { > days -= 366; > year += 1; > } > } else { > days -= 365; > year += 1; > } > } ... http://www.zuneboards.com/forums/349447-post1.html
I suspect it was trying to do something like this, only it missed:
while (days > (IsLeapYear(year) ? 366 : 365)) { days -= (IsLeapYear(year) ? 366 : 365); year += 1; }
-Rob
p.s. Yes, I know, "IsLeapYear()" is probably expensive, so the speed demons among us will probably want to rewrite it like this: ;-}
while (days > (year_length = (IsLeapYear(year) ? 366 : 365))) { days -= year_length; year += 1; }
----- Rob Warnock <r...@rpw3.org> 627 26th Avenue <URL:http://rpw3.org/> San Mateo, CA 94403 (650)572-2607
Rob Warnock wrote: > Terje Mathisen <terje.mathi...@hda.hydro.com> wrote: > +--------------- > | I read with disbelief about how most of Microsoft's 30 GB Zune players > | hung on Dec 31st, due to not being able to handle day number 366 of a > | leap year... > +---------------
> It's worse that you think. ;-} ;-} See the latest issue of RISKS
No, it isn't.
> for the actual failing code:
I first found a link to the full driver code, and knowing there was a date bug, it took me less than a minute to locate the error below.
> > while (days > 365) { > > if (IsLeapYear(year)) { > > if (days > 366) { > > days -= 366; > > year += 1; > > } > > } else { > > days -= 365; > > year += 1; > > } > > } > p.s. Yes, I know, "IsLeapYear()" is probably expensive, so the > speed demons among us will probably want to rewrite it like this: ;-}
> while (days > (year_length = (IsLeapYear(year) ? 366 : 365))) { > days -= year_length; > year += 1; > }
The point is that the fully correct version, that handles all possible dates until we run out of bits in integers, runs _faster_ than even the rewritten version, since the loop has to iterate at least 28 times!
I just reran a benchmark of the functions I posted: Doing the conversions both forwards and back, plus a post-conversion check that all generated dates are legal, and that the input date was equal to the output:
It took about 42 ns/iteration on my laptop, which means about 90 clock cycles, of which maximum 70 can have been used for the days2ymd() call.
28++ iterations of a loop which includes an inline IsLeapYear() call has to take significantly more time!
Terje
-- - <Terje.Mathi...@hda.hydro.com> "almost all programming can be viewed as an exercise in caching"
Terje Mathisen <terje.mathi...@hda.hydro.com> wrote: > I read with disbelief about how most of Microsoft's 30 GB Zune players > hung on Dec 31st
It should be noted that this appears to be a bug in some versions of the Freescale iMX31 Board Support Package for Windows CE. Obviously from the consumer's point of view it make little difference if it is MS's or Freescale's bug.
-p -- "Unix is user friendly, it's just picky about who its friends are." - Anonymous --------------------------------------------------------------------
On Mon, 05 Jan 2009 11:05:45 -0000, Terje Mathisen
<terje.mathi...@hda.hydro.com> wrote: > I read with disbelief about how most of Microsoft's 30 GB Zune players > hung on Dec 31st, due to not being able to handle day number 366 of a > leap year
Disbelief? You jest, sir. Leap year problems are easy to pose but the historical evidence is that both programmers and QA testers find them almost impossible to handle. My impression is that far more software failed on 29:Feb:2000 than did 8 weeks previously, which is quite stunning when you consider that the requirement here was for a no-op.
"Ken Hagan" <K.Ha...@thermoteknix.com> writes: > Disbelief? You jest, sir. Leap year problems are easy to pose but the > historical evidence is that both programmers and QA testers find them > almost impossible to handle. My impression is that far more software > failed on 29:Feb:2000 than did 8 weeks previously, which is quite > stunning when you consider that the requirement here was for a no-op.
there was y2k discussion thread in the early 80s (talking about the impending century problem) ... and there were instances of other kinds of failures in processing dates ... frequent failures involve leap yrs; one of the entries extracted here (including problem in airline reservation system that happened on 29feb72 and a problem in how dates are handled for shuttle missions): http://www.garlic.com/~lynn/99.html#email841207
Ken Hagan <K.Ha...@thermoteknix.com> wrote: > Disbelief? You jest, sir. Leap year problems are easy to pose but the > historical evidence is that both programmers and QA testers find them > almost impossible to handle.
Of course date-sensitive bugs are the worst sort, since they're generally impossible to detect through testing unless you test at exactly the right time, and worse, they tend to strike your entire customer base on the same day, often with very little warning (one learns to pay more attention to calls from New Zealand).
I wonder how many products now have, as part of their QA, a machine running with a clock set some time in the future, say 1-3 months ahead, for the express purpose of finding this sort of thing.
A related class of bugs that's also hard to test for are the "system fails after being up for exactly N days", but these at least have a simple temporary workaround and don't strike everyone at once.
On Mon, 05 Jan 2009 12:05:45 +0100, Terje Mathisen wrote: > I read with disbelief about how most of Microsoft's 30 GB Zune players > hung on Dec 31st, due to not being able to handle day number 366 of a > leap year, and started thinking about some discussions we've had here > over the years about the fastests/tightest/most elegant ways to convert > between year-month-day and day number.
The thing that I find curious about these sorts of incident is what business of devices like the Zune is the date, anyway? I suppose that they have some kind of convenience clock function, like my iPod, but I'm sure most users wouldn't care if they just didn't.
There were lots of industrial/control/embedded devices that were held up as potential y2k catastrophe initiators, back then, that had no business knowing what the date was, IMO. None of the audio gear that I've built ever did, except for that one that was sitting inside an industrial Windows PC. I suppose the Zune is a bit like that...
| > p.s. Yes, I know, "IsLeapYear()" is probably expensive, so the | > speed demons among us will probably want to rewrite it like this: ;-} | > | > while (days > (year_length = (IsLeapYear(year) ? 366 : 365))) { | > days -= year_length; | > year += 1; | > } | | The point is that the fully correct version, that handles all possible | dates until we run out of bits in integers, runs _faster_ than even the | rewritten version, since the loop has to iterate at least 28 times! +---------------
Yes, of course. For the record, I was *not* arguing with your fully correct version or its performance, only noting that even a minimally-correct [though obviously slow] version is (1) one-third the size of the broken one, (2) slightly faster, (3) *much* easier both to write and to read later, and (4) trivial. To say it another way, "What *WERE* they thinking?!?" [Ans: Probably weren't.]
+--------------- | I just reran a benchmark of the functions I posted: ... | It took about 42 ns/iteration on my laptop, which means about 90 clock | cycles, of which maximum 70 can have been used for the days2ymd() call. +---------------
Neat!
-Rob
----- Rob Warnock <r...@rpw3.org> 627 26th Avenue <URL:http://rpw3.org/> San Mateo, CA 94403 (650)572-2607
back circa 1970 ... i spent 3months with a number of other people discussing what to do about "leap seconds" (that and what does the "start of the century" mean ... i.e. did the century start in 1900 or 1901?) ... this was for the 370 TOD clock.
-- 40+yrs virtualization experience (since Jan68), online at home since Mar70
Terje Mathisen wrote: > I read with disbelief about how most of Microsoft's 30 GB Zune players > hung on Dec 31st, due to not being able to handle day number 366 of a > leap year, and started thinking about some discussions we've had here > over the years about the fastests/tightest/most elegant ways to convert > between year-month-day and day number.
> The code I wrote to handle this problem was written in Jan 2000, then we > had another discussion in 2005, at which time one of the IBM regulars > (probably Hack?) pointed me at an IBM report which used very similar ideas.
> Anyway, my code from 2005 uses about 10-15 regular instructions, plus > two unsigned multiplications, to do ymd2days, so about 10-20 clock cycles:
[... code ...]
Yes, a good hint one is doing it wrong is when you spot a loop in date code. It's just plain broken.
> This second function uses four integer multiplications, one of them is a > reciprocal to avoid division, the rest are all shifts/adds/subs etc.
This is, of course, good if you have to do a *lot* of these conversions (or you're writing a library, in which case you have to assume so), but otherwise I'd suggest leaving the divisions in to make the code more understandable to lower-level programmers.
One important bit about anything dealing with Gregorian dates is the observation Christian Zeller did well over a century ago: the math gets a lot cleaner if you treat January and February as months 13 and 14 of (year-1). March was, in fact, historically the first month, as evidenced by our names for months 9 to 12: "September" ("seventh month") to "December" ("tenth month")...
On Wed, 07 Jan 2009 08:18:12 -0000, H. Peter Anvin <h...@zytor.com> wrote:
> ... March was, in fact, historically the first month, as > evidenced by our names for months 9 to 12: "September" ("seventh month") > to "December" ("tenth month")...
...and by the fact that the leap day was originally placed at the very end of the year (where any sane person would put it) but wasn't moved to December in the Gregorian reform, presumably for reasons of backwards compatibility. :)
H. Peter Anvin <h...@zytor.com> wrote: +--------------- | One important bit about anything dealing with Gregorian dates is the | observation Christian Zeller did well over a century ago: the math gets | a lot cleaner if you treat January and February as months 13 and 14 of | (year-1). March was, in fact, historically the first month, as | evidenced by our names for months 9 to 12: "September" ("seventh month") | to "December" ("tenth month")... +---------------
Eric Naggum's classic paper on the subject [known mostly among the Lisp community, but applicable & readily accessible to a general audience]:
... The Roman tradition of starting the year in the month of March has also been lost. Most agrarian societies were far more interested in the onset of spring than in the winter solstice, even though various deities were naturally celebrated when the sun returned. ...but Julius Caesar decided to move the Roman calendar back two months... far more important was the decision to retain the leap day in February. In the old calendar, the leap day was added at the end of the year, as makes perfect sense, when the month was already short, but now it is squeezed into the middle of the first quarter, complicating all sorts of calculations, and affecting how much people work. In the old days, the leap day was used as an extra day for the various fertility festivities.
[If one starts the year on March 1st] the simplicity of the scheme is quite amazing: a 400-year cycle not only starts 2000-03-01 (as it did 1600-03-01), it contains an even number of weeks: 20,871. This means that we can make do with a single 400-year calculation for all time within the Gregorian calendar with respect to days of week, leap days, etc. ... The LOCAL-TIME Concept ... Because we are [in 1999] very close to the beginning of the next 400-year leap-year cycle, thanks to Pope Gregory, day 0 is defined to be 2000-03-01, which much less arbitrary than other systems, but not obviously so. Each 400-year cycle contains 146,097 days, so an arbitrary decision was made to limit the day to a maximal negative value of -146,097, or 1600-03-01. This can be changed at the peril of accurately representing days that do not belong to the calendar used at the time. ... [Because bignum arithmetic is expensive] The LOCAL-TIME concept therefore represents time as three disjoint fixnums [relatively small tagged integers that consume only one machine word, including a few bits of type tag]:
1. the number of days since (or until, when negative) 2000-03-01 2. the number of seconds since the start of the day in Coordinated Universal Time 3. the number of milliseconds since the start of the second.
All numbers have origin 0. Only the number of days may be negative. ...[more details about why this makes sense, how conversions and date/time calculations (including intervals) are done in the system]...
Note: Even if you don't care for his proposed system, the paper is a delightful tour of calendric history which touches on the influences of geography, politics, science, and general historical randomness. ;-}
-Rob
----- Rob Warnock <r...@rpw3.org> 627 26th Avenue <URL:http://rpw3.org/> San Mateo, CA 94403 (650)572-2607
>Eric Naggum's classic paper on the subject [known mostly among the Lisp >community, but applicable & readily accessible to a general audience]:
>suggests doing something very similar:
> The Roman tradition of starting the year in the month of March > has also been lost. Most agrarian societies were far more > interested in the onset of spring than in the winter solstice, > even though various deities were naturally celebrated when the > sun returned. ...
Well, he may address the point, but that statement is grossly misleading in isolation.
The reason that the winter solstice was the primary seasonal festival, has survived Christianity more-or-less unchanged and is much more important (even today) in northern Europe than elsewhere is FAR more likely to be due to how close we live to the Arctic circle. All of the other agrarian societies came from closer to the equator, often much closer.
I am pretty surprised that a Norwegian missed that aspect!
On Wed, 07 Jan 2009 06:10:03 -0600, r...@rpw3.org (Rob Warnock) wrote: > [If one starts the year on March 1st] the simplicity of the scheme > is quite amazing: a 400-year cycle not only starts 2000-03-01 > (as it did 1600-03-01), it contains an even number of weeks: 20,871.
Is 20,871 supposed to be the number of weeks? If so, I'm not sure if the latter portion of the above statement is correct.
> Terje Mathisen wrote: >> I read with disbelief about how most of Microsoft's 30 GB Zune players >> hung on Dec 31st, due to not being able to handle day number 366 of a >> leap year, and started thinking about some discussions we've had here >> over the years about the fastests/tightest/most elegant ways to convert >> between year-month-day and day number. >> >> The code I wrote to handle this problem was written in Jan 2000, then we >> had another discussion in 2005, at which time one of the IBM regulars >> (probably Hack?) pointed me at an IBM report which used very similar ideas. >> >> Anyway, my code from 2005 uses about 10-15 regular instructions, plus >> two unsigned multiplications, to do ymd2days, so about 10-20 clock cycles: > > [... code ...] > > Yes, a good hint one is doing it wrong is when you spot a loop in date > code. It's just plain broken. > >> This second function uses four integer multiplications, one of them is a >> reciprocal to avoid division, the rest are all shifts/adds/subs etc. > > This is, of course, good if you have to do a *lot* of these conversions > (or you're writing a library, in which case you have to assume so), but > otherwise I'd suggest leaving the divisions in to make the code more > understandable to lower-level programmers.
Sorry: It is written as a constant division, but I have verified (by code inspection) that pretty much all modern compilers, for x86, Power, Sparc, Alpha, ARM will convert that into a reciprocal division. > > One important bit about anything dealing with Gregorian dates is the > observation Christian Zeller did well over a century ago: the math gets > a lot cleaner if you treat January and February as months 13 and 14 of > (year-1). March was, in fact, historically the first month, as > evidenced by our names for months 9 to 12: "September" ("seventh month") > to "December" ("tenth month")...
<BG>
My STARTDATE is #define'd as March 1st, 1200 (or any other year divisible by 400).
I number the months from 0 to 11 starting with March, then correct at the end. Here's the conversion logic:
/* Correct for the March 1 starting point: */ mask = (mask_t) (9 - m) >> 4; // -1 if (m == 10 || m == 11) m += 3; INC_IF_MASK(y, mask); m -= (unsigned) (mask & 12);
Terje
-- - <Terje.Mathi...@hda.hydro.com> "almost all programming can be viewed as an exercise in caching"
n...@cam.ac.uk wrote: > The reason that the winter solstice was the primary seasonal > festival, has survived Christianity more-or-less unchanged and is > much more important (even today) in northern Europe than elsewhere > is FAR more likely to be due to how close we live to the Arctic > circle. All of the other agrarian societies came from closer to > the equator, often much closer.
> I am pretty surprised that a Norwegian missed that aspect!
I don't think he missed it that badly, it is common knowledge that after Olaf Trygvasson (later St Olaf) converted the country from the old Norse gods to Christianity, the new priests simply subsumed the old Juleblot, celebrated around winter solstice, into the Christmas celebration.
Here in Norway (as well as the rest of Scandinavia afaik) we still call it 'Jul' and 'Julefeiring (Jul celebration)', and the days are named Julaften (Jul evening) for the 24th, then 1 and 2 Juledag (1st and 2nd day of Jul), going all the way till either the 13th or 20th day of Jul when the festivities are officially over.
Jesus' birth, Christianity etc isn't mentioned at all in the naming of the period.
Terje -- - <Terje.Mathi...@hda.hydro.com> "almost all programming can be viewed as an exercise in caching"
Muzaffer Kal wrote: > On Wed, 07 Jan 2009 06:10:03 -0600, r...@rpw3.org (Rob Warnock) wrote: >> [If one starts the year on March 1st] the simplicity of the scheme >> is quite amazing: a 400-year cycle not only starts 2000-03-01 >> (as it did 1600-03-01), it contains an even number of weeks: 20,871.
> Is 20,871 supposed to be the number of weeks? If so, I'm not sure if > the latter portion of the above statement is correct.
146097 : 7 = 20871, so this is correct.
It does mean that you can discard the number of initial 400-year cycles when using the day number to calculate the day of week.
OTOH, I much prefer to put the zero point at 1200-03-01, or even earlier, since this means that all Gregorian dates will have a positive day number, something which makes the conversion algorithms more easily portable across all cpu architectures, including those without two's complement integer arithmetic.
Terje
-- - <Terje.Mathi...@hda.hydro.com> "almost all programming can be viewed as an exercise in caching"
In article <Gf6dnTS8KtWqn_jUnZ2dnUVZ8qXin...@giganews.com>, Terje Mathisen <terje.mathi...@hda.hydro.com> wrote:
>n...@cam.ac.uk wrote: >> The reason that the winter solstice was the primary seasonal >> festival, has survived Christianity more-or-less unchanged and is >> much more important (even today) in northern Europe than elsewhere >> is FAR more likely to be due to how close we live to the Arctic >> circle. All of the other agrarian societies came from closer to >> the equator, often much closer.
>> I am pretty surprised that a Norwegian missed that aspect!
>I don't think he missed it that badly, it is common knowledge that after >Olaf Trygvasson (later St Olaf) converted the country from the old Norse >gods to Christianity, the new priests simply subsumed the old Juleblot, >celebrated around winter solstice, into the Christmas celebration.
Yes, of course, but I was referring to the relative importance of the winter solstice and 'spring' - specifically with respect to when the year starts.
Where we live, the feeling "Thank God, at least the days are getting longer" is dominant, and must have been more so in the days of candles. In places closer to the equator, the gloom of winter is FAR less marked, and consequentially the solstice is less important.
Terje Mathisen <terje.mathi...@hda.hydro.com> writes: > n...@cam.ac.uk wrote: >> The reason that the winter solstice was the primary seasonal >> festival, has survived Christianity more-or-less unchanged and is >> much more important (even today) in northern Europe than elsewhere >> is FAR more likely to be due to how close we live to the Arctic >> circle. All of the other agrarian societies came from closer to >> the equator, often much closer.
>> I am pretty surprised that a Norwegian missed that aspect!
> I don't think he missed it that badly, it is common knowledge that > after Olaf Trygvasson (later St Olaf) converted the country from the > old Norse gods to Christianity, the new priests simply subsumed the > old Juleblot, celebrated around winter solstice, into the Christmas > celebration.
> Here in Norway (as well as the rest of Scandinavia afaik) we still > call it 'Jul' and 'Julefeiring (Jul celebration)', and the days are > named Julaften (Jul evening) for the 24th, then 1 and 2 Juledag (1st > and 2nd day of Jul), going all the way till either the 13th or 20th > day of Jul when the festivities are officially over.
Yup (although spelling varies slightly). Even the english have 'Yuletide' - adapted from the Danish vikings ;-)
Kai -- Kai Harrekilde-Petersen <khp(at)harrekilde(dot)dk>
Terje Mathisen <terje.mathi...@hda.hydro.com> writes: > Muzaffer Kal wrote: >> On Wed, 07 Jan 2009 06:10:03 -0600, r...@rpw3.org (Rob Warnock) wrote: >>> [If one starts the year on March 1st] the simplicity of the scheme >>> is quite amazing: a 400-year cycle not only starts 2000-03-01 >>> (as it did 1600-03-01), it contains an even number of weeks: 20,871.
>> Is 20,871 supposed to be the number of weeks? If so, I'm not sure if >> the latter portion of the above statement is correct.
> 146097 : 7 = 20871, so this is correct.
> It does mean that you can discard the number of initial 400-year > cycles when using the day number to calculate the day of week.
Though I believe that makes it an integral number of weeks, not an even number.
n...@cam.ac.uk writes: > Where we live, the feeling "Thank God, at least the days are getting > longer" is dominant, and must have been more so in the days of > candles. In places closer to the equator, the gloom of winter is > FAR less marked, and consequentially the solstice is less important.
And you near live Cambridge? =8-|
Its' quite marked how just the ~500km from Copenhagen to Oslo and again Oslo - Trondheim changes the amount of daylight both in the winter and the summer.
Kai -- Kai Harrekilde-Petersen <khp(at)harrekilde(dot)dk>
>>> The reason that the winter solstice was the primary seasonal >>> festival, has survived Christianity more-or-less unchanged and is >>> much more important (even today) in northern Europe than elsewhere >>> is FAR more likely to be due to how close we live to the Arctic >>> circle. All of the other agrarian societies came from closer to >>> the equator, often much closer.
>> Here in Norway (as well as the rest of Scandinavia afaik) we still >> call it 'Jul' and 'Julefeiring (Jul celebration)', and the days are >> named Julaften (Jul evening) for the 24th, then 1 and 2 Juledag (1st >> and 2nd day of Jul), going all the way till either the 13th or 20th >> day of Jul when the festivities are officially over.
>Yup (although spelling varies slightly). Even the english have >'Yuletide' - adapted from the Danish vikings ;-)
Yes. As many people have pointed out, the English festival of Christmas has never had anything to do with Christianity, and is merely a renamed festival of Yule. That is what I meant by "surviving Christianity" - it is the only one of the major seasonal festivals that remains in anything approximating its original state.
We don't know for certain that Yule was introduced into Europe by the Germanic peoples, and was not originally a Celtic festival, as far as I know. Given the importance of Hogmanay, my personal guess is the latter.
>> Where we live, the feeling "Thank God, at least the days are getting >> longer" is dominant, and must have been more so in the days of >> candles. In places closer to the equator, the gloom of winter is >> FAR less marked, and consequentially the solstice is less important.
>And you near live Cambridge? =8-|
>Its' quite marked how just the ~500km from Copenhagen to Oslo and >again Oslo - Trondheim changes the amount of daylight both in the >winter and the summer.
Quite. Britain's winters are gloomier than you might expect from the latitude, so Cambridge and Copenhagen are probably comparable in that respect. But, before the rise of northern Europe, the northernmost 'civilisation' was centred at 42 degrees north!
Muzaffer Kal wrote: > On Wed, 07 Jan 2009 06:10:03 -0600, r...@rpw3.org (Rob Warnock) wrote: >> [If one starts the year on March 1st] the simplicity of the scheme >> is quite amazing: a 400-year cycle not only starts 2000-03-01 >> (as it did 1600-03-01), it contains an even number of weeks: 20,871.
> Is 20,871 supposed to be the number of weeks? If so, I'm not sure if > the latter portion of the above statement is correct.
A 400-year cycle, no matter when you start and end it, contains exactly 20871 weeks or 146097 days. Starting it on 1600-3-1 simplifies the leap year calculation slightly. Starting it on March 1st simplifies the month calculation. Even though Augustus screwed up the original scheme, where a division by 30.5 gave the correct month, he didn't screw up too badly. Now a division by 30.6 gives the correct month (rounding still tricky, but with the right offset, it works). There are 153 days from 3-1 to 8-1, and another 153 days from 8-1 to 1-1 next year. The remaining 59 or 60 days follow the same pattern: First month 31 days, next month less, so that scheme works again.
So how to calculate the current year-month-day?
* year=floor(day/146097)*400 gives your base cycle start, day=day%146097 will be used further. (day+2)%7 gives day-of-the-week. First day of the week is Monday (if your week starts on another day, change the offset).
* year+=min(floor(day/36524),3)*100 gives the century (you can't have a century 4 here, it's just the hangover leap year in y2k); of course we keep the rest day=day%36524 for further calculation (add 365 if the division actually gave 4).
* year+=floor(day/1461)*4 breaks things down further, we keep day=day%1461.
* year+=min(floor(day/365),3) gives the current year, again we keep the remainder day=day%365 as above (if min triggers, add 365).
* month=floor((day+31)*5/153). We keep floor(remainder/5) as day of the month.
* Finally, we make the necessary corrections: month+=2, if month>12, year++, month-=12.
No table necessary. I tested this with a fairly short Forth program:
\ convert day since 0-3-1 to ymd
: /mod3 ( n1 n2 -- r q ) dup >r /mod dup 4 = IF drop r@ + 3 THEN rdrop ;
: day2dow ( day -- dow ) 2 + 7 mod ;
: day2ymd ( day -- y m d ) 146097 /mod 400 * swap 36524 /mod3 100 * rot + swap 1461 /mod 4 * rot + swap 365 /mod3 rot + swap 31 + 5 153 */mod swap 5 / >r 2 + dup 12 > IF 12 - swap 1+ swap THEN r> 1+ ;
: ymd2day ( y m d -- day ) -rot 2 - dup 0<= IF 12 + swap 1- swap THEN 153 5 */ 31 - swap 4 /mod swap 365 * swap 25 /mod swap 1461 * swap 4 /mod swap 36524 * swap 146097 * + + + + + ;
With floored division, this works even fine with negative numbers, just remember that the year 0 really is 1BC. It is fairly trivial to extend this little program to work correctly even before the October 15th, 1582 (or whenever the switch happend according to the current locale).