github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/common/format.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package common 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 ) 12 13 // LastConnection turns the *time.Time returned from the API server 14 // into a user facing string with either exact time or a user friendly 15 // string based on the args. 16 func LastConnection(connectionTime *time.Time, now time.Time, exact bool) string { 17 if connectionTime == nil { 18 return "never connected" 19 } 20 if exact { 21 return connectionTime.String() 22 } 23 return UserFriendlyDuration(*connectionTime, now) 24 } 25 26 // UserFriendlyDuration translates a time in the past into a user 27 // friendly string representation relative to the "now" time argument. 28 func UserFriendlyDuration(when, now time.Time) string { 29 since := now.Sub(when) 30 // if over 24 hours ago, just say the date. 31 if since.Hours() >= 24 { 32 return when.Format("2006-01-02") 33 } 34 if since.Hours() >= 1 { 35 unit := "hours" 36 if int(since.Hours()) == 1 { 37 unit = "hour" 38 } 39 return fmt.Sprintf("%d %s ago", int(since.Hours()), unit) 40 } 41 if since.Minutes() >= 1 { 42 unit := "minutes" 43 if int(since.Minutes()) == 1 { 44 unit = "minute" 45 } 46 return fmt.Sprintf("%d %s ago", int(since.Minutes()), unit) 47 } 48 if since.Seconds() >= 2 { 49 return fmt.Sprintf("%d seconds ago", int(since.Seconds())) 50 } 51 return "just now" 52 } 53 54 // FormatTime returns a string with the local time formatted 55 // in an arbitrary format used for status or and localized tz 56 // or in UTC timezone and format RFC3339 if u is specified. 57 func FormatTime(t *time.Time, formatISO bool) string { 58 if formatISO { 59 // If requested, use ISO time format. 60 // The format we use is RFC3339 without the "T". From the spec: 61 // NOTE: ISO 8601 defines date and time separated by "T". 62 // Applications using this syntax may choose, for the sake of 63 // readability, to specify a full-date and full-time separated by 64 // (say) a space character. 65 return t.UTC().Format("2006-01-02 15:04:05Z") 66 } 67 // Otherwise use local time. 68 return t.Local().Format("02 Jan 2006 15:04:05Z07:00") 69 } 70 71 // FormatTimeAsTimestamp formats a time.Time to a given machine readable format. 72 // Returns a string with the local time formatted or uses the localized tz or 73 // in UTC timezone. 74 // The difference between this and `common.FormatTime` is that this version 75 // drops the date part of the time.Time. 76 func FormatTimeAsTimestamp(t *time.Time, formatISO bool) string { 77 if formatISO { 78 return t.UTC().Format("15:04:05") 79 } 80 return t.Local().Format("15:04:05Z07:00") 81 } 82 83 // ConformYAML ensures all keys of any nested maps are strings. This is 84 // necessary because YAML unmarshals map[interface{}]interface{} in nested 85 // maps, which cannot be serialized by bson. Also, handle []interface{}. 86 // cf. gopkg.in/juju/charm.v4/actions.go cleanse 87 func ConformYAML(input interface{}) (interface{}, error) { 88 switch typedInput := input.(type) { 89 90 case map[string]interface{}: 91 newMap := make(map[string]interface{}) 92 for key, value := range typedInput { 93 newValue, err := ConformYAML(value) 94 if err != nil { 95 return nil, err 96 } 97 newMap[key] = newValue 98 } 99 return newMap, nil 100 101 case map[interface{}]interface{}: 102 newMap := make(map[string]interface{}) 103 for key, value := range typedInput { 104 typedKey, ok := key.(string) 105 if !ok { 106 return nil, errors.New("map keyed with non-string value") 107 } 108 newMap[typedKey] = value 109 } 110 return ConformYAML(newMap) 111 112 case []interface{}: 113 newSlice := make([]interface{}, len(typedInput)) 114 for i, sliceValue := range typedInput { 115 newSliceValue, err := ConformYAML(sliceValue) 116 if err != nil { 117 return nil, errors.New("map keyed with non-string value") 118 } 119 newSlice[i] = newSliceValue 120 } 121 return newSlice, nil 122 123 default: 124 return input, nil 125 } 126 }