@@ -269,6 +269,17 @@ impl Duration {
269
269
me
270
270
}
271
271
}
272
+
273
+ /// Initializes a Duration from a timezone offset
274
+ #[ must_use]
275
+ pub fn from_tz_offset ( sign : i8 , hours : i64 , minutes : i64 ) -> Self {
276
+ let dur = hours * Unit :: Hour + minutes * Unit :: Minute ;
277
+ if sign < 0 {
278
+ -dur
279
+ } else {
280
+ dur
281
+ }
282
+ }
272
283
}
273
284
274
285
#[ cfg_attr( feature = "python" , pymethods) ]
@@ -550,6 +561,47 @@ impl Duration {
550
561
}
551
562
}
552
563
564
+ /// Rounds this duration to the largest units represented in this duration.
565
+ ///
566
+ /// This is useful to provide an approximate human duration. Under the hood, this function uses `round`,
567
+ /// so the "tipping point" of the rounding is half way to the next increment of the greatest unit.
568
+ /// As shown below, one example is that 35 hours and 59 minutes rounds to 1 day, but 36 hours and 1 minute rounds
569
+ /// to 2 days because 2 days is closer to 36h 1 min than 36h 1 min is to 1 day.
570
+ ///
571
+ /// # Example
572
+ ///
573
+ /// ```
574
+ /// use hifitime::{Duration, TimeUnits};
575
+ ///
576
+ /// assert_eq!((2.hours() + 3.minutes()).approx(), 2.hours());
577
+ /// assert_eq!((24.hours() + 3.minutes()).approx(), 1.days());
578
+ /// assert_eq!((35.hours() + 59.minutes()).approx(), 1.days());
579
+ /// assert_eq!((36.hours() + 1.minutes()).approx(), 2.days());
580
+ /// assert_eq!((47.hours() + 3.minutes()).approx(), 2.days());
581
+ /// assert_eq!((49.hours() + 3.minutes()).approx(), 2.days());
582
+ /// ```
583
+ pub fn approx ( & self ) -> Self {
584
+ let ( _, days, hours, minutes, seconds, milli, us, _) = self . decompose ( ) ;
585
+
586
+ let round_to = if days > 0 {
587
+ 1 * Unit :: Day
588
+ } else if hours > 0 {
589
+ 1 * Unit :: Hour
590
+ } else if minutes > 0 {
591
+ 1 * Unit :: Minute
592
+ } else if seconds > 0 {
593
+ 1 * Unit :: Second
594
+ } else if milli > 0 {
595
+ 1 * Unit :: Millisecond
596
+ } else if us > 0 {
597
+ 1 * Unit :: Microsecond
598
+ } else {
599
+ 1 * Unit :: Nanosecond
600
+ } ;
601
+
602
+ self . round ( round_to)
603
+ }
604
+
553
605
/// Returns the minimum of the two durations.
554
606
///
555
607
/// ```
@@ -1220,6 +1272,7 @@ impl FromStr for Duration {
1220
1272
/// + ms, millisecond, milliseconds
1221
1273
/// + us, microsecond, microseconds
1222
1274
/// + ns, nanosecond, nanoseconds
1275
+ /// + `+` or `-` indicates a timezone offset
1223
1276
///
1224
1277
/// # Example
1225
1278
/// ```
@@ -1233,6 +1286,8 @@ impl FromStr for Duration {
1233
1286
/// assert_eq!(Duration::from_str("10.598 seconds").unwrap(), Unit::Second * 10.598);
1234
1287
/// assert_eq!(Duration::from_str("10.598 nanosecond").unwrap(), Unit::Nanosecond * 10.598);
1235
1288
/// assert_eq!(Duration::from_str("5 h 256 ms 1 ns").unwrap(), 5 * Unit::Hour + 256 * Unit::Millisecond + Unit::Nanosecond);
1289
+ /// assert_eq!(Duration::from_str("-01:15:30").unwrap(), -(1 * Unit::Hour + 15 * Unit::Minute + 30 * Unit::Second));
1290
+ /// assert_eq!(Duration::from_str("+3615").unwrap(), 36 * Unit::Hour + 15 * Unit::Minute);
1236
1291
/// ```
1237
1292
fn from_str ( s_in : & str ) -> Result < Self , Self :: Err > {
1238
1293
// Each part of a duration as days, hours, minutes, seconds, millisecond, microseconds, and nanoseconds
@@ -1244,6 +1299,86 @@ impl FromStr for Duration {
1244
1299
1245
1300
let s = s_in. trim ( ) ;
1246
1301
1302
+ if s. is_empty ( ) {
1303
+ return Err ( Errors :: ParseError ( ParsingErrors :: ValueError ) ) ;
1304
+ }
1305
+
1306
+ // There is at least one character, so we can unwrap this.
1307
+ if let Some ( char) = s. chars ( ) . next ( ) {
1308
+ if char == '+' || char == '-' {
1309
+ // This is a timezone offset.
1310
+ let offset_sign = if char == '-' { -1 } else { 1 } ;
1311
+
1312
+ let indexes: ( usize , usize , usize ) = ( 1 , 3 , 5 ) ;
1313
+ let colon = if s. len ( ) == 3 || s. len ( ) == 5 || s. len ( ) == 7 {
1314
+ // There is a zero or even number of separators between the hours, minutes, and seconds.
1315
+ // Only zero (or one) characters separator is supported. This will return a ValueError later if there is
1316
+ // an even but greater than one character separator.
1317
+ 0
1318
+ } else if s. len ( ) == 4 || s. len ( ) == 6 || s. len ( ) == 9 {
1319
+ // There is an odd number of characters as a separator between the hours, minutes, and seconds.
1320
+ // Only one character separator is supported. This will return a ValueError later if there is
1321
+ // an odd but greater than one character separator.
1322
+ 1
1323
+ } else {
1324
+ // This invalid
1325
+ return Err ( Errors :: ParseError ( ParsingErrors :: ValueError ) ) ;
1326
+ } ;
1327
+
1328
+ // Fetch the hours
1329
+ let hours: i64 = match lexical_core:: parse ( s[ indexes. 0 ..indexes. 1 ] . as_bytes ( ) ) {
1330
+ Ok ( val) => val,
1331
+ Err ( _) => return Err ( Errors :: ParseError ( ParsingErrors :: ValueError ) ) ,
1332
+ } ;
1333
+
1334
+ let mut minutes: i64 = 0 ;
1335
+ let mut seconds: i64 = 0 ;
1336
+
1337
+ match s. get ( indexes. 1 + colon..indexes. 2 + colon) {
1338
+ None => {
1339
+ //Do nothing, we've reached the end of the useful data.
1340
+ }
1341
+ Some ( subs) => {
1342
+ // Fetch the minutes
1343
+ match lexical_core:: parse ( subs. as_bytes ( ) ) {
1344
+ Ok ( val) => minutes = val,
1345
+ Err ( _) => return Err ( Errors :: ParseError ( ParsingErrors :: ValueError ) ) ,
1346
+ }
1347
+
1348
+ match s. get ( indexes. 2 + 2 * colon..) {
1349
+ None => {
1350
+ // Do nothing, there are no seconds inthis offset
1351
+ }
1352
+ Some ( subs) => {
1353
+ if !subs. is_empty ( ) {
1354
+ // Fetch the seconds
1355
+ match lexical_core:: parse ( subs. as_bytes ( ) ) {
1356
+ Ok ( val) => seconds = val,
1357
+ Err ( _) => {
1358
+ return Err ( Errors :: ParseError (
1359
+ ParsingErrors :: ValueError ,
1360
+ ) )
1361
+ }
1362
+ }
1363
+ }
1364
+ }
1365
+ }
1366
+ }
1367
+ }
1368
+
1369
+ // Return the constructed offset
1370
+ if offset_sign == -1 {
1371
+ return Ok ( -( hours * Unit :: Hour
1372
+ + minutes * Unit :: Minute
1373
+ + seconds * Unit :: Second ) ) ;
1374
+ } else {
1375
+ return Ok ( hours * Unit :: Hour
1376
+ + minutes * Unit :: Minute
1377
+ + seconds * Unit :: Second ) ;
1378
+ }
1379
+ }
1380
+ } ;
1381
+
1247
1382
for ( idx, char) in s. chars ( ) . enumerate ( ) {
1248
1383
if char == ' ' || idx == s. len ( ) - 1 {
1249
1384
if seeking_number {
0 commit comments