├── .gitignore ├── LICENSE ├── README.md └── durationfmt.go /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, David Scholberg 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## go-durationfmt 2 | 3 | go-durationfmt is a [Go](https://golang.org/) library that allows you to format [durations](https://golang.org/pkg/time/#Duration) according to a format string. You can specify years, months, days, hours, minutes, and seconds in the format string. 4 | 5 | See the [Duration Format](#duration-format) section for an explanation of the format string rules. 6 | 7 | See the [Caveats](#caveats) section before using this library. 8 | 9 | ### Duration Format 10 | 11 | The format string format that go-durationfmt uses is similar to that used by [Go's fmt package](https://golang.org/pkg/fmt/#hdr-Printing). The `%` character signifies that the next character is a modifier that specifies a particular duration unit. The following is the full list of modifiers supported by go-durationfmt: 12 | 13 | * `%y` - # of years 14 | * `%w` - # of weeks 15 | * `%d` - # of days 16 | * `%h` - # of hours 17 | * `%m` - # of minutes 18 | * `%s` - # of seconds 19 | * `%%` - print a percent sign 20 | 21 | You can place a `0` before the `h`, `m`, and `s` modifiers to zeropad those values to two digits. Zeropadding is undefined for the other modifiers. 22 | 23 | #### Format String Examples 24 | 25 | The following examples show how a duration of 42 hours, 4 minutes, and 2 seconds will be formatted with various format strings: 26 | 27 | | Format string | Output | 28 | |------------------------|--------------------| 29 | | `%d days, %h hours` | `1 days, 18 hours` | 30 | | `%m minutes` | `2524 minutes` | 31 | | `%s seconds` | `151442 seconds` | 32 | | `%d days, %0h:%0m:%0s` | `1 days, 18:04:02` | 33 | 34 | ### Get 35 | 36 | Fetch and build go-durationfmt: 37 | 38 | ``` 39 | go get github.com/davidscholberg/go-durationfmt 40 | ``` 41 | 42 | ### Usage 43 | 44 | Here's a simple example for using go-durationfmt: 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "fmt" 51 | "github.com/davidscholberg/go-durationfmt" 52 | "time" 53 | ) 54 | 55 | func main() { 56 | d := (42 * time.Hour) + (4 * time.Minute) + (2 * time.Second) 57 | durStr, err := durationfmt.Format(d, "%d days, %h hours") 58 | if err != nil { 59 | fmt.Println(err) 60 | } else { 61 | fmt.Println(durStr) 62 | } 63 | } 64 | ``` 65 | 66 | ### Caveats 67 | 68 | * go-durationfmt assumes that there are 24 hours in a day and 365 days in a year, which is not always the case due to such pesky things as leap years, leap seconds, and daylight savings time. **If you need your durations to account for such time jumps, then do not use this library.** 69 | * go-durationfmt returns durations as integer values, so any fractional durations are truncated. 70 | -------------------------------------------------------------------------------- /durationfmt.go: -------------------------------------------------------------------------------- 1 | // durationfmt provides a function to format durations according to a format 2 | // string. 3 | package durationfmt 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | const Day = 24 * time.Hour 11 | const Week = 7 * Day 12 | const Year = 365 * Day 13 | 14 | // durationUnit represets a possible duration unit. A durationUnit object 15 | // contains the divisor that the duration unit uses as well as if that duration 16 | // unit is present in the duration format. 17 | type durationUnit struct { 18 | Present bool 19 | DurDivisor time.Duration 20 | } 21 | 22 | // Format formats the given duration according to the given format string. 23 | // %y - # of years 24 | // %w - # of weeks 25 | // %d - # of days 26 | // %h - # of hours 27 | // %m - # of minutes 28 | // %s - # of seconds 29 | // %% - print a percent sign 30 | // You can place a 0 before the h, m, and s modifiers to zeropad those values to 31 | // two digits. Zeropadding is undefined for the other modifiers. 32 | func Format(dur time.Duration, fmtStr string) (string, error) { 33 | var durUnitSlice = []durationUnit{ 34 | durationUnit{ 35 | DurDivisor: Year, 36 | }, 37 | durationUnit{ 38 | DurDivisor: Week, 39 | }, 40 | durationUnit{ 41 | DurDivisor: Day, 42 | }, 43 | durationUnit{ 44 | DurDivisor: time.Hour, 45 | }, 46 | durationUnit{ 47 | DurDivisor: time.Minute, 48 | }, 49 | durationUnit{ 50 | DurDivisor: time.Second, 51 | }, 52 | } 53 | var durUnitMap = map[string]*durationUnit{ 54 | "y": &durUnitSlice[0], 55 | "w": &durUnitSlice[1], 56 | "d": &durUnitSlice[2], 57 | "h": &durUnitSlice[3], 58 | "m": &durUnitSlice[4], 59 | "s": &durUnitSlice[5], 60 | } 61 | 62 | sprintfFmt, durCount, err := parseFmtStr(fmtStr, durUnitMap) 63 | if err != nil { 64 | return "", err 65 | } 66 | 67 | durArray := make([]interface{}, durCount) 68 | calculateDurUnits(dur, durArray, durUnitSlice) 69 | 70 | return fmt.Sprintf(sprintfFmt, durArray...), nil 71 | } 72 | 73 | // calculateDurUnits takes a duration and breaks it up into its constituent 74 | // duration unit values. 75 | func calculateDurUnits(dur time.Duration, durArray []interface{}, durUnitSlice []durationUnit) { 76 | remainingDur := dur 77 | durCount := 0 78 | for _, d := range durUnitSlice { 79 | if d.Present { 80 | durArray[durCount] = remainingDur / d.DurDivisor 81 | remainingDur = remainingDur % d.DurDivisor 82 | durCount++ 83 | } 84 | } 85 | } 86 | 87 | // parseFmtStr parses the given duration format string into its constituent 88 | // units. 89 | // parseFmtStr returns a format string that can be passed to fmt.Sprintf and a 90 | // count of how many duration units are in the format string. 91 | func parseFmtStr(fmtStr string, durUnitMap map[string]*durationUnit) (string, int, error) { 92 | modifier, zeropad := false, false 93 | sprintfFmt := "" 94 | durCount := 0 95 | for _, c := range fmtStr { 96 | fmtChar := string(c) 97 | if modifier == false { 98 | if fmtChar == "%" { 99 | modifier = true 100 | } else { 101 | sprintfFmt += fmtChar 102 | } 103 | continue 104 | } 105 | if _, ok := durUnitMap[fmtChar]; ok { 106 | durUnitMap[fmtChar].Present = true 107 | durCount++ 108 | if zeropad { 109 | sprintfFmt += "%02d" 110 | zeropad = false 111 | } else { 112 | sprintfFmt += "%d" 113 | } 114 | } else { 115 | switch fmtChar { 116 | case "0": 117 | zeropad = true 118 | continue 119 | case "%": 120 | sprintfFmt += "%%" 121 | default: 122 | return "", durCount, fmt.Errorf("incorrect duration modifier") 123 | } 124 | } 125 | modifier = false 126 | } 127 | return sprintfFmt, durCount, nil 128 | } 129 | --------------------------------------------------------------------------------