336 lines
9.2 KiB
C
336 lines
9.2 KiB
C

/*



* SPDXLicenseIdentifier: BSD2ClauseFreeBSD



*



* Copyright (c) 1997 Wolfgang Helbig



* All rights reserved.



*



* Redistribution and use in source and binary forms, with or without



* modification, are permitted provided that the following conditions



* are met:



* 1. Redistributions of source code must retain the above copyright



* notice, this list of conditions and the following disclaimer.



* 2. Redistributions in binary form must reproduce the above copyright



* notice, this list of conditions and the following disclaimer in the



* documentation and/or other materials provided with the distribution.



*



* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND



* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE



* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE



* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE



* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL



* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS



* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)



* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT



* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY



* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF



* SUCH DAMAGE.



*/






#include <sys/cdefs.h>



#include <langinfo.h>



__FBSDID("$FreeBSD: head/lib/libcalendar/calendar.c 326219 20171126 02:00:33Z pfg $");






#include "calendar.h"



extern int weekstart;






#ifndef NULL



#define NULL 0



#endif






/*



* For each month tabulate the number of days elapsed in a year before the



* month. This assumes the internal date representation, where a year



* starts on March 1st. So we don't need a special table for leap years.



* But we do need a special table for the year 1582, since 10 days are



* deleted in October. This is month1s for the switch from Julian to



* Gregorian calendar.



*/



static int const month1[] =



{0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337};



/* M A M J J A S O N D J */



static int const month1s[]=



{0, 31, 61, 92, 122, 153, 184, 214, 235, 265, 296, 327};






typedef struct date date;






/* The last day of Julian calendar, in internal and ndays representation */



static int nswitch; /* The last day of Julian calendar */



static date jiswitch = {1582, 7, 3};






static date *date2idt(date *idt, date *dt);



static date *idt2date(date *dt, date *idt);



static int ndaysji(date *idt);



static int ndaysgi(date *idt);



static int firstweek(int year);






/*



* Compute the Julian date from the number of days elapsed since



* March 1st of year zero.



*/



date *



jdate(int ndays, date *dt)



{



date idt; /* Internal date representation */



int r; /* hold the rest of days */






/*



* Compute the year by starting with an approximation not smaller



* than the answer and using linear search for the greatest



* year which does not begin after ndays.



*/



idt.y = ndays / 365;



idt.m = 0;



idt.d = 0;



while ((r = ndaysji(&idt)) > ndays)



idt.y;






/*



* Set r to the days left in the year and compute the month by



* linear search as the largest month that does not begin after r



* days.



*/



r = ndays  r;



for (idt.m = 11; month1[idt.m] > r; idt.m)



;






/* Compute the days left in the month */



idt.d = r  month1[idt.m];






/* return external representation of the date */



return (idt2date(dt, &idt));



}






/*



* Return the number of days since March 1st of the year zero.



* The date is given according to Julian calendar.



*/



int



ndaysj(date *dt)



{



date idt; /* Internal date representation */






if (date2idt(&idt, dt) == NULL)



return (1);



else



return (ndaysji(&idt));



}






/*



* Same as above, where the Julian date is given in internal notation.



* This formula shows the beauty of this notation.



*/



static int



ndaysji(date * idt)



{






return (idt>d + month1[idt>m] + idt>y * 365 + idt>y / 4);



}






/*



* Compute the date according to the Gregorian calendar from the number of



* days since March 1st, year zero. The date computed will be Julian if it



* is older than 15821005. This is the reverse of the function ndaysg().



*/



date *



gdate(int ndays, date *dt)



{



int const *montht; /* monthtable */



date idt; /* for internal date representation */



int r; /* holds the rest of days */






/*



* Compute the year by starting with an approximation not smaller



* than the answer and search linearly for the greatest year not



* starting after ndays.



*/



idt.y = ndays / 365;



idt.m = 0;



idt.d = 0;



while ((r = ndaysgi(&idt)) > ndays)



idt.y;






/*



* Set ndays to the number of days left and compute by linear



* search the greatest month which does not start after ndays. We



* use the table month1 which provides for each month the number



* of days that elapsed in the year before that month. Here the



* year 1582 is special, as 10 days are left out in October to



* resynchronize the calendar with the earth's orbit. October 4th



* 1582 is followed by October 15th 1582. We use the "switch"



* table month1s for this year.



*/



ndays = ndays  r;



if (idt.y == 1582)



montht = month1s;



else



montht = month1;






for (idt.m = 11; montht[idt.m] > ndays; idt.m)



;






idt.d = ndays  montht[idt.m]; /* the rest is the day in month */






/* Advance ten days deleted from October if after switch in Oct 1582 */



if (idt.y == jiswitch.y && idt.m == jiswitch.m && jiswitch.d < idt.d)



idt.d += 10;






/* return external representation of found date */



return (idt2date(dt, &idt));



}






