1 /*
2  * sccsdate.cc: Part of GNU CSSC.
3  *
4  *
5  *  Copyright (C) 1997, 2007, 2008, 2009, 2010, 2011, 2014, 2019 Free
6  *  Software Foundation, Inc.
7  *
8  *  This program is free software: you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation, either version 3 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  * CSSC was originally Based on MySC, by Ross Ridge, which was
22  * placed in the Public Domain.
23  *
24  *
25  * Members of the class sccs_date.
26  *
27  */
28 #include <config.h>
29 #include <cstring>
30 #include <string>
31 #include <memory>
32 
33 #include "cssc.h"
34 #include "sccsdate.h"
35 #include "cssc-assert.h"
36 
37 #include <time.h>
38 
39 #include <ctype.h>
40 
41 namespace
42 {
y2k_window(int year)43   int y2k_window(int year)
44   {
45     // In the X/Open Commands and Utilities Issue 5 standard,
46     // it is specified that yy/mm/dd type dates with values
47     // for "yy" which range from 69-99 are to be interpreted
48     // as 1969-1999 and dates with year values 00-68 are to be
49     // interpreted as 2000-2068.
50     //
51     // For more information about this, please see that
52     // document itself and also the X/Open Year-2000 FAQ,
53     // which can be obtained from the URL
54     // http://www.xopen.org/public/tech/base/year2000.html
55     //
56     if (year < 69)
57       year += 2000;
58     else
59       year += 1900;
60 
61     ASSERT(year >= 1969);
62     ASSERT(year <  2069);
63     return year;
64   }
65 }
66 
67 // The MySC code used to just check for (year % 4) and (year == 0).
68 // This implementation is "right", but in any case it won't make a
69 // difference until 2100AD.
70 static int
is_leapyear(int year)71 is_leapyear(int year)
72 {
73   if (year % 4)
74     {
75       return 0;                 // not a leapyear.
76     }
77   else
78     {
79       if (year % 100)
80         {
81           return 1;             // non-century year
82         }
83       else
84         {
85           if (year % 400)
86             return 0;           // century years are not leap-years except
87           else
88             return 1;           // every fourth one, which IS a leap year.
89         }
90     }
91 }
92 
93 static int
days_in_month(int mon,int year)94 days_in_month(int mon, int year)
95 {
96   switch(mon)
97     {
98     case 1:
99     case 3:
100     case 5:
101     case 7:
102     case 8:
103     case 10:
104     case 12:
105       return 31;
106 
107     case 4:
108     case 6:
109     case 9:
110     case 11:
111       return 30;
112 
113     case 2:
114       if (is_leapyear(year))
115         return 29;
116       else
117         return 28;
118     }
119   return -1;
120 }
121 
122 static inline int
is_digit(char ch)123 is_digit(char ch)
124 {
125   return isdigit( (unsigned char) ch );
126 }
127 
128 static inline int
get_digit(char ch)129 get_digit(char ch)
130 {
131   ASSERT(isdigit( (unsigned char) ch ));
132   return ch - '0';
133 }
134 
135 static int
get_two_digits(const char * s)136 get_two_digits(const char *s)
137 {
138   return get_digit(s[0]) * 10 + get_digit(s[1]);
139 }
140 
141 static int
get_two_digits(const string & s,size_t pos)142 get_two_digits(const string& s, size_t pos)
143 {
144   return get_digit(s[pos]) * 10 + get_digit(s[pos+1]);
145 }
146 
147 static int
get_part(const char * & s,int def)148 get_part(const char *&s, int def)
149 {
150   char c = *s;
151   while (c)
152     {
153       if (is_digit(c))
154         {
155           s++;
156           if (is_digit(*s))
157             {
158               return get_digit(c) * 10 + get_digit(*s++);
159             }
160           else
161             {
162               return get_digit(c);
163             }
164         }
165       c = *++s;
166     }
167   return def;
168 }
169 
170 static int
count_digits(const char * s)171 count_digits(const char *s)
172 {
173   int count;
174 
175   for(count=0; *s; s++)
176     {
177       if (is_digit(*s))
178         count++;
179     }
180   return count;
181 }
182 
183 // Construct a date as specified on the command line.
sccs_date(const char * s)184 sccs_date::sccs_date(const char *s)
185 {
186   ASSERT(s != 0);
187   /* if (s == 0) return; */
188 
189   // A fully-qualified YYmmDDhhMMss specification contains
190   // twelve (12) digits.  If we have more than that, assume that
191   // the first two digits are the century.  We have to make this
192   // count before the first call to get_part(), since that
193   // actually advances the pointer.
194   const int n_digits = count_digits(s);
195 
196   // Get and check the year part.
197   if ( (year = get_part(s, -1)) == -1)
198     return;
199 
200 #if 1
201   // Here be Year-2000 code.
202   //
203   // This code checks if the year is 19 or 20 AND the next two
204   // characters are ALSO digits.  If so, we assume that we've
205   // just got the century.  This is an extension with respect to
206   // "real" SCCS.
207   //
208   // To prevent this simply breaking down in the year 2019 (with
209   // a two digit year part), we only activate this measure if we
210   // have more than the regulation number of digits.
211   //
212   // The version of MySC which I inherited assumed the first 2
213   // digits to be the century part if they were 19 or 20.  This
214   // approach breaks down rather unexpectedly for the user in
215   // the year 2019.
216   //
217   if (n_digits > 12)
218     {
219       // If we actually have a 4-digit year, the next two
220       // characters must be digits (we have already consumed the
221       // first two).
222       if (isdigit((unsigned char)s[0]) &&
223           isdigit((unsigned char)s[1]))
224         {
225           const int century_field_val = year;
226           year =  (century_field_val * 100) + get_two_digits(&s[0]);
227           s += 2;               // this consumes exactly two characters.
228         }
229     }
230   else
231     {
232       year = y2k_window(year);
233     }
234 #endif
235 
236   month     = get_part(s, 12);
237   month_day = get_part(s, days_in_month(month, year));
238   hour      = get_part(s, 23);
239   minute    = get_part(s, 59);
240   second    = get_part(s, 59);
241 
242   update_yearday();
243 }
244 
245 // Construct a date as specified in an SCCS file.
sccs_date(const char * date_arg,const char * time)246 sccs_date::sccs_date(const char *date_arg, const char *time)
247 {
248   string date(date_arg);
249   int century;
250 
251   /* Check for the symtoms of SourceForge bug ID 513800, where
252    * the Data General version of Unix puts a four-digit year
253    * into the p-file.
254    */
255   if (date.size() > 4
256       && is_digit(date[0])
257       && is_digit(date[1])
258       && is_digit(date[2])
259       && is_digit(date[3])
260       && ('/' == date[4]))
261   {
262       warning("this file has been written by a version of SCCS"
263 	      " which uses four-digit years, which is contrary to the"
264 	      " common SCCS file format (though it might have been a "
265 	      " good idea in the first place)\n");
266       century = get_two_digits(date, 0);
267       date = string(date, 2);
268   }
269   else
270   {
271       // this is a normal two-digit date.
272       century = 0;              // decide by windowing.
273   }
274 
275   // Peter Kjellerstedt writes:-
276   //
277   // This is a gross hack to handle that some old implementation of SCCS
278   // has a Y2K bug that results in that dates are written incorrectly as
279   // :0/01/01 instead of 00/01/01 (the colon is of course the next
280   // character after '9' in the ASCII table). The following should handle
281   // this correctly for years up to 2069 (after which the time format
282   // used is not valid anyway).
283   if (date[0] >= ':' && date[0] <= '@')
284     {
285       warning("date in SCCS file contains character '%c': "
286 	      "a version of SCCS which is not Year 2000 compliant "
287 	      "has probably been used on this file.\n",
288 	      date[0]);
289       /* We're about to subtract 10 from date[0], so make sure that's safe. */
290       static_assert(':' > 10, "unsupported character encoding");
291       date[0] = static_cast<char>(date[0] - 10);
292     }
293 
294   // The "1" in the if() is just there to make Emacs align the columns.
295   if (1
296       && is_digit(date[0]) && is_digit(date[1]) && date[2] == '/'
297       && is_digit(date[3]) && is_digit(date[4]) && date[5] == '/'
298       && is_digit(date[6]) && is_digit(date[7]) && date[8] == 0
299 
300       && is_digit(time[0]) && is_digit(time[1]) && time[2] == ':'
301       && is_digit(time[3]) && is_digit(time[4]) && time[5] == ':'
302       && is_digit(time[6]) && is_digit(time[7]) && time[8] == '\0')
303     {
304       year      = get_two_digits(date, 0);
305       month     = get_two_digits(date, 3);
306       month_day = get_two_digits(date, 6);
307 
308       hour      = get_two_digits(&time[0]);
309       minute    = get_two_digits(&time[3]);
310       second    = get_two_digits(&time[6]);
311 
312       if (century)
313       {
314           // SourceForge bug ID 513800 - Data General Unix uses 4-digit year
315           // in the p-file.
316           year = century * 100 + year;
317 	  ASSERT(year >= 1969);
318 	  ASSERT(year <  2069);
319       }
320       else
321       {
322 	// Year 2000 fix (mandated by X/Open white paper, see above
323 	// for more details).
324 	year = y2k_window(year);
325       }
326       update_yearday();
327     }
328 }
329 
330 int
printf(FILE * f,char fmt) const331 sccs_date::printf(FILE *f, char fmt) const
332 {
333   const int yy = year % 100;
334 
335   switch(fmt)
336     {
337     case 'D':
338       return printf_failed(fprintf(f, "%02d/%02d/%02d",
339                                    yy, month, month_day));
340     case 'H':
341       return printf_failed(fprintf(f, "%02d/%02d/%02d",
342                                    month, month_day, yy));
343 
344     case 'T':
345       return printf_failed(fprintf(f, "%02d:%02d:%02d",
346                                    hour, minute, second));
347     }
348 
349   int value = 0;
350   switch (fmt)
351     {
352     case 'y':
353       value = yy;
354       break;
355 
356     case 'o':
357       value = month;
358       break;
359 
360     case 'd':
361       value = month_day;
362       break;
363 
364     case 'h':
365       value = hour;
366       break;
367 
368     case 'm':
369       value = minute;
370       break;
371 
372     case 's':
373       value = second;
374       break;
375 
376     default:
377       ASSERT(!"sccs_date::printf: Invalid format");
378       // This code IS reached, when ASSERT() expands to nothing.
379       // TODO: throw exception here
380     }
381   return printf_failed(fprintf(f, "%02d", value));
382 }
383 
384 int
print(FILE * f) const385 sccs_date::print(FILE *f) const
386 {
387   return this->printf(f, 'D')
388     || putc_failed(putc(' ', f))
389     || this->printf(f, 'T');
390 }
391 
392 
393 std::string
as_string() const394 sccs_date::as_string() const
395 {
396   char buf[18];
397   const int yy = year % 100;
398 
399   sprintf(buf, "%02d/%02d/%02d %02d:%02d:%02d",
400           yy, month, month_day,
401           hour, minute, second);
402 
403   return std::string(buf);
404 }
405 
sccs_date(int yr,int mth,int day,int hr,int min,int sec)406 sccs_date::sccs_date(int yr, int mth, int day,
407                      int hr, int min, int sec)
408   : year(yr), month(mth), month_day(day),
409     hour(hr), minute(min), second(sec)
410 {
411   update_yearday();
412 }
413 
sccs_date()414 sccs_date::sccs_date()
415   : year(-1), month(-1), month_day(-1),
416     hour(-1), minute(-1), second(-1)
417 {
418 }
419 
420 sccs_date
now()421 sccs_date::now()                // static member.
422 {
423   time_t tt;
424   time(&tt);                    // TODO: throw exception if this fails.
425   struct tm *ptm = localtime(&tt);
426 
427   return sccs_date(ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,
428                    ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
429 }
430 
431 void
update_yearday()432 sccs_date::update_yearday()
433 {
434   int m=1, d=1;
435 
436   yearday = 1;
437 
438   while (m < month)
439     yearday += days_in_month(m++, year);
440 
441   while (d++ < month_day)
442     yearday++;
443 }
444 
445 
compare(const sccs_date & d) const446 int sccs_date::compare(const sccs_date& d) const
447 {
448   int diff;
449 
450   if ( (diff = year - d.year) != 0 )
451     return diff;
452   else if ( (diff = yearday - d.yearday) != 0)
453     return diff;
454 
455   /* The implementation below can compute an incorrect result when
456      comparing dates on a day when there is a clock change.  For
457      example, suppose the clock is put pack an hour the first time it
458      reaches 02:00 on this day.  Let t1 be a time 3602 seconds into
459      this day, and t2 be a time 7201 seconds into this day.  For both,
460      hour=1 and minute=0 but t1 has second=2 and t2 has second=1.
461      This function will decide that t1 > t2 while in reality t2 > t1.
462 
463      So in theory this implementation is incorrect.  However, since
464      the SCCS date format does not include time zone information, we
465      cannot receive t2 as input in this case (it would bre represnted
466      as the time 01:00::01 and would be understood to represent a time
467      3601 seconds into the day).
468    */
469   if ((diff = hour - d.hour) != 0)
470     return diff;
471   else if ((diff = minute - d.minute) != 0)
472     return diff;
473   else
474     return second - d.second;
475 }
476 
operator >(sccs_date const & d) const477 int sccs_date::operator >(sccs_date const & d) const
478 {
479   return compare(d) > 0;
480 }
481 
operator <(sccs_date const & d) const482 int sccs_date::operator <(sccs_date const &d) const
483 {
484   return compare(d) < 0;
485 }
486 
operator <=(sccs_date const & d) const487 int sccs_date::operator <=(sccs_date const &d) const
488 {
489   return compare(d) <= 0;
490 }
491 
492 int
valid() const493 sccs_date::valid() const
494 {
495   // Allow the seconds field to get as high as 61, since that is what
496   // the ANSI C spec for struct tm says, and we have to use a struct
497   // tm with localtime().
498   return year >= 0
499     && month > 0 && month < 13
500     && month_day > 0 && month_day <= days_in_month(month, year)
501     && hour >= 0 && hour < 24
502     && minute >= 0 && minute < 60
503     && second >= 0 && second <= 61;
504 }
505 
506 
507 /* Local variables: */
508 /* mode: c++ */
509 /* End: */
510