/* MRDates - AmigaDOS date support routines. 07/03/88 This package is a hybrid of code from Thad Floryan, Doug Merrit and myself. I wanted a reliable set of AmigaDOS date conversion routines and this combination seems to work pretty well. The star of the show here is Thad's algorithm for converting the "days elapsed" field of an AmigaDOS DateStamp, using an intermediate Julian date format. I lifted/embellished some of the data structures from Doug's ShowDate package and wrote the DateToDS function. History: (most recent change first) 12/31/88 -MRR- StrToDS was not handling the null string properly. 11/01/88 -MRR- Added Unix-style documentation. 07/03/88 - Changed some names: Str2DS => StrToDS DS2Str => DSToStr */ #define MRDATES #include "MRDates.h" #include #include char *index(); #define DATE_SEPARATORS "/:-." #define MINS_PER_HOUR 60 #define SECS_PER_MIN 60 #define SECS_PER_HOUR (SECS_PER_MIN * MINS_PER_HOUR) #define TICS_PER_SEC 50 #define YEARS_PER_CENTURY 100 /* definitions to calculate current date */ #define FEB 1 /* index of feb. in table (for leap years) */ #define DAYS_PER_WEEK 7 #define DAYS_PER_YEAR 365 #define YEARS_PER_LEAP 4 #define START_YEAR 1978 #define FIRST_LEAP_YEAR 1980 #define LEAP_ADJUST (FIRST_LEAP_YEAR - START_YEAR) #define LEAP_FEB_DAYS 29 #define NORM_FEB_DAYS 28 #define IsLeap(N) (((N) % YEARS_PER_LEAP) ? 0 : 1) /* FUNCTION DSToDate - convert a DateStamp to a DATE. SYNOPSIS int DSToDate(dateStamp, date) struct DateStamp *dateStamp; DATE *date; DESCRIPTION Extracts the date components from an AmigaDOS datestamp. The calculations herein use the following assertions: 146097 = number of days in 400 years per 400 * 365.2425 = 146097.00 36524 = number of days in 100 years per 100 * 365.2425 = 36524.25 1461 = number of days in 4 years per 4 * 365.2425 = 1460.97 AUTHOR Thad Floryan, 12-NOV-85 Mods by Mark Rinfret, 04-JUL-88 SEE ALSO Include file MRDates.h. */ #define DDELTA 722449 /* days from Jan.1,0000 to Jan.1,1978 */ static int mthvec[] = {-1, -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364}; int DSToDate(ds, date) long *ds; DATE *date; { long jdate, day0, day1, day2, day3; long year, month, day, temp; jdate = ds[0] + DDELTA; /* adjust internal date to Julian */ year = (jdate / 146097) * 400; day0 = day1 = jdate %= 146097; year += (jdate / 36524) * 100; day2 = day1 %= 36524; year += (day2 / 1461) * 4; day3 = day1 %= 1461; year += day3 / 365; month = 1 + (day1 %= 365); day = month % 30; month /= 30; if ( ( day3 >= 59 && day0 < 59 ) || ( day3 < 59 && (day2 >= 59 || day0 < 59) ) ) ++day1; if (day1 > mthvec[1 + month]) ++month; day = day1 - mthvec[month]; date->Dyear = year; date->Dmonth = month; date->Dday = day; date->Dweekday = ds[0] % DAYS_PER_WEEK; temp = ds[1]; /* get ds_Minute value */ date->Dhour = temp / MINS_PER_HOUR; date->Dminute = temp % MINS_PER_HOUR; date->Dsecond = ds[2] / TICS_PER_SEC; return 0; } /* FUNCTION DateToDS(date, dateStamp) SYNOPSIS void DateToDS(date, dateStamp) DATE *date; struct DateStamp *dateStamp; DESCRIPTION DateToDS converts the special DATE format to a DateStamp. */ DateToDS(date, ds) DATE *date; long *ds; { long daysElapsed, yearsElapsed, leapYears, thisMonth, thisDay, thisYear; int month; /* Note the special handling for year < START_YEAR. In this case, * the other fields are not even checked - the user just gets the * "start of time". */ if ((thisYear = date->Dyear) < START_YEAR) { ds[0] = ds[1] = ds[2] = 0; return; } if (IsLeap(thisYear)) calendar[FEB].Mdays = LEAP_FEB_DAYS; thisDay = date->Dday - 1; thisMonth = date->Dmonth -1; yearsElapsed = thisYear - START_YEAR; leapYears = (yearsElapsed + LEAP_ADJUST -1) / YEARS_PER_LEAP; daysElapsed = (yearsElapsed * DAYS_PER_YEAR) + leapYears; for (month = 0; month < thisMonth; ++month) daysElapsed += calendar[month].Mdays; daysElapsed += thisDay; calendar[FEB].Mdays = NORM_FEB_DAYS; ds[0] = daysElapsed; ds[1] = date->Dhour * MINS_PER_HOUR + date->Dminute; ds[2] = date->Dsecond * TICS_PER_SEC; } /* FUNCTION CompareDS - compare two DateStamp values. SYNOPSIS int CompareDS(date1, date2) struct DateStamp *date1, *date2; DESCRIPTION CompareDS performs an ordered comparison between two DateStamp values, returning the following result codes: -1 => date1 < date2 0 => date1 == date2 1 => date1 > date2 NOTE: This routine makes an assumption about the DateStamp structure, specifically that it can be viewed as an array of 3 long integers in days, minutes and ticks order. */ int CompareDS(d1, d2) long *d1, *d2; { USHORT i; long compare; for (i = 0; i < 3; ++i) { if (compare = (d1[i] - d2[i])) { if (compare < 0) return -1; return 1; } } return 0; /* dates match */ } /* FUNCTION DSToStr - convert a DateStamp to a formatted string. SYNOPSIS void DSToStr(str,fmt,d) char *str, *fmt; struct DateStamp *d; DESCRIPTION DSToStr works a little like sprintf. It converts a DateStamp to an ascii formatted string. The formatting style is dependent upon the contents of the format string, fmt, passed to this function. The content of the format string is very similar to that for printf, with the exception that the following letters have special significance: y => year minus 1900 Y => full year value m => month value as integer M => month name d => day of month (1..31) D => day name ("Monday".."Sunday") h => hour in twenty-four hour notation H => hour in twelve hour notation i => 12 hour indicator for H notation (AM or PM) I => same as i n => minutes (sorry...conflict with m = months) N => same as n s => seconds S => same as s All other characters are passed through as part of the normal formatting process. The following are some examples with Saturday, July 18, 1987, 13:53 as an input date: "%y/%m/%d" => 87/7/18 "%02m/%02d/%2y" => 07/18/87 "%D, %M %d, %Y" => Saturday, July 18, 1987 "%02H:%02m i" => 01:53 PM "Time now: %h%m" => Time now: 13:53 */ void DSToStr(str,fmt,d) char *str, *fmt; long *d; { DATE date; char fc,*fs,*out; USHORT ivalue; char new_fmt[256]; /* make it big to be "safe" */ USHORT new_fmt_lng; char *svalue; DSToDate(d, &date); /* convert DateStamp to DATE format */ *str = '\0'; /* insure output is empty */ out = str; fs = fmt; /* make copy of format string pointer */ while (fc = *fs++) { /* get format characters */ if (fc == '%') { /* formatting meta-character? */ new_fmt_lng = 0; new_fmt[new_fmt_lng++] = fc; /* copy width information */ while (isdigit(fc = *fs++) || fc == '-') new_fmt[new_fmt_lng++] = fc; switch (fc) { /* what are we trying to do? */ case 'y': /* year - 1980 */ ivalue = date.Dyear % 100; write_int: new_fmt[new_fmt_lng++] = 'd'; new_fmt[new_fmt_lng] = '\0'; sprintf(out,new_fmt,ivalue); out = str + strlen(str); break; case 'Y': /* full year value */ ivalue = date.Dyear; goto write_int; case 'm': /* month */ ivalue = date.Dmonth; goto write_int; case 'M': /* month name */ svalue = calendar[date.Dmonth - 1].Mname; write_str: new_fmt[new_fmt_lng++] = 's'; new_fmt[new_fmt_lng] = '\0'; sprintf(out,new_fmt,svalue); out = str + strlen(str); break; case 'd': /* day */ ivalue = date.Dday; goto write_int; case 'D': /* day name */ svalue = dayNames[d[0] % DAYS_PER_WEEK]; goto write_str; case 'h': /* hour */ ivalue = date.Dhour; goto write_int; case 'H': /* hour in 12 hour notation */ ivalue = date.Dhour; if (ivalue >= 12) ivalue -= 12; goto write_int; case 'i': /* AM/PM indicator */ case 'I': if (date.Dhour >= 12) svalue = "PM"; else svalue = "AM"; goto write_str; case 'n': /* minutes */ case 'N': ivalue = date.Dminute; goto write_int; case 's': /* seconds */ case 'S': ivalue = date.Dsecond; goto write_int; default: /* We are in deep caca - don't know what to do with this * format character. Copy the raw format string to the * output as debugging information. */ new_fmt[new_fmt_lng++] = fc; new_fmt[new_fmt_lng] = '\0'; strcat(out, new_fmt); out = out + strlen(out); /* advance string pointer */ break; } } else *out++ = fc; /* copy literal character */ } *out = '\0'; /* terminating null */ } /* FUNCTION StrToDS - convert a string to a DateStamp. SYNOPSIS int StrToDS(string, date) char *string; struct DateStamp *date; DESCRIPTION StrToDS expects its string argument to contain a date in MM/DD/YY HH:MM:SS format. The time portion is optional. StrToDS will attempt to convert the string to a DateStamp representation. If successful, it will return 0. On failure, a 1 is returned. */ int StrToDS(str, d) char *str; long *d; { register char c; int count; int i, item; DATE date; /* unpacked DateStamp */ char *s; int values[3]; int value; s = str; for (item = 0; item < 2; ++item) { /* item = date, then time */ for (i = 0; i < 3; ++i) values[i] = 0; count = 0; while (c = *s++) { /* get date value */ if (c <= ' ') break; if (isdigit(c)) { value = 0; do { value = value*10 + c - '0'; c = *s++; } while (isdigit(c)); if (count == 3) { bad_value: #ifdef DEBUG puts("Error in date-time format.\n"); printf("at %s: values(%d) = %d, %d, %d\n", s, count, values[0], values[1], values[2]); #endif return 1; } values[count++] = value; if (c <= ' ') break; } else if (! index(DATE_SEPARATORS, c) ) goto bad_value; /* Illegal character - quit. */ } /* end while */ if (item) { /* Getting time? */ date.Dhour = values[0]; date.Dminute = values[1]; date.Dsecond = values[2]; } else { /* Getting date? */ /* It's OK to have a null date string, but it's not OK to specify only 1 or 2 of the date components. */ if (count && count != 3) goto bad_value; date.Dmonth = values[0]; date.Dday = values[1]; date.Dyear = values[2]; if (date.Dyear == 0) { date.Dyear = START_YEAR; date.Dday = 1; } else { if (date.Dyear < (START_YEAR - 1900) ) date.Dyear += 100; date.Dyear += 1900; } } } /* end for */ DateToDS(&date, d); return 0; } /* StrToDS */ #ifdef DEBUG #include "stdio.h" main(ac, av) int ac; char **av; { long datestamp[3]; /* A little dangerous with Aztec */ long datestamp2[3]; DATE date, oldDate; long day, lastDay; int errors = 0; /* * display results from DateStamp() (hours:minutes:seconds) */ DateStamp(datestamp); /* * display results from DSToDate() (e.g. "03-May-88") */ DSToDate(datestamp, &date); printf("Current date: %02d-%s-%02d\n", date.Dday, calendar[ date.Dmonth - 1].Mname, (date.Dyear % YEARS_PER_CENTURY)); printf("Current time: %02d:%02d:%02d\n", date.Dhour, date.Dminute, date.Dsecond); printf("\nDoing sanity check through year 2000...\n\t"); lastDay = (2000L - START_YEAR) * 365L; lastDay += (2000L - START_YEAR) / YEARS_PER_LEAP; for (day = 0; day <= lastDay; ++day) { if (day % 1000 == 0) { printf(" %ld", day); fflush(stdout); } datestamp[0] = day; datestamp[1] = MINS_PER_HOUR - 1; datestamp[2] = TICS_PER_SEC * (SECS_PER_MIN - 1); DSToDate(datestamp, &date); if (day && date == oldDate) { printf("Got same date for days %d, %d: %02d-%s-%02d\n", day - 1, day, date.Dday, calendar[ date.Dmonth - 1 ].Mname, (date.Dyear % YEARS_PER_CENTURY)); if (++errors == 10) exit(1); } DateToDS(&date, datestamp2); if (day != datestamp2[0]) { printf("\nConversion mismatch at day %ld!\n", day); printf("\tBad value = %ld", datestamp2[0]); printf("\tDate: %02d-%s-%02d\n", date.Dday, calendar[ date.Dmonth -1 ].Mname, (date.Dyear % YEARS_PER_CENTURY)); if (++errors == 10) exit(1); } oldDate = date; } printf("\nSanity check passed.\n"); } /* main() */ #endif