1251881Speter/* 2251881Speter * time.c: time/date utilities 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#include <string.h> 27251881Speter#include <stdlib.h> 28251881Speter#include <apr_pools.h> 29251881Speter#include <apr_time.h> 30251881Speter#include <apr_strings.h> 31251881Speter#include "svn_io.h" 32251881Speter#include "svn_time.h" 33251881Speter#include "svn_utf.h" 34251881Speter#include "svn_error.h" 35251881Speter#include "svn_private_config.h" 36251881Speter 37299742Sdim#include "private/svn_string_private.h" 38251881Speter 39299742Sdim 40251881Speter 41251881Speter/*** Code. ***/ 42251881Speter 43251881Speter/* Our timestamp strings look like this: 44251881Speter * 45251881Speter * "2002-05-07Thh:mm:ss.uuuuuuZ" 46251881Speter * 47251881Speter * The format is conformant with ISO-8601 and the date format required 48251881Speter * by RFC2518 for creationdate. It is a direct conversion between 49251881Speter * apr_time_t and a string, so converting to string and back retains 50251881Speter * the exact value. 51251881Speter */ 52251881Speter#define TIMESTAMP_FORMAT "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ" 53251881Speter 54251881Speter/* Our old timestamp strings looked like this: 55251881Speter * 56251881Speter * "Tue 3 Oct 2000 HH:MM:SS.UUU (day 277, dst 1, gmt_off -18000)" 57251881Speter * 58251881Speter * The idea is that they are conventionally human-readable for the 59251881Speter * first part, and then in parentheses comes everything else required 60251881Speter * to completely fill in an apr_time_exp_t: tm_yday, tm_isdst, 61251881Speter * and tm_gmtoff. 62251881Speter * 63251881Speter * This format is still recognized on input, for backward 64251881Speter * compatibility, but no longer generated. 65251881Speter */ 66251881Speter#define OLD_TIMESTAMP_FORMAT \ 67251881Speter "%3s %d %3s %d %02d:%02d:%02d.%06d (day %03d, dst %d, gmt_off %06d)" 68251881Speter 69251881Speter/* Our human representation of dates looks like this: 70251881Speter * 71251881Speter * "2002-06-23 11:13:02 +0300 (Sun, 23 Jun 2002)" 72251881Speter * 73251881Speter * This format is used whenever time is shown to the user. It consists 74251881Speter * of a machine parseable, almost ISO-8601, part in the beginning - 75251881Speter * and a human explanatory part at the end. The machine parseable part 76251881Speter * is generated strictly by APR and our code, with a apr_snprintf. The 77251881Speter * human explanatory part is generated by apr_strftime, which means 78251881Speter * that its generation can be affected by locale, it can fail and it 79251881Speter * doesn't need to be constant in size. In other words, perfect to be 80251881Speter * converted to a configuration option later on. 81251881Speter */ 82251881Speter/* Maximum length for the date string. */ 83251881Speter#define SVN_TIME__MAX_LENGTH 80 84251881Speter/* Machine parseable part, generated by apr_snprintf. */ 85251881Speter#define HUMAN_TIMESTAMP_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %+.2d%.2d" 86251881Speter/* Human explanatory part, generated by apr_strftime as "Sat, 01 Jan 2000" */ 87299742Sdim#define HUMAN_TIMESTAMP_FORMAT_SUFFIX _(" (%a, %d %b %Y)") 88251881Speter 89251881Speterconst char * 90251881Spetersvn_time_to_cstring(apr_time_t when, apr_pool_t *pool) 91251881Speter{ 92251881Speter apr_time_exp_t exploded_time; 93251881Speter 94251881Speter /* We toss apr_status_t return value here -- for one thing, caller 95251881Speter should pass in good information. But also, where APR's own code 96251881Speter calls these functions it tosses the return values, and 97251881Speter furthermore their current implementations can only return success 98251881Speter anyway. */ 99251881Speter 100251881Speter /* We get the date in GMT now -- and expect the tm_gmtoff and 101251881Speter tm_isdst to be not set. We also ignore the weekday and yearday, 102251881Speter since those are not needed. */ 103251881Speter 104251881Speter apr_time_exp_gmt(&exploded_time, when); 105251881Speter 106251881Speter /* It would be nice to use apr_strftime(), but APR doesn't give a 107251881Speter way to convert back, so we wouldn't be able to share the format 108251881Speter string between the writer and reader. */ 109251881Speter return apr_psprintf(pool, 110251881Speter TIMESTAMP_FORMAT, 111251881Speter exploded_time.tm_year + 1900, 112251881Speter exploded_time.tm_mon + 1, 113251881Speter exploded_time.tm_mday, 114251881Speter exploded_time.tm_hour, 115251881Speter exploded_time.tm_min, 116251881Speter exploded_time.tm_sec, 117251881Speter exploded_time.tm_usec); 118251881Speter} 119251881Speter 120251881Speter 121251881Speterstatic apr_int32_t 122251881Speterfind_matching_string(char *str, apr_size_t size, const char strings[][4]) 123251881Speter{ 124251881Speter apr_size_t i; 125251881Speter 126251881Speter for (i = 0; i < size; i++) 127251881Speter if (strings[i] && (strcmp(str, strings[i]) == 0)) 128251881Speter return (apr_int32_t) i; 129251881Speter 130251881Speter return -1; 131251881Speter} 132251881Speter 133251881Speter 134251881Spetersvn_error_t * 135251881Spetersvn_time_from_cstring(apr_time_t *when, const char *data, apr_pool_t *pool) 136251881Speter{ 137251881Speter apr_time_exp_t exploded_time; 138251881Speter apr_status_t apr_err; 139251881Speter char wday[4], month[4]; 140299742Sdim const char *c; 141251881Speter 142251881Speter /* Open-code parsing of the new timestamp format, as this 143251881Speter is a hot path for reading the entries file. This format looks 144251881Speter like: "2001-08-31T04:24:14.966996Z" */ 145299742Sdim exploded_time.tm_year = (apr_int32_t) svn__strtoul(data, &c); 146251881Speter if (*c++ != '-') goto fail; 147299742Sdim exploded_time.tm_mon = (apr_int32_t) svn__strtoul(c, &c); 148251881Speter if (*c++ != '-') goto fail; 149299742Sdim exploded_time.tm_mday = (apr_int32_t) svn__strtoul(c, &c); 150251881Speter if (*c++ != 'T') goto fail; 151299742Sdim exploded_time.tm_hour = (apr_int32_t) svn__strtoul(c, &c); 152251881Speter if (*c++ != ':') goto fail; 153299742Sdim exploded_time.tm_min = (apr_int32_t) svn__strtoul(c, &c); 154251881Speter if (*c++ != ':') goto fail; 155299742Sdim exploded_time.tm_sec = (apr_int32_t) svn__strtoul(c, &c); 156251881Speter if (*c++ != '.') goto fail; 157299742Sdim exploded_time.tm_usec = (apr_int32_t) svn__strtoul(c, &c); 158251881Speter if (*c++ != 'Z') goto fail; 159251881Speter 160251881Speter exploded_time.tm_year -= 1900; 161251881Speter exploded_time.tm_mon -= 1; 162251881Speter exploded_time.tm_wday = 0; 163251881Speter exploded_time.tm_yday = 0; 164251881Speter exploded_time.tm_isdst = 0; 165251881Speter exploded_time.tm_gmtoff = 0; 166251881Speter 167251881Speter apr_err = apr_time_exp_gmt_get(when, &exploded_time); 168251881Speter if (apr_err == APR_SUCCESS) 169251881Speter return SVN_NO_ERROR; 170251881Speter 171251881Speter return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); 172251881Speter 173251881Speter fail: 174251881Speter /* Try the compatibility option. This does not need to be fast, 175251881Speter as this format is no longer generated and the client will convert 176251881Speter an old-format entries file the first time it reads it. */ 177251881Speter if (sscanf(data, 178251881Speter OLD_TIMESTAMP_FORMAT, 179251881Speter wday, 180251881Speter &exploded_time.tm_mday, 181251881Speter month, 182251881Speter &exploded_time.tm_year, 183251881Speter &exploded_time.tm_hour, 184251881Speter &exploded_time.tm_min, 185251881Speter &exploded_time.tm_sec, 186251881Speter &exploded_time.tm_usec, 187251881Speter &exploded_time.tm_yday, 188251881Speter &exploded_time.tm_isdst, 189251881Speter &exploded_time.tm_gmtoff) == 11) 190251881Speter { 191251881Speter exploded_time.tm_year -= 1900; 192251881Speter exploded_time.tm_yday -= 1; 193251881Speter /* Using hard coded limits for the arrays - they are going away 194251881Speter soon in any case. */ 195251881Speter exploded_time.tm_wday = find_matching_string(wday, 7, apr_day_snames); 196251881Speter exploded_time.tm_mon = find_matching_string(month, 12, apr_month_snames); 197251881Speter 198251881Speter apr_err = apr_time_exp_gmt_get(when, &exploded_time); 199251881Speter if (apr_err != APR_SUCCESS) 200251881Speter return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); 201251881Speter 202251881Speter return SVN_NO_ERROR; 203251881Speter } 204251881Speter /* Timestamp is something we do not recognize. */ 205251881Speter else 206251881Speter return svn_error_create(SVN_ERR_BAD_DATE, NULL, NULL); 207251881Speter} 208251881Speter 209251881Speter 210251881Speterconst char * 211251881Spetersvn_time_to_human_cstring(apr_time_t when, apr_pool_t *pool) 212251881Speter{ 213251881Speter apr_time_exp_t exploded_time; 214251881Speter apr_size_t len, retlen; 215251881Speter apr_status_t ret; 216251881Speter char *datestr, *curptr, human_datestr[SVN_TIME__MAX_LENGTH]; 217251881Speter 218251881Speter /* Get the time into parts */ 219251881Speter ret = apr_time_exp_lt(&exploded_time, when); 220251881Speter if (ret) 221251881Speter return NULL; 222251881Speter 223251881Speter /* Make room for datestring */ 224251881Speter datestr = apr_palloc(pool, SVN_TIME__MAX_LENGTH); 225251881Speter 226251881Speter /* Put in machine parseable part */ 227251881Speter len = apr_snprintf(datestr, 228251881Speter SVN_TIME__MAX_LENGTH, 229251881Speter HUMAN_TIMESTAMP_FORMAT, 230251881Speter exploded_time.tm_year + 1900, 231251881Speter exploded_time.tm_mon + 1, 232251881Speter exploded_time.tm_mday, 233251881Speter exploded_time.tm_hour, 234251881Speter exploded_time.tm_min, 235251881Speter exploded_time.tm_sec, 236251881Speter exploded_time.tm_gmtoff / (60 * 60), 237251881Speter (abs(exploded_time.tm_gmtoff) / 60) % 60); 238251881Speter 239251881Speter /* If we overfilled the buffer, just return what we got. */ 240251881Speter if (len >= SVN_TIME__MAX_LENGTH) 241251881Speter return datestr; 242251881Speter 243251881Speter /* Calculate offset to the end of the machine parseable part. */ 244251881Speter curptr = datestr + len; 245251881Speter 246251881Speter /* Put in human explanatory part */ 247251881Speter ret = apr_strftime(human_datestr, 248251881Speter &retlen, 249251881Speter SVN_TIME__MAX_LENGTH - len, 250299742Sdim HUMAN_TIMESTAMP_FORMAT_SUFFIX, 251251881Speter &exploded_time); 252251881Speter 253251881Speter /* If there was an error, ensure that the string is zero-terminated. */ 254251881Speter if (ret || retlen == 0) 255251881Speter *curptr = '\0'; 256251881Speter else 257251881Speter { 258251881Speter const char *utf8_string; 259251881Speter svn_error_t *err; 260251881Speter 261251881Speter err = svn_utf_cstring_to_utf8(&utf8_string, human_datestr, pool); 262251881Speter if (err) 263251881Speter { 264251881Speter *curptr = '\0'; 265251881Speter svn_error_clear(err); 266251881Speter } 267251881Speter else 268251881Speter apr_cpystrn(curptr, utf8_string, SVN_TIME__MAX_LENGTH - len); 269251881Speter } 270251881Speter 271251881Speter return datestr; 272251881Speter} 273251881Speter 274251881Speter 275251881Spetervoid 276251881Spetersvn_sleep_for_timestamps(void) 277251881Speter{ 278251881Speter svn_io_sleep_for_timestamps(NULL, NULL); 279251881Speter} 280