/*============================================================================= D A T E ------- John Hastwell-Batten Jonathon Richard Saxton January 1987 A program which (approximately) emulates DATE.COM as distributed with CP/M 3 by Digital Research. Command formats: --------------- date displays current date and time. date set prompts for date and time and adjusts the system clock accordingly. date cont loops, displaying date and time, until a key is pressed. date read reads hardware clock and sets system date and time. date dd/mm/yy hh:mm:ss -or- date mm/dd/yy hh:mm:ss sets the system clock to the specified value. Only the first letter of the argument is significant so "date s" is equivalent to "date set". The format in which this program will accept dates and times is much less strict than that allowed by DRI's DATE program:-- 1. DRI's DATE wants date input in the format MM/DD/YY and no other. 05/07/86 is acceptable, 5/7/86 is not. This program will accept either of the above and also 5-7-86, 5 7 86 or just about anything else. It does not care what you use as a separator between the fields and does not insist that you pad the numbers with leading zeros. It will also accept 1986 as a valid year. 2. Depending on a compile-time option, this program will accept numeric dates in the month/day/year format used in the USA or in the more usual day/month/year format used throughout the rest of the world. It will also accept dates in universal alphanumeric format - e.g. 7 dec 86 (again, the program is not fussy about what delimiters are used). 3. DRI's DATE wants time input in strict HH:MM:SS format. This program removes the unnecessary stringencies for time input as for date input. =============================================================================== Modification history: ?? Jan 87 Jon Saxton Original release. 02 Mar 87 Jon Saxton Bug fix - A spurious line of code in setClock() was causing a one-day error when year mod 4 was 0 or 3. =============================================================================*/ #include #include #include /* ONE or NEITHER of the following should be defined */ #undef DS1216E #undef DS1216 #ifdef DS1216E #define SWaddr 0x6100 /* I had one of each installed while writing */ #endif /* this program and they were in different */ #ifdef DS1216 /* sockets. */ #define SWaddr 0x2100 #endif #define TPAbank 1 #define SWbank -4 #define A2 4 unsigned char buf[8]; unsigned char key[8] = {0xC5,0x3A,0xA3,0x5C,0xC5,0x3A,0xA3,0x5C}; unsigned char b64[64]; unsigned char b1[1]; /* BDOS functions */ #define GET_VSN 12 #define GET_TIME 105 #define SET_SCB 49 #define DATE 0x58 /* Offset of date/time within SCB */ #define WORD 0xFE /* Signal to set a word value in SCB */ #define BYTE 0xFF /* Signal to set a byte value in SCB */ /* BIOS functions */ #define TIME 26 struct { unsigned short int days; unsigned char hour, minute, second; } time_pb; struct { unsigned char offset; unsigned char worb; unsigned short int value; } scb_pb; struct { unsigned char year, month, day, hour, minute, second; } time; #ifndef SWaddr struct { unsigned char func; unsigned char aVal; unsigned short int bcVal, deVal, hlVal; } bios_pb; #endif unsigned char sentinel[] = {'U','S','A','-','>'}; unsigned char usa = 0; /* Use wierd MM/DD/YY format if non-0 */ extern unsigned int bdos(); unsigned char monthName[] = {"Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec"}; unsigned char dayName[] = {"Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat"}; unsigned short int atou(p) unsigned char *p; { unsigned int g = 0; while (isspace(*p)) ++p; while (isdigit(*p)) g = g*10 + *p++ - '0'; return g; } /*----------------------------------------------------------------------------- g e t D a t e and g e t T i m e ============= ============= Prompt for date or time and get input from keyboard. -----------------------------------------------------------------------------*/ void getDate() { printf("Enter today's date (%s/YY): ", (usa ? "MM/DD" : "DD/MM")); gets(b64); } void getTime() { printf("Enter the time (HH:MM:SS): "); gets(b64); } /*----------------------------------------------------------------------------- u s a g e ========= This routine is entered if we couldn't make sense of the command- line. Displays a very terse instruction on usage. ------------------------------------------------------------------------------*/ void usage() { #ifdef SWaddr printf("date [read | set | continuous | %s/yy hh:mm:ss]", #else printf("date [set | continuous | %s/yy hh:mm:ss]", #endif (usa ? "mm/dd" : "dd/mm")); } /*----------------------------------------------------------------------------- a M o n t h =========== Checks a string to see if it is a month name. Insensitive to upper/lower case. Only looks at first three characters. Returns month number (1-12) or 0. -----------------------------------------------------------------------------*/ unsigned short int aMonth(c) unsigned char *c; { unsigned short int m; unsigned char *n, *s, looking = 1; for (m = 0; looking && m < 12; ++m) { n = &monthName[m*4]; s = c; if (isalpha(*s) && (*s++ & 0x5F) == *n++ && isalpha(*s) && (*s++ | 0x20) == *n++ && isalpha(*s) && (*s | 0x20) == *n) looking = 0; } return looking ? 0 : m; } /*----------------------------------------------------------------------------- l e a p ======= Returns 1 if the year is a leap year, 0 otherwise. Crude and takes advantage of the fact that 2000 will be a leap year by ignoring the checks for non-leap end-of-century years. -----------------------------------------------------------------------------*/ unsigned short int leap(year) unsigned short int year; { return (year & 3) ? 0 : 1; } /*----------------------------------------------------------------------------- y e a r L e n g t h =================== Determines the number of days in a year. Crude, and takes full advantage of the fact that 2000 will be a leap year. -----------------------------------------------------------------------------*/ unsigned int yearLength(year) unsigned short int year; { return 365 + leap(year); } /*----------------------------------------------------------------------------- d a y s I n =========== Returns the number of days in a month (taking leap years into account). -----------------------------------------------------------------------------*/ unsigned char daysIn(month,year) unsigned short int month, year; { static unsigned char monthLength[] = {31,28,31,30,31,30,31,31,30,31,30,31}; return monthLength[month] + ((month == 1) ? leap(year) : 0); } /*----------------------------------------------------------------------------- e x p a n d and c o n t r a c t =========== =============== expand() takes an array of 64 bits and blows it out to an array of 64 bytes with the low-order bit of each byte set according to the corresponding bit in the bit array. contract() does the reverse and builds a bit array from the low- order bits of a byte array. -----------------------------------------------------------------------------*/ void expand(narrow,wide) unsigned char narrow[], *wide; { int byte, bit; for (byte=0; byte<8; ++byte) for (bit=0; bit<8; ++bit) *wide++ = (narrow[byte] >> bit) & 1; } void contract(wide,narrow) unsigned char *wide, *narrow; { int byte, bit; unsigned char val; for (byte=0; byte<8; ++byte) { val = 0; for(bit=0; bit<8; ++bit) val |= (*wide++ & 1) << bit; narrow[byte] = val; } } /*----------------------------------------------------------------------------- u n b c d ========= Converts a BCD byte to a binary number. -----------------------------------------------------------------------------*/ unsigned short int unbcd(v) unsigned char v; { return (v >> 4) * 10 + (v & 15); } /*----------------------------------------------------------------------------- b c d ===== Converts a binary number to a BCD byte. -----------------------------------------------------------------------------*/ unsigned char bcd(v) unsigned short int v; { return ((v / 10) << 4) | (v % 10); } /*----------------------------------------------------------------------------- r e a d C l o c k ================= Reads the hardware clock, converts the time to DRI format and sets the date and time fields in the SCB. Thereafter, the CIO interrupts keep the system clock ticking. -----------------------------------------------------------------------------*/ void readClock() { #ifdef SWaddr extern void ibmm(); /* Inter-bank memory move - parameters are:- Source address, Source bank number, Destination address, Destiation bank number, Number of bytes to transfer. */ #endif extern void ei(), di(); unsigned int y, m; #ifdef DS1216 ibmm(SWaddr,SWbank,b64,TPAbank,64); expand(key,b64); /* Expand key to 64 bytes */ ibmm(b64,TPAbank,SWaddr,SWbank,64); /* Write key to unlock clock */ ibmm(SWaddr,SWbank,b64,TPAbank,64); /* Read clock */ contract(b64,buf); /* Contract clock readout */ #endif #ifdef DS1216E for (y=64; y--;) ibmm(SWaddr+A2,SWbank,b64,TPAbank,1); expand(key,b64); /* Expand key to 64 bytes */ for (y=0; y<64; ++y) ibmm(SWaddr+b64[y],SWbank,b1,TPAbank,1); for (y=0; y<64; ++y) ibmm(SWaddr+A2,SWbank,&b64[y],TPAbank,1); /* Read clock */ contract(b64,buf); /* Contract clock readout */ #endif /* buf[] now contains the clock readout in BCD as follows .... buf[0] 1/100 second buf[1] second buf[2] minute buf[3] hour buf[4] 12/24-hr flag and day of week buf[5] day buf[6] month buf[7] year We convert it to DRI format .... */ time_pb.days = 0; for (y=78; y < unbcd(buf[7]); ++y) time_pb.days += yearLength(y); for (m=0; m < unbcd(buf[6])-1; ++m) time_pb.days += daysIn(m,y); time_pb.days += unbcd(buf[5]); time_pb.hour = buf[3]; time_pb.minute = buf[2]; time_pb.second = buf[1]; /* Now we update the SCB fields. Note that we must do this with interrupts disabled so we don't get a clock rollover. Also, we must directly update the SCB fields because the standard BDOS function (104) for setting the clock clears the seconds field!!! */ scb_pb.offset = DATE; scb_pb.worb = WORD; scb_pb.value = time_pb.days; di(); bdos(SET_SCB,&scb_pb); scb_pb.offset += 2; scb_pb.worb = BYTE; scb_pb.value = time_pb.hour; bdos(SET_SCB,&scb_pb); scb_pb.offset++; scb_pb.value = time_pb.minute; bdos(SET_SCB,&scb_pb); scb_pb.offset++; scb_pb.value = time_pb.second; bdos(SET_SCB,&scb_pb); ei(); /* If neither SmartWatch socket is present then we want to tell the CP/M+ BIOS that it should update its clock. */ #ifndef SWaddr bios_pb.bcVal = 0xFF; /* Tell BIOS to set h/w clock */ bios_pb.func = TIME; bdos(50,&bios_pb); #endif } /*----------------------------------------------------------------------------- s h o w T i m e =============== Displays the current date and time. May loop pending a keypress. -----------------------------------------------------------------------------*/ void showTime(looping) unsigned char looping; { short int y, m, d, w; unsigned char ss = 0xFF; do { while ((time_pb.second = bdos(GET_TIME, &time_pb)) == ss) if (looping) if (kbhit()) { looping = 0; getch(); } ss = time_pb.second; w = ((d = time_pb.days) + 6) % 7; for (y = 78; d > yearLength(y); ++y) d -= yearLength(y); for (m = 0; d > daysIn(m,y); ++m) d -= daysIn(m,y); printf("\r%s %d %s %d; %02x:%02x:%02x ", &dayName[w*4], d, &monthName[m*4], y+1900, time_pb.hour, time_pb.minute, time_pb.second); } while (looping); } /*----------------------------------------------------------------------------- p a r s e D a t e ================= -----------------------------------------------------------------------------*/ unsigned char parseDate(p) unsigned char *p; { unsigned short int v; unsigned char a1, mf; mf = usa; a1 = 0; while (isspace(*p)) ++p; if (isalpha(*p)) /* then may be (should be) a month name */ { time.month = aMonth(p); a1 = 1; while (isalpha(*p)) ++p; } else if (isdigit(*p)) /* then should be a month or day number */ { time.month = atou(p); /* treat it as a month for now */ while (isdigit(*p)) ++p; } while (*p && !isalpha(*p) && !isdigit(*p)) ++p; if (isalpha(*p)) /* then should be a month name */ { v = aMonth(p); if (a1) /* then we've already seen a month name */ return 1; /* so this is wrong */ time.day = time.month; time.month = v; while (isalpha(*p)) ++p; } else if (isdigit(*p)) { v = atou(p); while (isdigit(*p)) ++p; if (a1) /* first thing was a month name */ time.day = v; /* this must be a day number */ else /* first thing was a number */ if (mf) /* if MM/DD/YY format */ time.day = v; /* this is a day */ else /* else first thing was a day */ { /* and this is a month */ time.day = time.month; time.month = v; } } while (*p && !isdigit(*p)) ++p; if (isdigit(*p)) { v = atou(p); if (v > 1900) v -= 1900; if (v < 78 || v > 114) v = 0; time.year = v; } if (time.year == 0 || time.day == 0 || time.month == 0) return 1; if (time.month > 12) return 1; if (time.day > daysIn(time.month-1,time.year)) return 1; return 0; } /*----------------------------------------------------------------------------- p a r s e T i m e ================= -----------------------------------------------------------------------------*/ unsigned char parseTime(p) unsigned char *p; { unsigned short int v; while (isspace(*p)) ++p; if (!isdigit(*p)) return 1; if ((v = atou(p)) > 23) return 1; time.hour = v; while (isdigit(*p)) ++p; while (*p && !isdigit(*p)) ++p; if ((v = atou(p)) > 59) return 1; time.minute = v; while (isdigit(*p)) ++p; while (*p && !isdigit(*p)) ++p; if ((v = atou(p)) > 59) return 1; time.second = v; return 0; } /*----------------------------------------------------------------------------- s e t C l o c k =============== Obtains date and time specifications and sets the system clock. -----------------------------------------------------------------------------*/ void setClock(dateStr, timeStr) unsigned char *dateStr, *timeStr; { unsigned short int y, m, days = 0; unsigned char prompt = 0; #ifdef SWaddr extern void ibmm(); /* Inter-bank memory move - parameters are:- Source address, Source bank number, Destination address, Destiation bank number, Number of bytes to transfer. */ #endif if (dateStr == NULL) { prompt = 1; getDate(); dateStr = b64; } if (parseDate(dateStr)) { puts("Badly-formed date"); exit(); } if (timeStr == NULL) { getTime(); timeStr = b64; } if (parseTime(timeStr)) { puts("Badly-formed time"); exit(); } /* If we get here then we have a valid date and time. Now program the clock */ buf[7] = bcd(time.year); buf[6] = bcd(time.month); buf[5] = bcd(time.day); buf[3] = bcd(time.hour); buf[2] = bcd(time.minute); buf[1] = bcd(time.second); buf[0] = 0; days = 0; for (y=78; y < time.year; ++y) days += yearLength(y); for (m=0; m < time.month-1; ++m) days += daysIn(m,time.year); days += time.day; buf[4] = (days+6) % 7 + 16; if (prompt) { printf("Press any key to set time: "); if (getch() == 3) /* Allow ^C abort */ exit(); putchar('\n'); } #ifdef DS1216 ibmm(SWaddr,SWbank,b64,TPAbank,64); expand(key,b64); ibmm(b64,TPAbank,SWaddr,SWbank,64); expand(buf,b64); ibmm(b64,TPAbank,SWaddr,SWbank,64); #endif #ifdef DS1216E for (y=64; y--;) ibmm(SWaddr+A2,SWbank,b64,TPAbank,1); expand(key,b64); for (y=0; y<64; ++y) ibmm(SWaddr+b64[y],SWbank,b1,TPAbank,1); expand(buf,b64); for (y=0; y<64; ++y) ibmm(SWaddr+b64[y],SWbank,b1,TPAbank,1); #endif /* If neither clock is present then the properly-formatted date is still in buf[] and in the struct time */ } /*----------------------------------------------------------------------------- m a i n ======= Entry point to this program. Performs primary command-line analysis and invokes appropriate function. -----------------------------------------------------------------------------*/ main(argc, argv) short int argc; unsigned char *argv[]; { signal(SIGINT, SIG_IGN); if (bdos(GET_VSN,0) < 0x30) { puts("This program requires CP/M 3"); exit(); } switch (argc) { case 1: showTime(0); break; case 2: switch (*(argv[1]) & 0x5F) { case 'S': setClock(NULL,NULL); #ifdef SWaddr case 'R': #endif readClock(); showTime(0); break; case 'C': showTime(1); break; default: usage(); } break; case 3: setClock(argv[1],argv[2]); readClock(); showTime(0); break; default: usage(); } }