Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does tm_mday start from 1 while all other elements of struct tm start from 0?

While answering another question, I told the OP he needs to initialize his struct tm variable correctly but needs to be careful because he couldn't simply use

struct tm mytime;
memset(&mytime, 0, sizeof(mytime));

because not all fields of struct tm are valid from 0. A closer look on struct tm showed me that it is exactly one field of struct tm that doesn't have 0 as valid value, namely tm_mday:

int    tm_sec   seconds [0,61]
int    tm_min   minutes [0,59]
int    tm_hour  hour [0,23]
int    tm_mday  day of month [1,31]
int    tm_mon   month of year [0,11]
int    tm_year  years since 1900
int    tm_wday  day of week [0,6] (Sunday = 0)
int    tm_yday  day of year [0,365]
int    tm_isdst daylight savings flag

Why? What were the thoughts behind the decision that for this very element, 0 shall be no valid value???

like image 413
eckes Avatar asked Jul 13 '15 10:07

eckes


1 Answers

It makes sense if you assume the following two rules:

  • Store the value starting from 1 if that allows for easiest display without having to add or subtract one in common date formats
  • In all other cases (or when the first rule could go either way depending on the format), store the value starting from 0

Applying the rule:

  • tm_sec, tm_min, tm_hour displayed starting from 0 so store starting from 0. In 12-hour format the first hour is 12, but the rest can be displayed "as-is" by starting from 0.
  • tm_mday displayed starting from 1 so store starting from 1
  • tm_mon displayed starting from 1 in dates like 24/02/1964, but also makes sense to store starting from 0 for ease of indexing strings in an array for dates like 24th February 1964, so could go either way -> start from 0
  • tm_year 20th century years can be displayed as-is in 2 year format e.g. 24/02/64, or else add 1900, no case where starting from 1 makes sense
  • tm_wday Usually displayed by indexing a string array, start from 0
  • tm_yday No clear reason to start from 1 for ease of display, start from 0

So tm_mday is the only case where there is a clear advantage to storing it starting from 1 for ease of display in all common cases.

The reference implementation of asctime from the C-89 standard is consistent with this, the only adjustment to any of the values being to add 1900 to tm_year:

     char *asctime(const struct tm *timeptr)
     {
         static const char wday_name[7][3] = {
                  "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
         };
         static const char mon_name[12][3] = {
                  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
         };
         static char result[26];

         sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
                  wday_name[timeptr->tm_wday],
                  mon_name[timeptr->tm_mon],
                  timeptr->tm_mday, timeptr->tm_hour,
                  timeptr->tm_min, timeptr->tm_sec,
                  1900 + timeptr->tm_year);
         return result;
     }
like image 101
samgak Avatar answered Sep 27 '22 22:09

samgak