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