1/* 2 * time.c: time/date utilities 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#include <string.h> 27#include <stdlib.h> 28#include <apr_pools.h> 29#include <apr_time.h> 30#include <apr_strings.h> 31#include "svn_io.h" 32#include "svn_time.h" 33#include "svn_utf.h" 34#include "svn_error.h" 35#include "svn_private_config.h" 36 37#include "private/svn_string_private.h" 38 39 40 41/*** Code. ***/ 42 43/* Our timestamp strings look like this: 44 * 45 * "2002-05-07Thh:mm:ss.uuuuuuZ" 46 * 47 * The format is conformant with ISO-8601 and the date format required 48 * by RFC2518 for creationdate. It is a direct conversion between 49 * apr_time_t and a string, so converting to string and back retains 50 * the exact value. 51 */ 52#define TIMESTAMP_FORMAT "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ" 53 54/* Our old timestamp strings looked like this: 55 * 56 * "Tue 3 Oct 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)" 57 * 58 * The idea is that they are conventionally human-readable for the 59 * first part, and then in parentheses comes everything else required 60 * to completely fill in an apr_time_exp_t: tm_yday, tm_isdst, 61 * and tm_gmtoff. 62 * 63 * This format is still recognized on input, for backward 64 * compatibility, but no longer generated. 65 */ 66#define OLD_TIMESTAMP_FORMAT \ 67 "%3s %d %3s %d %02d:%02d:%02d.%06d (day %03d, dst %d, gmt_off %06d)" 68 69/* Our human representation of dates looks like this: 70 * 71 * "2002-06-23 11:13:02 +0300 (Sun, 23 Jun 2002)" 72 * 73 * This format is used whenever time is shown to the user. It consists 74 * of a machine parseable, almost ISO-8601, part in the beginning - 75 * and a human explanatory part at the end. The machine parseable part 76 * is generated strictly by APR and our code, with a apr_snprintf. The 77 * human explanatory part is generated by apr_strftime, which means 78 * that its generation can be affected by locale, it can fail and it 79 * doesn't need to be constant in size. In other words, perfect to be 80 * converted to a configuration option later on. 81 */ 82/* Maximum length for the date string. */ 83#define SVN_TIME__MAX_LENGTH 80 84/* Machine parseable part, generated by apr_snprintf. */ 85#define HUMAN_TIMESTAMP_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %+.2d%.2d" 86/* Human explanatory part, generated by apr_strftime as "Sat, 01 Jan 2000" */ 87#define HUMAN_TIMESTAMP_FORMAT_SUFFIX _(" (%a, %d %b %Y)") 88 89const char * 90svn_time_to_cstring(apr_time_t when, apr_pool_t *pool) 91{ 92 apr_time_exp_t exploded_time; 93 94 /* We toss apr_status_t return value here -- for one thing, caller 95 should pass in good information. But also, where APR's own code 96 calls these functions it tosses the return values, and 97 furthermore their current implementations can only return success 98 anyway. */ 99 100 /* We get the date in GMT now -- and expect the tm_gmtoff and 101 tm_isdst to be not set. We also ignore the weekday and yearday, 102 since those are not needed. */ 103 104 apr_time_exp_gmt(&exploded_time, when); 105 106 /* It would be nice to use apr_strftime(), but APR doesn't give a 107 way to convert back, so we wouldn't be able to share the format 108 string between the writer and reader. */ 109 return apr_psprintf(pool, 110 TIMESTAMP_FORMAT, 111 exploded_time.tm_year + 1900, 112 exploded_time.tm_mon + 1, 113 exploded_time.tm_mday, 114 exploded_time.tm_hour, 115 exploded_time.tm_min, 116 exploded_time.tm_sec, 117 exploded_time.tm_usec); 118} 119 120 121static apr_int32_t 122find_matching_string(char *str, apr_size_t size, const char strings[][4]) 123{ 124 apr_size_t i; 125 126 for (i = 0; i < size; i++) 127 if (strings[i] && (strcmp(str, strings[i]) == 0)) 128 return (apr_int32_t) i; 129 130 return -1; 131} 132 133 134svn_error_t * 135svn_time_from_cstring(apr_time_t *when, const char *data, apr_pool_t *pool) 136{ 137 apr_time_exp_t exploded_time; 138 apr_status_t apr_err; 139 char wday[4], month[4]; 140 const char *c; 141 142 /* Open-code parsing of the new timestamp format, as this 143 is a hot path for reading the entries file. This format looks 144 like: "2001-08-31T04:24:14.966996Z" */ 145 exploded_time.tm_year = (apr_int32_t) svn__strtoul(data, &c); 146 if (*c++ != '-') goto fail; 147 exploded_time.tm_mon = (apr_int32_t) svn__strtoul(c, &c); 148 if (*c++ != '-') goto fail; 149 exploded_time.tm_mday = (apr_int32_t) svn__strtoul(c, &c); 150 if (*c++ != 'T') goto fail; 151 exploded_time.tm_hour = (apr_int32_t) svn__strtoul(c, &c); 152 if (*c++ != ':') goto fail; 153 exploded_time.tm_min = (apr_int32_t) svn__strtoul(c, &c); 154 if (*c++ != ':') goto fail; 155 exploded_time.tm_sec = (apr_int32_t) svn__strtoul(c, &c); 156 if (*c++ != '.') goto fail; 157 exploded_time.tm_usec = (apr_int32_t) svn__strtoul(c, &c); 158 if (*c++ != 'Z') goto fail; 159 160 exploded_time.tm_year -= 1900; 161 exploded_time.tm_mon -= 1; 162 exploded_time.tm_wday = 0; 163 exploded_time.tm_yday = 0; 164 exploded_time.tm_isdst = 0; 165 exploded_time.tm_gmtoff = 0; 166 167 apr_err = apr_time_exp_gmt_get(when, &exploded_time); 168 if (apr_err == APR_SUCCESS) 169 return SVN_NO_ERROR; 170 171 return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); 172 173 fail: 174 /* Try the compatibility option. This does not need to be fast, 175 as this format is no longer generated and the client will convert 176 an old-format entries file the first time it reads it. */ 177 if (sscanf(data, 178 OLD_TIMESTAMP_FORMAT, 179 wday, 180 &exploded_time.tm_mday, 181 month, 182 &exploded_time.tm_year, 183 &exploded_time.tm_hour, 184 &exploded_time.tm_min, 185 &exploded_time.tm_sec, 186 &exploded_time.tm_usec, 187 &exploded_time.tm_yday, 188 &exploded_time.tm_isdst, 189 &exploded_time.tm_gmtoff) == 11) 190 { 191 exploded_time.tm_year -= 1900; 192 exploded_time.tm_yday -= 1; 193 /* Using hard coded limits for the arrays - they are going away 194 soon in any case. */ 195 exploded_time.tm_wday = find_matching_string(wday, 7, apr_day_snames); 196 exploded_time.tm_mon = find_matching_string(month, 12, apr_month_snames); 197 198 apr_err = apr_time_exp_gmt_get(when, &exploded_time); 199 if (apr_err != APR_SUCCESS) 200 return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); 201 202 return SVN_NO_ERROR; 203 } 204 /* Timestamp is something we do not recognize. */ 205 else 206 return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); 207} 208 209 210const char * 211svn_time_to_human_cstring(apr_time_t when, apr_pool_t *pool) 212{ 213 apr_time_exp_t exploded_time; 214 apr_size_t len, retlen; 215 apr_status_t ret; 216 char *datestr, *curptr, human_datestr[SVN_TIME__MAX_LENGTH]; 217 218 /* Get the time into parts */ 219 ret = apr_time_exp_lt(&exploded_time, when); 220 if (ret) 221 return NULL; 222 223 /* Make room for datestring */ 224 datestr = apr_palloc(pool, SVN_TIME__MAX_LENGTH); 225 226 /* Put in machine parseable part */ 227 len = apr_snprintf(datestr, 228 SVN_TIME__MAX_LENGTH, 229 HUMAN_TIMESTAMP_FORMAT, 230 exploded_time.tm_year + 1900, 231 exploded_time.tm_mon + 1, 232 exploded_time.tm_mday, 233 exploded_time.tm_hour, 234 exploded_time.tm_min, 235 exploded_time.tm_sec, 236 exploded_time.tm_gmtoff / (60 * 60), 237 (abs(exploded_time.tm_gmtoff) / 60) % 60); 238 239 /* If we overfilled the buffer, just return what we got. */ 240 if (len >= SVN_TIME__MAX_LENGTH) 241 return datestr; 242 243 /* Calculate offset to the end of the machine parseable part. */ 244 curptr = datestr + len; 245 246 /* Put in human explanatory part */ 247 ret = apr_strftime(human_datestr, 248 &retlen, 249 SVN_TIME__MAX_LENGTH - len, 250 HUMAN_TIMESTAMP_FORMAT_SUFFIX, 251 &exploded_time); 252 253 /* If there was an error, ensure that the string is zero-terminated. */ 254 if (ret || retlen == 0) 255 *curptr = '\0'; 256 else 257 { 258 const char *utf8_string; 259 svn_error_t *err; 260 261 err = svn_utf_cstring_to_utf8(&utf8_string, human_datestr, pool); 262 if (err) 263 { 264 *curptr = '\0'; 265 svn_error_clear(err); 266 } 267 else 268 apr_cpystrn(curptr, utf8_string, SVN_TIME__MAX_LENGTH - len); 269 } 270 271 return datestr; 272} 273 274 275void 276svn_sleep_for_timestamps(void) 277{ 278 svn_io_sleep_for_timestamps(NULL, NULL); 279} 280