1 /******************************************************************************* 2 3 Functions to format time strings. 4 5 Copyright: 6 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 *******************************************************************************/ 15 16 module ocean.text.util.Time; 17 18 19 20 21 import core.sys.posix.time : gmtime_r; 22 23 import ocean.meta.types.Qualifiers; 24 25 import ocean.core.Enforce; 26 27 import ocean.core.Array : copy; 28 29 import ocean.core.Verify; 30 31 import core.stdc.time : strftime, time_t, tm; 32 33 import ocean.text.convert.Formatter; 34 35 36 37 /******************************************************************************* 38 39 Formats the given UNIX timestamp into the form specified by the format 40 string. If no format string is given, and the length of the destination 41 output buffer has been set to at least 20 characters, then the timestamp 42 will be formatted into the form `1982-09-15 10:37:29`. 43 44 Refer to the manual page of `strftime` for the various conversion 45 specifications that can be used to construct the format string. 46 47 This function should be preferred when formatting the time into a static 48 array. 49 50 Params: 51 timestamp = timestamp to format 52 output = slice to destination string buffer, must be long enough to 53 contain the resulting string post-conversion 54 format_string = format string to define how the timestamp should be 55 formatted, must be null-terminated (defaults to "%F %T\0") 56 note that the format string requires an explicit '\0' even if string 57 literals are being passed 58 59 Returns: 60 slice to the string formatted in 'output', may be an empty slice if the 61 provided buffer is too small or if an error occurred 62 63 *******************************************************************************/ 64 65 public mstring formatTime ( time_t timestamp, mstring output, 66 cstring format_string = "%F %T\0" ) 67 { 68 verify(!format_string[$ - 1], "Format string must be null-terminated"); 69 70 tm time; 71 size_t len; 72 73 if ( gmtime_r(×tamp, &time) ) 74 { 75 len = strftime(output.ptr, output.length, format_string.ptr, &time); 76 } 77 78 return output[0 .. len]; 79 } 80 81 82 /******************************************************************************* 83 84 Formats the given UNIX timestamp into the form specified by the format 85 string. If no format string is given, then the timestamp will be formatted 86 into the form `1982-09-15 10:37:29`. 87 88 Refer to the manual page of `strftime` for the various conversion 89 specifications that can be used to construct the format string. 90 91 This function should be preferred when formatting the time into a dynamic 92 array. 93 94 Params: 95 timestamp = timestamp to format 96 output = slice to destination string buffer 97 format_string = format string to define how the timestamp should be 98 formatted, must be null-terminated (defaults to "%F %T\0") 99 note that the format string requires an explicit '\0' even if string 100 literals are being passed 101 max_output_len = maximum length of the resulting string post-conversion 102 (defaults to 50) 103 104 Returns: 105 the formatted string, may be an empty slice if the result exceeds the 106 maximum output length specified or if an error occurred 107 108 *******************************************************************************/ 109 110 public mstring formatTimeRef ( time_t timestamp, ref mstring output, 111 cstring format_string = "%F %T\0", uint max_output_len = 50 ) 112 { 113 output.length = max_output_len; 114 assumeSafeAppend(output); 115 116 output.length = formatTime(timestamp, output, format_string).length; 117 assumeSafeAppend(output); 118 119 return output; 120 } 121 122 123 /******************************************************************************* 124 125 Formats a string with the number of years, days, hours, minutes & seconds 126 specified. 127 128 Params: 129 s = number of seconds elapsed 130 output = destination string buffer 131 132 Returns: 133 formatted string 134 135 *******************************************************************************/ 136 137 public mstring formatDuration ( ulong s, ref mstring output ) 138 { 139 output.length = 0; 140 assumeSafeAppend(output); 141 142 bool comma = false; 143 144 /*************************************************************************** 145 146 Appends the count of the specified value to the output string, if the 147 value is > 0. Also appends a comma first, if this is not the first value 148 to be appended to the output string. In this way, a comma-separated list 149 of values is built up over multiple calls to this function. 150 151 Params: 152 number = value to append 153 name = name of quantity 154 155 ***************************************************************************/ 156 157 void append ( ulong number, cstring name ) 158 { 159 if ( number > 0 ) 160 { 161 if ( comma ) output ~= ", "; 162 sformat(output, "{} {}{}", number, name, 163 number > 1 ? "s" : ""); 164 comma = true; 165 } 166 } 167 168 if ( s == 0 ) 169 { 170 output.copy("0 seconds"); 171 } 172 else 173 { 174 uint years, days, hours, minutes, seconds; 175 extractTimePeriods(s, years, days, hours, minutes, seconds); 176 177 append(years, "year"); 178 append(days, "day"); 179 append(hours, "hour"); 180 append(minutes, "minute"); 181 append(seconds, "second"); 182 } 183 184 return output; 185 } 186 187 188 /******************************************************************************* 189 190 Formats a string with the number of years, days, hours, minutes & seconds 191 specified. The string is formatted with short names for the time periods 192 (e.g. 's' instead of 'seconds'). 193 194 Params: 195 s = number of seconds elapsed 196 output = destination string buffer 197 198 Returns: 199 formatted string 200 201 *******************************************************************************/ 202 203 public mstring formatDurationShort ( ulong s, ref mstring output ) 204 { 205 output.length = 0; 206 assumeSafeAppend(output); 207 208 /*************************************************************************** 209 210 Appends the count of the specified value to the output string, if the 211 value is > 0. Also appends a comma first, if this is not the first value 212 to be appended to the output string. In this way, a comma-separated list 213 of values is built up over multiple calls to this function. 214 215 Params: 216 number = value to append 217 name = name of quantity 218 219 ***************************************************************************/ 220 221 void append ( ulong number, cstring name ) 222 { 223 if ( number > 0 ) 224 { 225 sformat(output, "{}{}", number, name); 226 } 227 } 228 229 if ( s == 0 ) 230 { 231 output.copy("0s"); 232 } 233 else 234 { 235 uint years, days, hours, minutes, seconds; 236 extractTimePeriods(s, years, days, hours, minutes, seconds); 237 238 append(years, "y"); 239 append(days, "d"); 240 append(hours, "h"); 241 append(minutes, "m"); 242 append(seconds, "s"); 243 } 244 245 return output; 246 } 247 248 249 /******************************************************************************* 250 251 Works out the number of multiples of various timespans (years, days, hours, 252 minutes, seconds) in the provided total count of seconds, breaking the 253 seconds count down into constituent parts. 254 255 Params: 256 s = total seconds count to extract timespans from 257 years = receives the extracted count of years in s 258 days = receives the extracted count of days in s 259 hours = receives the extracted count of hours in s 260 minutes = receives the extracted count of minutes in s 261 seconds = receives the remaining seconds after all other timespans have 262 been extracted from s 263 264 *******************************************************************************/ 265 266 public void extractTimePeriods ( ulong s, out uint years, out uint days, 267 out uint hours, out uint minutes, out uint seconds ) 268 { 269 /*************************************************************************** 270 271 Works out the number of multiples of the specified timespan in the total 272 count of seconds, and reduces the seconds count by these multiples. In 273 this way, when this function is called multiple times with decreasingly 274 large timespans, the seconds count can be broken down into constituent 275 parts. 276 277 Params: 278 timespan = number of seconds in timespan to extract 279 280 Returns: 281 number of timespans in seconds 282 283 ***************************************************************************/ 284 285 uint extract ( ulong timespan ) 286 { 287 auto extracted = seconds / timespan; 288 seconds -= extracted * timespan; 289 return cast(uint) extracted; 290 } 291 292 static immutable minute_timespan = 60; 293 static immutable hour_timespan = minute_timespan * 60; 294 static immutable day_timespan = hour_timespan * 24; 295 static immutable year_timespan = day_timespan * 365; 296 297 enforce(s <= uint.max); 298 seconds = cast(uint) s; 299 300 years = extract(year_timespan); 301 days = extract(day_timespan); 302 hours = extract(hour_timespan); 303 minutes = extract(minute_timespan); 304 } 305 306 307 version (unittest) 308 { 309 import ocean.core.Test; 310 } 311 312 /// 313 unittest 314 { 315 time_t timestamp = 400934249; 316 char[20] static_str; 317 char[50] big_static_str; 318 319 test!("==")(formatTime(timestamp, static_str), "1982-09-15 10:37:29"); 320 321 // An empty string is returned if the buffer is not large enough 322 test!("==")(formatTime(timestamp, static_str, "%A, %d %B %Y %T\0"), ""); 323 324 test!("==")(formatTime(timestamp, big_static_str, "%A, %d %B %Y %T\0"), 325 "Wednesday, 15 September 1982 10:37:29"); 326 327 mstring buf; 328 329 formatTimeRef(timestamp, buf); 330 test!("==")(buf, "1982-09-15 10:37:29"); 331 332 formatTimeRef(timestamp, buf, "%A, %d %B %Y %T\0"); 333 test!("==")(buf, "Wednesday, 15 September 1982 10:37:29"); 334 335 // An empty string is returned if the resulting string is longer than the 336 // maximum length 337 formatTimeRef(timestamp, buf, 338 "%d/%m/%y, but Americans would write that as %D\0"); 339 test!("==")(buf, ""); 340 341 // A larger maximum length can be set if necessary 342 formatTimeRef(timestamp, buf, 343 "%d/%m/%y, but Americans would write that as %D\0", 100); 344 test!("==")(buf, "15/09/82, but Americans would write that as 09/15/82"); 345 346 mstring str; 347 uint seconds = 94523; 348 349 formatDuration(seconds, str); 350 test!("==")(str, "1 day, 2 hours, 15 minutes, 23 seconds"); 351 352 formatDurationShort(seconds, str); 353 test!("==")(str, "1d2h15m23s"); 354 355 uint years, days, hours, minutes; 356 357 extractTimePeriods(100000000, years, days, hours, minutes, seconds); 358 test!("==")(years, 3); 359 test!("==")(days, 62); 360 test!("==")(hours, 9); 361 test!("==")(minutes, 46); 362 test!("==")(seconds, 40); 363 }