date.con commit date.c: use the local timezone if none specified (eaa8512)
   1/*
   2 * GIT - The information manager from hell
   3 *
   4 * Copyright (C) Linus Torvalds, 2005
   5 */
   6
   7#include <stdio.h>
   8#include <stdlib.h>
   9#include <string.h>
  10#include <ctype.h>
  11#include <time.h>
  12
  13static time_t my_mktime(struct tm *tm)
  14{
  15        static const int mdays[] = {
  16            0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
  17        };
  18        int year = tm->tm_year - 70;
  19        int month = tm->tm_mon;
  20        int day = tm->tm_mday;
  21
  22        if (year < 0 || year > 129) /* algo only works for 1970-2099 */
  23                return -1;
  24        if (month < 0 || month > 11) /* array bounds */
  25                return -1;
  26        if (month < 2 || (year + 2) % 4)
  27                day--;
  28        return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
  29                tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
  30}
  31
  32static const char *month_names[] = {
  33        "January", "February", "March", "April", "May", "June",
  34        "July", "August", "September", "October", "November", "December"
  35};
  36
  37static const char *weekday_names[] = {
  38        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
  39};
  40
  41/*
  42 * Check these. And note how it doesn't do the summer-time conversion.
  43 *
  44 * In my world, it's always summer, and things are probably a bit off
  45 * in other ways too.
  46 */
  47static const struct {
  48        const char *name;
  49        int offset;
  50} timezone_names[] = {
  51        { "IDLW", -12 },        /* International Date Line West */
  52        { "NT",   -11 },        /* Nome */
  53        { "CAT",  -10 },        /* Central Alaska */
  54        { "HST",  -10 },        /* Hawaii Standard */
  55        { "HDT",   -9 },        /* Hawaii Daylight */
  56        { "YDT",   -8 },        /* Yukon Daylight */
  57        { "YST",   -9 },        /* Yukon Standard */
  58        { "PST",   -8 },        /* Pacific Standard */
  59        { "PDT",   -7 },        /* Pacific Daylight */
  60        { "MST",   -7 },        /* Mountain Standard */
  61        { "MDT",   -6 },        /* Mountain Daylight */
  62        { "CST",   -6 },        /* Central Standard */
  63        { "CDT",   -5 },        /* Central Daylight */
  64        { "EST",   -5 },        /* Eastern Standard */
  65        { "EDT",   -4 },        /* Eastern Daylight */
  66        { "AST",   -3 },        /* Atlantic Standard */
  67        { "ADT",   -2 },        /* Atlantic Daylight */
  68        { "WAT",   -1 },        /* West Africa */
  69
  70        { "GMT",    0 },        /* Greenwich Mean */
  71        { "UTC",    0 },        /* Universal (Coordinated) */
  72
  73        { "WET",    0 },        /* Western European */
  74        { "BST",    0 },        /* British Summer */
  75        { "CET",   +1 },        /* Central European */
  76        { "MET",   +1 },        /* Middle European */
  77        { "MEWT",  +1 },        /* Middle European Winter */
  78        { "MEST",  +2 },        /* Middle European Summer */
  79        { "CEST",  +2 },        /* Central European Summer */
  80        { "MESZ",  +1 },        /* Middle European Summer */
  81        { "FWT",   +1 },        /* French Winter */
  82        { "FST",   +2 },        /* French Summer */
  83        { "EET",   +2 },        /* Eastern Europe, USSR Zone 1 */
  84        { "WAST",  +7 },        /* West Australian Standard */
  85        { "WADT",  +8 },        /* West Australian Daylight */
  86        { "CCT",   +8 },        /* China Coast, USSR Zone 7 */
  87        { "JST",   +9 },        /* Japan Standard, USSR Zone 8 */
  88        { "EAST", +10 },        /* Eastern Australian Standard */
  89        { "EADT", +11 },        /* Eastern Australian Daylight */
  90        { "GST",  +10 },        /* Guam Standard, USSR Zone 9 */
  91        { "NZT",  +11 },        /* New Zealand */
  92        { "NZST", +11 },        /* New Zealand Standard */
  93        { "NZDT", +12 },        /* New Zealand Daylight */
  94        { "IDLE", +12 },        /* International Date Line East */
  95};
  96
  97#define NR_TZ (sizeof(timezone_names) / sizeof(timezone_names[0]))
  98        
  99static int match_string(const char *date, const char *str)
 100{
 101        int i = 0;
 102
 103        for (i = 0; *date; date++, str++, i++) {
 104                if (*date == *str)
 105                        continue;
 106                if (toupper(*date) == toupper(*str))
 107                        continue;
 108                if (!isalnum(*date))
 109                        break;
 110                return 0;
 111        }
 112        return i;
 113}
 114
 115/*
 116* Parse month, weekday, or timezone name
 117*/
 118static int match_alpha(const char *date, struct tm *tm, int *offset)
 119{
 120        int i;
 121
 122        for (i = 0; i < 12; i++) {
 123                int match = match_string(date, month_names[i]);
 124                if (match >= 3) {
 125                        tm->tm_mon = i;
 126                        return match;
 127                }
 128        }
 129
 130        for (i = 0; i < 7; i++) {
 131                int match = match_string(date, weekday_names[i]);
 132                if (match >= 3) {
 133                        tm->tm_wday = i;
 134                        return match;
 135                }
 136        }
 137
 138        for (i = 0; i < NR_TZ; i++) {
 139                int match = match_string(date, timezone_names[i].name);
 140                if (match >= 3) {
 141                        *offset = 60*timezone_names[i].offset;
 142                        return match;
 143                }
 144        }
 145
 146        /* BAD CRAP */
 147        return 0;
 148}
 149
 150static int match_digit(char *date, struct tm *tm, int *offset)
 151{
 152        char *end, c;
 153        unsigned long num, num2, num3;
 154
 155        num = strtoul(date, &end, 10);
 156
 157        /* Time? num:num[:num] */
 158        if (num < 24 && end[0] == ':' && isdigit(end[1])) {
 159                tm->tm_hour = num;
 160                num = strtoul(end+1, &end, 10);
 161                if (num < 60) {
 162                        tm->tm_min = num;
 163                        if (end[0] == ':' && isdigit(end[1])) {
 164                                num = strtoul(end+1, &end, 10);
 165                                if (num < 61)
 166                                        tm->tm_sec = num;
 167                        }
 168                }
 169                return end - date;
 170        }
 171
 172        /* Year? Day of month? Numeric date-string?*/
 173        c = *end;
 174        switch (c) {
 175        default:
 176                if (num > 0 && num < 32) {
 177                        tm->tm_mday = num;
 178                        break;
 179                }
 180                if (num > 1900) {
 181                        tm->tm_year = num - 1900;
 182                        break;
 183                }
 184                if (num > 70) {
 185                        tm->tm_year = num;
 186                        break;
 187                }
 188                break;
 189
 190        case '-':
 191        case '/':
 192                if (num && num < 32 && isdigit(end[1])) {
 193                        num2 = strtoul(end+1, &end, 10);
 194                        if (!num2 || num2 > 31)
 195                                break;
 196                        if (num > 12) {
 197                                if (num2 > 12)
 198                                        break;
 199                                num3 = num;
 200                                num  = num2;
 201                                num2 = num3;
 202                        }
 203                        tm->tm_mon = num - 1;
 204                        tm->tm_mday = num2;
 205                        if (*end == c && isdigit(end[1])) {
 206                                num3 = strtoul(end, &end, 10);
 207                                if (num3 > 1900)
 208                                        num3 -= 1900;
 209                                tm->tm_year = num3;
 210                        }
 211                        break;
 212                }
 213        }
 214                
 215        return end - date;
 216                        
 217}
 218
 219static int match_tz(char *date, int *offp)
 220{
 221        char *end;
 222        int offset = strtoul(date+1, &end, 10);
 223        int min, hour;
 224
 225        min = offset % 100;
 226        hour = offset / 100;
 227
 228        offset = hour*60+min;
 229        if (*date == '-')
 230                offset = -offset;
 231
 232        *offp = offset;
 233        return end - date;
 234}
 235
 236/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
 237   (i.e. English) day/month names, and it doesn't work correctly with %z. */
 238void parse_date(char *date, char *result, int maxlen)
 239{
 240        struct tm tm;
 241        int offset;
 242        time_t then;
 243
 244        memset(&tm, 0, sizeof(tm));
 245        tm.tm_year = -1;
 246        tm.tm_mon = -1;
 247        tm.tm_mday = -1;
 248        tm.tm_isdst = -1;
 249        offset = -1;
 250
 251        for (;;) {
 252                int match = 0;
 253                unsigned char c = *date;
 254
 255                /* Stop at end of string or newline */
 256                if (!c || c == '\n')
 257                        break;
 258
 259                if (isalpha(c))
 260                        match = match_alpha(date, &tm, &offset);
 261                else if (isdigit(c))
 262                        match = match_digit(date, &tm, &offset);
 263                else if ((c == '-' || c == '+') && isdigit(date[1]))
 264                        match = match_tz(date, &offset);
 265
 266                if (!match) {
 267                        /* BAD CRAP */
 268                        match = 1;
 269                }       
 270
 271                date += match;
 272        }
 273
 274        /* mktime uses local timezone */
 275        then = my_mktime(&tm); 
 276        if (offset == -1)
 277                offset = (then - mktime(&tm)) / 60;
 278
 279        if (then == -1)
 280                return;
 281
 282        then -= offset * 60;
 283
 284        snprintf(result, maxlen, "%lu %+03d%02d", then, offset/60, offset % 60);
 285}
 286
 287void datestamp(char *buf, int bufsize)
 288{
 289        time_t now;
 290        int offset;
 291
 292        time(&now);
 293
 294        offset = my_mktime(localtime(&now)) - now;
 295        offset /= 60;
 296
 297        snprintf(buf, bufsize, "%lu %+05d", now, offset/60*100 + offset%60);
 298}