root/ext/calendar/gregor.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. SdnToGregorian
  2. GregorianToSdn

   1 /* $selId: gregor.c,v 2.0 1995/10/24 01:13:06 lees Exp $
   2  * Copyright 1993-1995, Scott E. Lee, all rights reserved.
   3  * Permission granted to use, copy, modify, distribute and sell so long as
   4  * the above copyright and this permission statement are retained in all
   5  * copies.  THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
   6  */
   7 
   8 /**************************************************************************
   9  *
  10  * These are the externally visible components of this file:
  11  *
  12  *     void
  13  *     SdnToGregorian(
  14  *         long int  sdn,
  15  *         int      *pYear,
  16  *         int      *pMonth,
  17  *         int      *pDay);
  18  *
  19  * Convert a SDN to a Gregorian calendar date.  If the input SDN is less
  20  * than 1, the three output values will all be set to zero, otherwise
  21  * *pYear will be >= -4714 and != 0; *pMonth will be in the range 1 to 12
  22  * inclusive; *pDay will be in the range 1 to 31 inclusive.
  23  *
  24  *     long int
  25  *     GregorianToSdn(
  26  *         int inputYear,
  27  *         int inputMonth,
  28  *         int inputDay);
  29  *
  30  * Convert a Gregorian calendar date to a SDN.  Zero is returned when the
  31  * input date is detected as invalid or out of the supported range.  The
  32  * return value will be > 0 for all valid, supported dates, but there are
  33  * some invalid dates that will return a positive value.  To verify that a
  34  * date is valid, convert it to SDN and then back and compare with the
  35  * original.
  36  *
  37  *     char *MonthNameShort[13];
  38  *
  39  * Convert a Gregorian month number (1 to 12) to the abbreviated (three
  40  * character) name of the Gregorian month (null terminated).  An index of
  41  * zero will return a zero length string.
  42  *
  43  *     char *MonthNameLong[13];
  44  *
  45  * Convert a Gregorian month number (1 to 12) to the name of the Gregorian
  46  * month (null terminated).  An index of zero will return a zero length
  47  * string.
  48  *
  49  * VALID RANGE
  50  *
  51  *     4714 B.C. to at least 10000 A.D.
  52  *
  53  *     Although this software can handle dates all the way back to 4714
  54  *     B.C., such use may not be meaningful.  The Gregorian calendar was
  55  *     not instituted until October 15, 1582 (or October 5, 1582 in the
  56  *     Julian calendar).  Some countries did not accept it until much
  57  *     later.  For example, Britain converted in 1752, The USSR in 1918 and
  58  *     Greece in 1923.  Most European countries used the Julian calendar
  59  *     prior to the Gregorian.
  60  *
  61  * CALENDAR OVERVIEW
  62  *
  63  *     The Gregorian calendar is a modified version of the Julian calendar.
  64  *     The only difference being the specification of leap years.  The
  65  *     Julian calendar specifies that every year that is a multiple of 4
  66  *     will be a leap year.  This leads to a year that is 365.25 days long,
  67  *     but the current accepted value for the tropical year is 365.242199
  68  *     days.
  69  *
  70  *     To correct this error in the length of the year and to bring the
  71  *     vernal equinox back to March 21, Pope Gregory XIII issued a papal
  72  *     bull declaring that Thursday October 4, 1582 would be followed by
  73  *     Friday October 15, 1582 and that centennial years would only be a
  74  *     leap year if they were a multiple of 400.  This shortened the year
  75  *     by 3 days per 400 years, giving a year of 365.2425 days.
  76  *
  77  *     Another recently proposed change in the leap year rule is to make
  78  *     years that are multiples of 4000 not a leap year, but this has never
  79  *     been officially accepted and this rule is not implemented in these
  80  *     algorithms.
  81  *
  82  * ALGORITHMS
  83  *
  84  *     The calculations are based on three different cycles: a 400 year
  85  *     cycle of leap years, a 4 year cycle of leap years and a 5 month
  86  *     cycle of month lengths.
  87  *
  88  *     The 5 month cycle is used to account for the varying lengths of
  89  *     months.  You will notice that the lengths alternate between 30
  90  *     and 31 days, except for three anomalies: both July and August
  91  *     have 31 days, both December and January have 31, and February
  92  *     is less than 30.  Starting with March, the lengths are in a
  93  *     cycle of 5 months (31, 30, 31, 30, 31):
  94  *
  95  *         Mar   31 days  \
  96  *         Apr   30 days   |
  97  *         May   31 days    > First cycle
  98  *         Jun   30 days   |
  99  *         Jul   31 days  /
 100  *
 101  *         Aug   31 days  \
 102  *         Sep   30 days   |
 103  *         Oct   31 days    > Second cycle
 104  *         Nov   30 days   |
 105  *         Dec   31 days  /
 106  *
 107  *         Jan   31 days  \
 108  *         Feb 28/9 days   |
 109  *                          > Third cycle (incomplete)
 110  *
 111  *     For this reason the calculations (internally) assume that the
 112  *     year starts with March 1.
 113  *
 114  * TESTING
 115  *
 116  *     This algorithm has been tested from the year 4714 B.C. to 10000
 117  *     A.D.  The source code of the verification program is included in
 118  *     this package.
 119  *
 120  * REFERENCES
 121  *
 122  *     Conversions Between Calendar Date and Julian Day Number by Robert J.
 123  *     Tantzen, Communications of the Association for Computing Machinery
 124  *     August 1963.  (Also published in Collected Algorithms from CACM,
 125  *     algorithm number 199).
 126  *
 127  **************************************************************************/
 128 
 129 #include "sdncal.h"
 130 #include <limits.h>
 131 
 132 #define GREGOR_SDN_OFFSET         32045
 133 #define DAYS_PER_5_MONTHS  153
 134 #define DAYS_PER_4_YEARS   1461
 135 #define DAYS_PER_400_YEARS 146097
 136 
 137 void SdnToGregorian(
 138                                            zend_long sdn,
 139                                            int *pYear,
 140                                            int *pMonth,
 141                                            int *pDay)
 142 {
 143         int century;
 144         int year;
 145         int month;
 146         int day;
 147         zend_long temp;
 148         int dayOfYear;
 149 
 150         if (sdn <= 0 ||
 151                         sdn > (LONG_MAX - 4 * GREGOR_SDN_OFFSET) / 4) {
 152                 goto fail;
 153         }
 154         temp = (sdn + GREGOR_SDN_OFFSET) * 4 - 1;
 155 
 156         /* Calculate the century (year/100). */
 157         century = temp / DAYS_PER_400_YEARS;
 158 
 159         /* Calculate the year and day of year (1 <= dayOfYear <= 366). */
 160         temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
 161         year = (century * 100) + (temp / DAYS_PER_4_YEARS);
 162         dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
 163 
 164         /* Calculate the month and day of month. */
 165         temp = dayOfYear * 5 - 3;
 166         month = temp / DAYS_PER_5_MONTHS;
 167         day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
 168 
 169         /* Convert to the normal beginning of the year. */
 170         if (month < 10) {
 171                 month += 3;
 172         } else {
 173                 year += 1;
 174                 month -= 9;
 175         }
 176 
 177         /* Adjust to the B.C./A.D. type numbering. */
 178         year -= 4800;
 179         if (year <= 0)
 180                 year--;
 181 
 182         *pYear = year;
 183         *pMonth = month;
 184         *pDay = day;
 185         return;
 186 
 187 fail:
 188         *pYear = 0;
 189         *pMonth = 0;
 190         *pDay = 0;
 191 }
 192 
 193 zend_long GregorianToSdn(
 194                                                    int inputYear,
 195                                                    int inputMonth,
 196                                                    int inputDay)
 197 {
 198         int year;
 199         int month;
 200 
 201         /* check for invalid dates */
 202         if (inputYear == 0 || inputYear < -4714 ||
 203                 inputMonth <= 0 || inputMonth > 12 ||
 204                 inputDay <= 0 || inputDay > 31) {
 205                 return (0);
 206         }
 207         /* check for dates before SDN 1 (Nov 25, 4714 B.C.) */
 208         if (inputYear == -4714) {
 209                 if (inputMonth < 11) {
 210                         return (0);
 211                 }
 212                 if (inputMonth == 11 && inputDay < 25) {
 213                         return (0);
 214                 }
 215         }
 216         /* Make year always a positive number. */
 217         if (inputYear < 0) {
 218                 year = inputYear + 4801;
 219         } else {
 220                 year = inputYear + 4800;
 221         }
 222 
 223         /* Adjust the start of the year. */
 224         if (inputMonth > 2) {
 225                 month = inputMonth - 3;
 226         } else {
 227                 month = inputMonth + 9;
 228                 year--;
 229         }
 230 
 231         return (((year / 100) * DAYS_PER_400_YEARS) / 4
 232                         + ((year % 100) * DAYS_PER_4_YEARS) / 4
 233                         + (month * DAYS_PER_5_MONTHS + 2) / 5
 234                         + inputDay
 235                         - GREGOR_SDN_OFFSET);
 236 }
 237 
 238 char *MonthNameShort[13] =
 239 {
 240         "",
 241         "Jan",
 242         "Feb",
 243         "Mar",
 244         "Apr",
 245         "May",
 246         "Jun",
 247         "Jul",
 248         "Aug",
 249         "Sep",
 250         "Oct",
 251         "Nov",
 252         "Dec"
 253 };
 254 
 255 char *MonthNameLong[13] =
 256 {
 257         "",
 258         "January",
 259         "February",
 260         "March",
 261         "April",
 262         "May",
 263         "June",
 264         "July",
 265         "August",
 266         "September",
 267         "October",
 268         "November",
 269         "December"
 270 };

/* [<][>][^][v][top][bottom][index][help] */