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