go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/tools/time_utils/time_utils.ts (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import { Duration } from 'luxon'; 16 17 import { Duration as ProtoDuration } from '@/proto/google/protobuf/duration.pb'; 18 19 export const SHORT_TIME_FORMAT = 'y-MM-dd HH:mm'; 20 export const LONG_TIME_FORMAT = 'HH:mm:ss ccc, MMM dd yyyy ZZZZ'; 21 export const NUMERIC_TIME_FORMAT = 'y-MM-dd HH:mm:ss ZZ'; 22 23 export function displayDuration(duration: Duration) { 24 const shifted = duration.shiftTo( 25 'days', 26 'hours', 27 'minutes', 28 'seconds', 29 'milliseconds', 30 ); 31 const parts = []; 32 if (shifted.days >= 1) { 33 parts.push(shifted.days + ' ' + (shifted.days === 1 ? 'day' : 'days')); 34 } 35 if (shifted.hours >= 1) { 36 parts.push(shifted.hours + ' ' + (shifted.hours === 1 ? 'hour' : 'hours')); 37 } 38 // We only care about minutes if days and hours are not both present 39 if (shifted.minutes >= 1 && parts.length <= 1) { 40 parts.push( 41 shifted.minutes + ' ' + (shifted.minutes === 1 ? 'min' : 'mins'), 42 ); 43 } 44 // We only care about seconds if it is significant enough 45 if (shifted.seconds >= 1 && parts.length <= 1) { 46 parts.push( 47 shifted.seconds + ' ' + (shifted.seconds === 1 ? 'sec' : 'secs'), 48 ); 49 } 50 // We only care about ms if there are no other part 51 if (parts.length === 0) { 52 parts.push(Math.floor(shifted.milliseconds) + ' ms'); 53 } 54 return parts.join(' '); 55 } 56 57 export function displayCompactDuration( 58 duration: Duration | null, 59 ): [string, string] { 60 if (duration === null) { 61 return ['N/A', '']; 62 } 63 64 const shifted = duration.shiftTo( 65 'days', 66 'hours', 67 'minutes', 68 'seconds', 69 'milliseconds', 70 ); 71 if (shifted.days >= 1) { 72 return [`${(shifted.days + shifted.hours / 24).toFixed(1)}d`, 'd']; 73 } 74 if (shifted.hours >= 1) { 75 return [`${(shifted.hours + shifted.minutes / 60).toPrecision(2)}h`, 'h']; 76 } 77 if (shifted.minutes >= 1) { 78 return [`${(shifted.minutes + shifted.seconds / 60).toPrecision(2)}m`, 'm']; 79 } 80 81 // Unlike other units, shifted.milliseconds may not be an integer. 82 // shifted.milliseconds.toFixed(0) may give us 1000 when 83 // shifted.milliseconds >= 999.5. We should display it as 1.0s in that case. 84 if (shifted.seconds >= 1 || shifted.milliseconds >= 999.5) { 85 return [ 86 `${(shifted.seconds + shifted.milliseconds / 1000).toPrecision(2)}s`, 87 's', 88 ]; 89 } 90 return [`${shifted.milliseconds.toFixed(0)}ms`, 'ms']; 91 } 92 93 /* eslint-disable max-len */ 94 /** 95 * Parses the JSON encoding of google.protobuf.Duration (e.g. '4.5s'). 96 * https://github.com/protocolbuffers/protobuf/blob/68cb69ea68822d96eee6d6104463edf85e70d689/src/google/protobuf/duration.proto#L92 97 * 98 * Returns the number of milliseconds. 99 */ 100 /* eslint-disable max-len */ 101 export function parseProtoDurationStr(duration: string): Duration { 102 return Duration.fromObject({ 103 second: Number(duration.substring(0, duration.length - 1)), 104 }); 105 } 106 107 /** 108 * Converts a google.protobuf.Duration object to a luxon Duration object. 109 */ 110 export function parseProtoDuration(duration: ProtoDuration): Duration { 111 return Duration.fromObject({ 112 second: Number(duration.seconds), 113 millisecond: duration.nanos / 1000000, 114 }); 115 }