📄 jdate.c
字号:
/* * $Id: //pentools/main/datemath/jdate.c#2 $ * * written by: Stephen J. Friedl * Software Consultant * Tustin, California USA * steve@unixwiz.net / www.unixwiz.net * * This module is responsible for calculating "jdates" (Julian * Dates), and the algorithm mirrors that used by the Informix * 3.3 product (predating Inofrmix SQL). */#include <stdio.h>#include <string.h>#include <memory.h>#include <assert.h>#include <ctype.h>#include <time.h>#include "defs.h"/*------------------------------------------------------------------------ * This macro represents the "null" date and is treated specially * throughout the code. This corresponds to the most negative long * int and would be outside the range of these date routines anyway. */#define DATENULL 0x80000000LU/*------------------------------------------------------------------------ * these are indexes into the short mdy[3] array for each of * the month/day/year components. */#define MM 0#define DD 1#define YY 2/*------------------------------------------------------------------------ * These macros help out with leap year calculations. A leap year happens * every four years, except century years, but including years evenly * divisible by 400. So, 1900 was not a leap year but 2000 will be. */#define MOD(a,b) (((a)%(b))==0)#define is_leap(yy) (MOD((yy),4) && (!MOD((yy),100)||MOD((yy),400)))#define daysinyear(yy) (is_leap(yy) ? 366 : 365)/*------------------------------------------------------------------------ * This macro can be added to a Julian date go shift it from a 12/31/1899- * based date to one starting at 01/01/0001. This makes the calculations * a *lot* easier throughout. */#define DATE_OFFSET ( \ (1899 * 365) + /* # of full years */\ (1899 / 4) - /* add in leap years */\ (1899 / 100) + /* take out div-by-100 leap yrs */\ (1899 / 400) ) /* add back div-by-100 leap yrs *//* * rdayofweek() * * Given a Julian date, return its day of week: * * 0 Sunday * 1 Monday * 2 Tuesday * 3 Wednesday * 4 Thursday * 5 Friday * 6 Saturday * * A date of DATENULL is considered to be Sunday, and there * are no other error routines. */intrdayofweek(jdate_t jdate){ if (jdate == DATENULL) /* a null date? */ return(0); /* .. yup, it's a Sunday */ jdate += DATE_OFFSET; /* convert to 1/1/1 dates */ return(jdate % 7); /* 1/1/1 was a Sunday too */}/* * jdaysinyymm() * * Given a full year and a month, return the number of days * in the month. This routine assumes that the input values * are valid and does not do any checking on them. Sorry. */static int jdaysinyymm(int yy, int mm){static const short days[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };int dd; dd = days[mm]; if (mm == 2 && is_leap(yy)) /* leap year? */ dd++; return(dd);}/* * rtoday() * * Get today's date, convert it to Julian format, and stuff it * in the long integer pointed to by pjdate; */int rtoday(jdate_t *pjdate){time_t now;struct tm *tm, *localtime();short mdy[3]; assert(pjdate != NULL); /*---------------------------------------------------------------- * get the current UNIX time and convert it to the time struct. */ (void)time(&now); tm = localtime(&now); /*---------------------------------------------------------------- * build the mm/dd/yy array */ mdy[MM] = tm->tm_mon + 1; mdy[DD] = tm->tm_mday; mdy[YY] = 1900 + tm->tm_year; /*---------------------------------------------------------------- * finally, convert to Julian */ return(rmdyjul(mdy, pjdate));}/* * rjulmdy() * * This function takes a Julian date and converts it into a * month/day/year array. The date is the number of days since * 12/31/1899, and any year from 1 to 9999 is allowed. Return * values are: * * 0 all is OK * -1210 error in date: year>9999 or date is null * * In event of error, the mdy[] array is zeroed out. A date * of null (DATENULL) zeroes out the array as well. */int rjulmdy(jdate_t jdate, short *mdy){short mm = 1, dd = 1, yy = 1, ndays; assert(mdy != NULL); /*---------------------------------------------------------------- * first se if we have been passed a NULL date. If so, set all the * mm/dd/yy values to indicate this and return the appropriate * error code for this. */ if (jdate == DATENULL) { mdy[MM] = mdy[DD] = mdy[YY] = 0; return(-1210); } /*---------------------------------------------------------------- * The Julian dates are based at 12/31/1899 but we can do our math * a lot easier if we start them at 1/1/1 -- that's why we add the * offset and calculate from there. */ jdate += DATE_OFFSET; /*---------------------------------------------------------------- * we have to first figure out the proper year. We start with * year one and loop until the number of remaining days is less * than one year. */ while ((yy < 9999) && (int)jdate > (ndays = daysinyear(yy))) { jdate -= ndays; yy++; } /*---------------------------------------------------------------- * If we have too many years, bomb out with an error. This code * only works up to 9999 (oh well). */ if (yy > 9999) { mdy[MM] = mdy[DD] = mdy[YY] = 0; return(-1210); } /*---------------------------------------------------------------- * Now we have the proper year, so count down by months. */ while ( (int)jdate > (ndays = jdaysinyymm(yy, mm))) { jdate -= ndays; mm++; } /*---------------------------------------------------------------- * the only thing left is the number of days in the current month. */ dd = jdate; /*---------------------------------------------------------------- * all is OK, so assign the proper mm/dd/yy values and return OK. */ mdy[MM] = mm; mdy[DD] = dd; mdy[YY] = yy; return(0);}/* * rmdyjul() * * Given an array containing month/day/year values and a pointer * to a long, convert the mdy[] date to the number of days since * 12/31/1899. The year must be in full format (i.e. 19YY), and * negative jdate values mean days before 12/31/1899. * * Return values are: * * 0 all is OK * -1204 invalid year component (must be 1-9999) * -1205 invalid month (must be 1-12) * -1206 invalid day of month * * The long date value is not touched if the input date is not * valid, and in no case are the mdy[] elements touched. If the * mm/dd/yy are all zero, a null date is created. * * Rather than have different algorithms before/after 12/31/1899, * we calculate the number of days since 1/1/1 and then add in the * "offset" to give it a 12/31/1899-based value. */int rmdyjul(const short *mdy, jdate_t *pjdate){static const short dy[2][13] = { { 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, { 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }};long jdate = 0; /* date being constructed */short mm = mdy[MM], /* current month 1-12 */ dd = mdy[DD], /* current day 1-31 (or so) */ yy = mdy[YY]; /* current year 1-9999 */ assert(mdy != NULL); assert(pjdate != NULL); /*---------------------------------------------------------------- * see if the user has requested a NULL date. */ if (mm == 0 && dd == 0 && yy == 0) { *pjdate = DATENULL; return(0); } /*---------------------------------------------------------------- * first make sure that the input parameters are not out of bounds. * If so, return the proper error codes for them (all negative). */ if (yy < 1 || yy > 9999) /* valid year? */ return(-1204); if (mm < 1 || mm > 12) /* valid month? */ return(-1205); if (dd < 1 || dd > jdaysinyymm(yy, mm)) /* valid day? */ return(-1206); /*---------------------------------------------------------------- * calculate the number of days since 1/1/1 (the first day of * the A.D.). */ jdate += (yy-1) * 365; /* add in full years */ jdate += (yy-1) / 4; /* add in leap year days */ jdate -= (yy-1) / 100; /* sub out div-by-100 */ jdate += (yy-1) / 400; /* add back div-by-400 */ jdate += dy[is_leap(yy)][mm]; /* add in the months */ jdate += dd; /* add in the days */ /*---------------------------------------------------------------- * store the date after subtracting the offset. This calculation * is simply the number of days before 1/1/1900. */ *pjdate = jdate - DATE_OFFSET; return(0);}/* * rdatestr() * * Given a Julian date, convert it to a string in MM/DD/YYYY * format. The output buffer must be at least 11 bytes long. * A date of DATENULL is converted to a buffer of all spaces, * and in all cases the output buffer is NUL terminated. * * This routine does not use sprintf(). */int rdatestr(jdate_t jdate, char *str){int rv;short mdy[3]; assert(str != NULL); /*---------------------------------------------------------------- * if the date is NULL, convert everything to spaces and return * success. */ if (jdate == DATENULL) { memset(str, ' ', 10); str[10] = '\0'; return(0); } /*---------------------------------------------------------------- * first convert from the Julian to the individual pieces. If * there is an error, return that error code. */ if ( (rv = rjulmdy(jdate, mdy)) < 0) return(rv); /*---------------------------------------------------------------- * now go from the mdy[] array and build the string. We avoid * sprintf() because it is so big and this is probably lots * faster anyway. */ *str++ = (mdy[MM] / 10) + '0'; *str++ = (mdy[MM] % 10) + '0'; *str++ = '/'; *str++ = (mdy[DD] / 10) + '0'; *str++ = (mdy[DD] % 10) + '0'; *str++ = '/'; *str++ = ((mdy[YY] / 1000) % 10) + '0'; *str++ = ((mdy[YY] / 100) % 10) + '0'; *str++ = ((mdy[YY] / 10) % 10) + '0'; *str++ = ((mdy[YY] ) % 10) + '0'; *str = '\0'; return(0);}/* * rfmtdate() * * This function takes a Julian date, a format string, and * and output buffer and formats the date into the buffer. * The format string can contain: * * mm month number (01-12) * mmm month abbrev (Jan-Dec) * dd day number (01-31) * ddd day of week abbrev (Sun-Sat) * yy last two digits of year * yyyy full year notation * * Any other characters appear literally in the output string. * * For instance, * * rfmtdate(jdate, "dd mmm, yyyy", outbuf); * * might yield * * "07-Aug, 1989" * * Return is zero if all is well and a negative value on error. * It seems that the only cause of an error is an invalid jdate, * and there is nothing else short of a NULL format string that * does this. * * ===NOTE=== This is not defined by the interface, but it appears * that if longer sequences of m/d/y are used, the entire sequence * has the replacment text shoved into the rightmost portion. For * instance, a format of * * "mmmmm ddddd yyyyy" * yields "mmDec ddTue y1989" * * This suggests the algorithm used internally and we use it too. */int rfmtdate(jdate_t jdate, const char *format, char *outbuf){static const char daynames[] = "SunMonTueWedThuFriSat";static const char monthnames[] = "___JanFebMarAprMayJunJulAugSepOctNovDec";short mdy[3];char *p;int rv; if ( (rv = rjulmdy(jdate, mdy)) < 0) return(rv); for (p = strcpy(outbuf, format); *p; p++) { char const *constp; char *q; int len, c; if (strchr("MmDdYy", *p) == NULL) continue; /*-------------------------------------------------------- * We found a special sequence, so run to the end of it. */ q = p; while (*++q == *p) ; /*-------------------------------------------------------- * At this point, `q' is pointing one character beyond * the end of the sequence of m or d or y. If the length * is just one, then this is an ordinary character and * not part of any special sequence so skip processing. * If the length if greater than four, we treat it as * just four during this function. Note that we only * use the length as an indicator and not as an index * into the string so this is OK. */ if (len = q - p, len <= 1) /* too short? */ continue; else if (len > 4) len = 4; /*-------------------------------------------------------- * We multiply the character by the length to give us a * "key" into the switch. Note that we use upper case * for all the indicators. */ c = isupper(*p) ? *p : toupper(*p); switch (c * len) { /*-------------------------------------------------------- * process a two-digit month number (01-12) */ case 'M'*2: /* 2-digit month */ *p++ = (mdy[MM]/10) + '0'; *p = (mdy[MM]%10) + '0'; break; /*-------------------------------------------------------- * generate the three-charaacter month abbreviation. */ case 'M'*3: /* 3-char month abbrev */ case 'M'*4: p = q-3; constp = &monthnames[mdy[MM]*3]; *p++ = *constp++; *p++ = *constp++; *p = *constp; break; /*-------------------------------------------------------- * process a two-digit day. */ case 'D'*2: /* 2-digit day */ *p++ = (mdy[DD]/10) + '0'; *p = (mdy[DD]%10) + '0'; break; /*-------------------------------------------------------- * take care of the three-charaacter day-of-week abbrev. */ case 'D'*3: case 'D'*4: p = q-3; constp = &daynames[rdayofweek(jdate)*3]; *p++ = *constp++; *p++ = *constp++; *p = *constp; break; /*-------------------------------------------------------- * The year is a bit different. In all cases we have to * process the final two digits, and only in the case of * a length of four do we have to do the first two. */ case 'Y'*4: /* four-digit year */ p = q-4; *p++ = (mdy[YY]/1000) + '0'; /* thou */ *p++ = (mdy[YY]/ 100)%10 + '0'; /* hund */ /*FALLTHROUGH*/ case 'Y'*2: /* two-digit year */ case 'Y'*3: p = q-2; *p++ = (mdy[YY]/10)%10 + '0'; /* tens */ *p = mdy[YY] %10 + '0'; /* ones */ break; } } return(0);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -