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