/*



* Return the number of days since March 1st of the year zero. The date is



* assumed Gregorian if younger than 15821004 and Julian otherwise. This



* is the reverse of gdate.



*/



int



ndaysg(date *dt)



{



date idt; /* Internal date representation */






if (date2idt(&idt, dt) == NULL)



return (1);



return (ndaysgi(&idt));



}






/*



* Same as above, but with the Gregorian date given in internal



* representation.



*/



static int



ndaysgi(date *idt)



{



int nd; /* Number of daysreturn value */






/* Cache nswitch if not already done */



if (nswitch == 0)



nswitch = ndaysji(&jiswitch);






/*



* Assume Julian calendar and adapt to Gregorian if necessary, i. e.



* younger than nswitch. Gregori deleted



* the ten days from Oct 5th to Oct 14th 1582.



* Thereafter years which are multiples of 100 and not multiples



* of 400 were not leap years anymore.



* This makes the average length of a year



* 365d +.25d  .01d + .0025d = 365.2425d. But the tropical



* year measures 365.2422d. So in 10000/3 years we are



* again one day ahead of the earth. Sigh :)



* (d is the average length of a day and tropical year is the



* time from one spring point to the next.)



*/



if ((nd = ndaysji(idt)) == 1)



return (1);



if (idt>y >= 1600)



nd = (nd  10  (idt>y  1600) / 100 + (idt>y  1600) / 400);



else if (nd > nswitch)



nd = 10;



return (nd);



}






/*



* Compute the week number from the number of days since March 1st year 0.



* The weeks are numbered per year starting with 1. If the first



* week of a year includes at least four days of that year it is week 1,



* otherwise it gets the number of the last week of the previous year.



* The variable y will be filled with the year that contains the greater



* part of the week.



*/



int



week(int nd, int *y)



{



date dt;



int fw; /* 1st day of week 1 of previous, this and



* next year */



gdate(nd, &dt);



for (*y = dt.y + 1; nd < (fw = firstweek(*y)); (*y))



;



return ((nd  fw) / 7 + 1);



}






/* return the first day of week 1 of year y */



static int



firstweek(int y)



{



date idt;



int nd, wd;






idt.y = y  1; /* internal representation of y11 */



idt.m = 10;



idt.d = 0;






nd = ndaysgi(&idt);



/*



* If more than 3 days of this week are in the preceding year, the



* next week is week 1 (and the next sunday/monday is the answer),



* otherwise this week is week 1 and the last sunday/monday is the



* answer.



*/



/* 3 may or may not be correct, better use what the locale says */



if ((wd = weekday(nd) + 1  weekstart) >= *nl_langinfo(_NL_TIME_WEEK_1STWEEK))



return (nd  wd + 7);



else



return (nd  wd);



}






/* return the weekday (Mo = 0 .. Su = 6) */



int



weekday(int nd)



{



date dmondaygi = {1997, 8, 16}; /* Internal repr. of 19971117 */



static int nmonday; /* ... which is a monday */






/* Cache the daynumber of one monday */



if (nmonday == 0)



nmonday = ndaysgi(&dmondaygi);






/* return (nd  nmonday) modulo 7 which is the weekday */



nd = (nd  nmonday) % 7;



if (nd < 0)



return (nd + 7);



else



return (nd);



}






/*



* Convert a date to internal date representation: The year starts on



* March 1st, month and day numbering start at zero. E. g. March 1st of



* year zero is written as y=0, m=0, d=0.



*/



static date *



date2idt(date *idt, date *dt)



{






idt>d = dt>d  1;



if (dt>m > 2) {



idt>m = dt>m  3;



idt>y = dt>y;



} else {



idt>m = dt>m + 9;



idt>y = dt>y  1;



}



if (idt>m < 0  idt>m > 11  idt>y < 0)



return (NULL);



else



return idt;



}






/* Reverse of date2idt */



static date *



idt2date(date *dt, date *idt)



{






dt>d = idt>d + 1;



if (idt>m < 10) {



dt>m = idt>m + 3;



dt>y = idt>y;



} else {



dt>m = idt>m  9;



dt>y = idt>y + 1;



}



if (dt>m < 1)



return (NULL);



else



return (dt);



}
