1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* 18 * apr_date.c: date parsing utility routines 19 * These routines are (hopefully) platform independent. 20 * 21 * 27 Oct 1996 Roy Fielding 22 * Extracted (with many modifications) from mod_proxy.c and 23 * tested with over 50,000 randomly chosen valid date strings 24 * and several hundred variations of invalid date strings. 25 * 26 */ 27 28#include "apr.h" 29#include "apr_lib.h" 30 31#define APR_WANT_STRFUNC 32#include "apr_want.h" 33 34#if APR_HAVE_STDLIB_H 35#include <stdlib.h> 36#endif 37 38#if APR_HAVE_CTYPE_H 39#include <ctype.h> 40#endif 41 42#include "apr_date.h" 43 44/* 45 * Compare a string to a mask 46 * Mask characters (arbitrary maximum is 256 characters, just in case): 47 * @ - uppercase letter 48 * $ - lowercase letter 49 * & - hex digit 50 * # - digit 51 * ~ - digit or space 52 * * - swallow remaining characters 53 * <x> - exact match for any other character 54 */ 55APU_DECLARE(int) apr_date_checkmask(const char *data, const char *mask) 56{ 57 int i; 58 char d; 59 60 for (i = 0; i < 256; i++) { 61 d = data[i]; 62 switch (mask[i]) { 63 case '\0': 64 return (d == '\0'); 65 66 case '*': 67 return 1; 68 69 case '@': 70 if (!apr_isupper(d)) 71 return 0; 72 break; 73 case '$': 74 if (!apr_islower(d)) 75 return 0; 76 break; 77 case '#': 78 if (!apr_isdigit(d)) 79 return 0; 80 break; 81 case '&': 82 if (!apr_isxdigit(d)) 83 return 0; 84 break; 85 case '~': 86 if ((d != ' ') && !apr_isdigit(d)) 87 return 0; 88 break; 89 default: 90 if (mask[i] != d) 91 return 0; 92 break; 93 } 94 } 95 return 0; /* We only get here if mask is corrupted (exceeds 256) */ 96} 97 98/* 99 * Parses an HTTP date in one of three standard forms: 100 * 101 * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 102 * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 103 * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format 104 * 105 * and returns the apr_time_t number of microseconds since 1 Jan 1970 GMT, 106 * or APR_DATE_BAD if this would be out of range or if the date is invalid. 107 * 108 * The restricted HTTP syntax is 109 * 110 * HTTP-date = rfc1123-date | rfc850-date | asctime-date 111 * 112 * rfc1123-date = wkday "," SP date1 SP time SP "GMT" 113 * rfc850-date = weekday "," SP date2 SP time SP "GMT" 114 * asctime-date = wkday SP date3 SP time SP 4DIGIT 115 * 116 * date1 = 2DIGIT SP month SP 4DIGIT 117 * ; day month year (e.g., 02 Jun 1982) 118 * date2 = 2DIGIT "-" month "-" 2DIGIT 119 * ; day-month-year (e.g., 02-Jun-82) 120 * date3 = month SP ( 2DIGIT | ( SP 1DIGIT )) 121 * ; month day (e.g., Jun 2) 122 * 123 * time = 2DIGIT ":" 2DIGIT ":" 2DIGIT 124 * ; 00:00:00 - 23:59:59 125 * 126 * wkday = "Mon" | "Tue" | "Wed" 127 * | "Thu" | "Fri" | "Sat" | "Sun" 128 * 129 * weekday = "Monday" | "Tuesday" | "Wednesday" 130 * | "Thursday" | "Friday" | "Saturday" | "Sunday" 131 * 132 * month = "Jan" | "Feb" | "Mar" | "Apr" 133 * | "May" | "Jun" | "Jul" | "Aug" 134 * | "Sep" | "Oct" | "Nov" | "Dec" 135 * 136 * However, for the sake of robustness (and Netscapeness), we ignore the 137 * weekday and anything after the time field (including the timezone). 138 * 139 * This routine is intended to be very fast; 10x faster than using sscanf. 140 * 141 * Originally from Andrew Daviel <andrew@vancouver-webpages.com>, 29 Jul 96 142 * but many changes since then. 143 * 144 */ 145APU_DECLARE(apr_time_t) apr_date_parse_http(const char *date) 146{ 147 apr_time_exp_t ds; 148 apr_time_t result; 149 int mint, mon; 150 const char *monstr, *timstr; 151 static const int months[12] = 152 { 153 ('J' << 16) | ('a' << 8) | 'n', ('F' << 16) | ('e' << 8) | 'b', 154 ('M' << 16) | ('a' << 8) | 'r', ('A' << 16) | ('p' << 8) | 'r', 155 ('M' << 16) | ('a' << 8) | 'y', ('J' << 16) | ('u' << 8) | 'n', 156 ('J' << 16) | ('u' << 8) | 'l', ('A' << 16) | ('u' << 8) | 'g', 157 ('S' << 16) | ('e' << 8) | 'p', ('O' << 16) | ('c' << 8) | 't', 158 ('N' << 16) | ('o' << 8) | 'v', ('D' << 16) | ('e' << 8) | 'c'}; 159 160 if (!date) 161 return APR_DATE_BAD; 162 163 while (*date && apr_isspace(*date)) /* Find first non-whitespace char */ 164 ++date; 165 166 if (*date == '\0') 167 return APR_DATE_BAD; 168 169 if ((date = strchr(date, ' ')) == NULL) /* Find space after weekday */ 170 return APR_DATE_BAD; 171 172 ++date; /* Now pointing to first char after space, which should be */ 173 174 /* start of the actual date information for all 4 formats. */ 175 176 if (apr_date_checkmask(date, "## @$$ #### ##:##:## *")) { 177 /* RFC 1123 format with two days */ 178 ds.tm_year = ((date[7] - '0') * 10 + (date[8] - '0') - 19) * 100; 179 if (ds.tm_year < 0) 180 return APR_DATE_BAD; 181 182 ds.tm_year += ((date[9] - '0') * 10) + (date[10] - '0'); 183 184 ds.tm_mday = ((date[0] - '0') * 10) + (date[1] - '0'); 185 186 monstr = date + 3; 187 timstr = date + 12; 188 } 189 else if (apr_date_checkmask(date, "##-@$$-## ##:##:## *")) { 190 /* RFC 850 format */ 191 ds.tm_year = ((date[7] - '0') * 10) + (date[8] - '0'); 192 if (ds.tm_year < 70) 193 ds.tm_year += 100; 194 195 ds.tm_mday = ((date[0] - '0') * 10) + (date[1] - '0'); 196 197 monstr = date + 3; 198 timstr = date + 10; 199 } 200 else if (apr_date_checkmask(date, "@$$ ~# ##:##:## ####*")) { 201 /* asctime format */ 202 ds.tm_year = ((date[16] - '0') * 10 + (date[17] - '0') - 19) * 100; 203 if (ds.tm_year < 0) 204 return APR_DATE_BAD; 205 206 ds.tm_year += ((date[18] - '0') * 10) + (date[19] - '0'); 207 208 if (date[4] == ' ') 209 ds.tm_mday = 0; 210 else 211 ds.tm_mday = (date[4] - '0') * 10; 212 213 ds.tm_mday += (date[5] - '0'); 214 215 monstr = date; 216 timstr = date + 7; 217 } 218 else if (apr_date_checkmask(date, "# @$$ #### ##:##:## *")) { 219 /* RFC 1123 format with one day */ 220 ds.tm_year = ((date[6] - '0') * 10 + (date[7] - '0') - 19) * 100; 221 if (ds.tm_year < 0) 222 return APR_DATE_BAD; 223 224 ds.tm_year += ((date[8] - '0') * 10) + (date[9] - '0'); 225 226 ds.tm_mday = (date[0] - '0'); 227 228 monstr = date + 2; 229 timstr = date + 11; 230 } 231 else 232 return APR_DATE_BAD; 233 234 if (ds.tm_mday <= 0 || ds.tm_mday > 31) 235 return APR_DATE_BAD; 236 237 ds.tm_hour = ((timstr[0] - '0') * 10) + (timstr[1] - '0'); 238 ds.tm_min = ((timstr[3] - '0') * 10) + (timstr[4] - '0'); 239 ds.tm_sec = ((timstr[6] - '0') * 10) + (timstr[7] - '0'); 240 241 if ((ds.tm_hour > 23) || (ds.tm_min > 59) || (ds.tm_sec > 61)) 242 return APR_DATE_BAD; 243 244 mint = (monstr[0] << 16) | (monstr[1] << 8) | monstr[2]; 245 for (mon = 0; mon < 12; mon++) 246 if (mint == months[mon]) 247 break; 248 249 if (mon == 12) 250 return APR_DATE_BAD; 251 252 if ((ds.tm_mday == 31) && (mon == 3 || mon == 5 || mon == 8 || mon == 10)) 253 return APR_DATE_BAD; 254 255 /* February gets special check for leapyear */ 256 if ((mon == 1) && 257 ((ds.tm_mday > 29) || 258 ((ds.tm_mday == 29) 259 && ((ds.tm_year & 3) 260 || (((ds.tm_year % 100) == 0) 261 && (((ds.tm_year % 400) != 100))))))) 262 return APR_DATE_BAD; 263 264 ds.tm_mon = mon; 265 266 /* ap_mplode_time uses tm_usec and tm_gmtoff fields, but they haven't 267 * been set yet. 268 * It should be safe to just zero out these values. 269 * tm_usec is the number of microseconds into the second. HTTP only 270 * cares about second granularity. 271 * tm_gmtoff is the number of seconds off of GMT the time is. By 272 * definition all times going through this function are in GMT, so this 273 * is zero. 274 */ 275 ds.tm_usec = 0; 276 ds.tm_gmtoff = 0; 277 if (apr_time_exp_get(&result, &ds) != APR_SUCCESS) 278 return APR_DATE_BAD; 279 280 return result; 281} 282 283/* 284 * Parses a string resembling an RFC 822 date. This is meant to be 285 * leinent in its parsing of dates. Hence, this will parse a wider 286 * range of dates than apr_date_parse_http. 287 * 288 * The prominent mailer (or poster, if mailer is unknown) that has 289 * been seen in the wild is included for the unknown formats. 290 * 291 * Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 292 * Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 293 * Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format 294 * Sun, 6 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 295 * Sun, 06 Nov 94 08:49:37 GMT ; RFC 822 296 * Sun, 6 Nov 94 08:49:37 GMT ; RFC 822 297 * Sun, 06 Nov 94 08:49 GMT ; Unknown [drtr@ast.cam.ac.uk] 298 * Sun, 6 Nov 94 08:49 GMT ; Unknown [drtr@ast.cam.ac.uk] 299 * Sun, 06 Nov 94 8:49:37 GMT ; Unknown [Elm 70.85] 300 * Sun, 6 Nov 94 8:49:37 GMT ; Unknown [Elm 70.85] 301 * Mon, 7 Jan 2002 07:21:22 GMT ; Unknown [Postfix] 302 * Sun, 06-Nov-1994 08:49:37 GMT ; RFC 850 with four digit years 303 * 304 */ 305 306#define TIMEPARSE(ds,hr10,hr1,min10,min1,sec10,sec1) \ 307 { \ 308 ds.tm_hour = ((hr10 - '0') * 10) + (hr1 - '0'); \ 309 ds.tm_min = ((min10 - '0') * 10) + (min1 - '0'); \ 310 ds.tm_sec = ((sec10 - '0') * 10) + (sec1 - '0'); \ 311 } 312#define TIMEPARSE_STD(ds,timstr) \ 313 { \ 314 TIMEPARSE(ds, timstr[0],timstr[1], \ 315 timstr[3],timstr[4], \ 316 timstr[6],timstr[7]); \ 317 } 318 319APU_DECLARE(apr_time_t) apr_date_parse_rfc(const char *date) 320{ 321 apr_time_exp_t ds; 322 apr_time_t result; 323 int mint, mon; 324 const char *monstr, *timstr, *gmtstr; 325 static const int months[12] = 326 { 327 ('J' << 16) | ('a' << 8) | 'n', ('F' << 16) | ('e' << 8) | 'b', 328 ('M' << 16) | ('a' << 8) | 'r', ('A' << 16) | ('p' << 8) | 'r', 329 ('M' << 16) | ('a' << 8) | 'y', ('J' << 16) | ('u' << 8) | 'n', 330 ('J' << 16) | ('u' << 8) | 'l', ('A' << 16) | ('u' << 8) | 'g', 331 ('S' << 16) | ('e' << 8) | 'p', ('O' << 16) | ('c' << 8) | 't', 332 ('N' << 16) | ('o' << 8) | 'v', ('D' << 16) | ('e' << 8) | 'c' }; 333 334 if (!date) 335 return APR_DATE_BAD; 336 337 /* Not all dates have text days at the beginning. */ 338 if (!apr_isdigit(date[0])) 339 { 340 while (*date && apr_isspace(*date)) /* Find first non-whitespace char */ 341 ++date; 342 343 if (*date == '\0') 344 return APR_DATE_BAD; 345 346 if ((date = strchr(date, ' ')) == NULL) /* Find space after weekday */ 347 return APR_DATE_BAD; 348 349 ++date; /* Now pointing to first char after space, which should be */ } 350 351 /* start of the actual date information for all 11 formats. */ 352 if (apr_date_checkmask(date, "## @$$ #### ##:##:## *")) { /* RFC 1123 format */ 353 ds.tm_year = ((date[7] - '0') * 10 + (date[8] - '0') - 19) * 100; 354 355 if (ds.tm_year < 0) 356 return APR_DATE_BAD; 357 358 ds.tm_year += ((date[9] - '0') * 10) + (date[10] - '0'); 359 360 ds.tm_mday = ((date[0] - '0') * 10) + (date[1] - '0'); 361 362 monstr = date + 3; 363 timstr = date + 12; 364 gmtstr = date + 21; 365 366 TIMEPARSE_STD(ds, timstr); 367 } 368 else if (apr_date_checkmask(date, "##-@$$-## ##:##:## *")) {/* RFC 850 format */ 369 ds.tm_year = ((date[7] - '0') * 10) + (date[8] - '0'); 370 371 if (ds.tm_year < 70) 372 ds.tm_year += 100; 373 374 ds.tm_mday = ((date[0] - '0') * 10) + (date[1] - '0'); 375 376 monstr = date + 3; 377 timstr = date + 10; 378 gmtstr = date + 19; 379 380 TIMEPARSE_STD(ds, timstr); 381 } 382 else if (apr_date_checkmask(date, "@$$ ~# ##:##:## ####*")) { 383 /* asctime format */ 384 ds.tm_year = ((date[16] - '0') * 10 + (date[17] - '0') - 19) * 100; 385 if (ds.tm_year < 0) 386 return APR_DATE_BAD; 387 388 ds.tm_year += ((date[18] - '0') * 10) + (date[19] - '0'); 389 390 if (date[4] == ' ') 391 ds.tm_mday = 0; 392 else 393 ds.tm_mday = (date[4] - '0') * 10; 394 395 ds.tm_mday += (date[5] - '0'); 396 397 monstr = date; 398 timstr = date + 7; 399 gmtstr = NULL; 400 401 TIMEPARSE_STD(ds, timstr); 402 } 403 else if (apr_date_checkmask(date, "# @$$ #### ##:##:## *")) { 404 /* RFC 1123 format*/ 405 ds.tm_year = ((date[6] - '0') * 10 + (date[7] - '0') - 19) * 100; 406 407 if (ds.tm_year < 0) 408 return APR_DATE_BAD; 409 410 ds.tm_year += ((date[8] - '0') * 10) + (date[9] - '0'); 411 ds.tm_mday = (date[0] - '0'); 412 413 monstr = date + 2; 414 timstr = date + 11; 415 gmtstr = date + 20; 416 417 TIMEPARSE_STD(ds, timstr); 418 } 419 else if (apr_date_checkmask(date, "## @$$ ## ##:##:## *")) { 420 /* This is the old RFC 1123 date format - many many years ago, people 421 * used two-digit years. Oh, how foolish. 422 * 423 * Two-digit day, two-digit year version. */ 424 ds.tm_year = ((date[7] - '0') * 10) + (date[8] - '0'); 425 426 if (ds.tm_year < 70) 427 ds.tm_year += 100; 428 429 ds.tm_mday = ((date[0] - '0') * 10) + (date[1] - '0'); 430 431 monstr = date + 3; 432 timstr = date + 10; 433 gmtstr = date + 19; 434 435 TIMEPARSE_STD(ds, timstr); 436 } 437 else if (apr_date_checkmask(date, " # @$$ ## ##:##:## *")) { 438 /* This is the old RFC 1123 date format - many many years ago, people 439 * used two-digit years. Oh, how foolish. 440 * 441 * Space + one-digit day, two-digit year version.*/ 442 ds.tm_year = ((date[7] - '0') * 10) + (date[8] - '0'); 443 444 if (ds.tm_year < 70) 445 ds.tm_year += 100; 446 447 ds.tm_mday = (date[1] - '0'); 448 449 monstr = date + 3; 450 timstr = date + 10; 451 gmtstr = date + 19; 452 453 TIMEPARSE_STD(ds, timstr); 454 } 455 else if (apr_date_checkmask(date, "# @$$ ## ##:##:## *")) { 456 /* This is the old RFC 1123 date format - many many years ago, people 457 * used two-digit years. Oh, how foolish. 458 * 459 * One-digit day, two-digit year version. */ 460 ds.tm_year = ((date[6] - '0') * 10) + (date[7] - '0'); 461 462 if (ds.tm_year < 70) 463 ds.tm_year += 100; 464 465 ds.tm_mday = (date[0] - '0'); 466 467 monstr = date + 2; 468 timstr = date + 9; 469 gmtstr = date + 18; 470 471 TIMEPARSE_STD(ds, timstr); 472 } 473 else if (apr_date_checkmask(date, "## @$$ ## ##:## *")) { 474 /* Loser format. This is quite bogus. */ 475 ds.tm_year = ((date[7] - '0') * 10) + (date[8] - '0'); 476 477 if (ds.tm_year < 70) 478 ds.tm_year += 100; 479 480 ds.tm_mday = ((date[0] - '0') * 10) + (date[1] - '0'); 481 482 monstr = date + 3; 483 timstr = date + 10; 484 gmtstr = NULL; 485 486 TIMEPARSE(ds, timstr[0],timstr[1], timstr[3],timstr[4], '0','0'); 487 } 488 else if (apr_date_checkmask(date, "# @$$ ## ##:## *")) { 489 /* Loser format. This is quite bogus. */ 490 ds.tm_year = ((date[6] - '0') * 10) + (date[7] - '0'); 491 492 if (ds.tm_year < 70) 493 ds.tm_year += 100; 494 495 ds.tm_mday = (date[0] - '0'); 496 497 monstr = date + 2; 498 timstr = date + 9; 499 gmtstr = NULL; 500 501 TIMEPARSE(ds, timstr[0],timstr[1], timstr[3],timstr[4], '0','0'); 502 } 503 else if (apr_date_checkmask(date, "## @$$ ## #:##:## *")) { 504 /* Loser format. This is quite bogus. */ 505 ds.tm_year = ((date[7] - '0') * 10) + (date[8] - '0'); 506 507 if (ds.tm_year < 70) 508 ds.tm_year += 100; 509 510 ds.tm_mday = ((date[0] - '0') * 10) + (date[1] - '0'); 511 512 monstr = date + 3; 513 timstr = date + 9; 514 gmtstr = date + 18; 515 516 TIMEPARSE(ds, '0',timstr[1], timstr[3],timstr[4], timstr[6],timstr[7]); 517 } 518 else if (apr_date_checkmask(date, "# @$$ ## #:##:## *")) { 519 /* Loser format. This is quite bogus. */ 520 ds.tm_year = ((date[6] - '0') * 10) + (date[7] - '0'); 521 522 if (ds.tm_year < 70) 523 ds.tm_year += 100; 524 525 ds.tm_mday = (date[0] - '0'); 526 527 monstr = date + 2; 528 timstr = date + 8; 529 gmtstr = date + 17; 530 531 TIMEPARSE(ds, '0',timstr[1], timstr[3],timstr[4], timstr[6],timstr[7]); 532 } 533 else if (apr_date_checkmask(date, " # @$$ #### ##:##:## *")) { 534 /* RFC 1123 format with a space instead of a leading zero. */ 535 ds.tm_year = ((date[7] - '0') * 10 + (date[8] - '0') - 19) * 100; 536 537 if (ds.tm_year < 0) 538 return APR_DATE_BAD; 539 540 ds.tm_year += ((date[9] - '0') * 10) + (date[10] - '0'); 541 542 ds.tm_mday = (date[1] - '0'); 543 544 monstr = date + 3; 545 timstr = date + 12; 546 gmtstr = date + 21; 547 548 TIMEPARSE_STD(ds, timstr); 549 } 550 else if (apr_date_checkmask(date, "##-@$$-#### ##:##:## *")) { 551 /* RFC 1123 with dashes instead of spaces between date/month/year 552 * This also looks like RFC 850 with four digit years. 553 */ 554 ds.tm_year = ((date[7] - '0') * 10 + (date[8] - '0') - 19) * 100; 555 if (ds.tm_year < 0) 556 return APR_DATE_BAD; 557 558 ds.tm_year += ((date[9] - '0') * 10) + (date[10] - '0'); 559 560 ds.tm_mday = ((date[0] - '0') * 10) + (date[1] - '0'); 561 562 monstr = date + 3; 563 timstr = date + 12; 564 gmtstr = date + 21; 565 566 TIMEPARSE_STD(ds, timstr); 567 } 568 else 569 return APR_DATE_BAD; 570 571 if (ds.tm_mday <= 0 || ds.tm_mday > 31) 572 return APR_DATE_BAD; 573 574 if ((ds.tm_hour > 23) || (ds.tm_min > 59) || (ds.tm_sec > 61)) 575 return APR_DATE_BAD; 576 577 mint = (monstr[0] << 16) | (monstr[1] << 8) | monstr[2]; 578 for (mon = 0; mon < 12; mon++) 579 if (mint == months[mon]) 580 break; 581 582 if (mon == 12) 583 return APR_DATE_BAD; 584 585 if ((ds.tm_mday == 31) && (mon == 3 || mon == 5 || mon == 8 || mon == 10)) 586 return APR_DATE_BAD; 587 588 /* February gets special check for leapyear */ 589 590 if ((mon == 1) && 591 ((ds.tm_mday > 29) 592 || ((ds.tm_mday == 29) 593 && ((ds.tm_year & 3) 594 || (((ds.tm_year % 100) == 0) 595 && (((ds.tm_year % 400) != 100))))))) 596 return APR_DATE_BAD; 597 598 ds.tm_mon = mon; 599 600 /* tm_gmtoff is the number of seconds off of GMT the time is. 601 * 602 * We only currently support: [+-]ZZZZ where Z is the offset in 603 * hours from GMT. 604 * 605 * If there is any confusion, tm_gmtoff will remain 0. 606 */ 607 ds.tm_gmtoff = 0; 608 609 /* Do we have a timezone ? */ 610 if (gmtstr) { 611 int offset; 612 switch (*gmtstr) { 613 case '-': 614 offset = atoi(gmtstr+1); 615 ds.tm_gmtoff -= (offset / 100) * 60 * 60; 616 ds.tm_gmtoff -= (offset % 100) * 60; 617 break; 618 case '+': 619 offset = atoi(gmtstr+1); 620 ds.tm_gmtoff += (offset / 100) * 60 * 60; 621 ds.tm_gmtoff += (offset % 100) * 60; 622 break; 623 } 624 } 625 626 /* apr_time_exp_get uses tm_usec field, but it hasn't been set yet. 627 * It should be safe to just zero out this value. 628 * tm_usec is the number of microseconds into the second. HTTP only 629 * cares about second granularity. 630 */ 631 ds.tm_usec = 0; 632 633 if (apr_time_exp_gmt_get(&result, &ds) != APR_SUCCESS) 634 return APR_DATE_BAD; 635 636 return result; 637} 638