github.com/altipla-consulting/ravendb-go-client@v0.1.3/duration.go (about)

     1  package ravendb
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  )
     9  
    10  // Duration is an alias for time.Duration that serializes to JSON
    11  // in format compatible with ravendb server
    12  type Duration time.Duration
    13  
    14  // MarshalJSON converts to JSON format (as a string)
    15  func (d Duration) MarshalJSON() ([]byte, error) {
    16  	dur := time.Duration(d)
    17  	nDays := dur / (time.Hour * 24)
    18  	dur = dur - (time.Hour * 24 * nDays)
    19  	nHours := dur / time.Hour
    20  	dur = dur - (time.Hour * nHours)
    21  	nMins := dur / time.Minute
    22  	dur = dur - (time.Minute * nMins)
    23  	nSecs := dur / time.Second
    24  	dur = dur - (time.Second * nSecs)
    25  	var s string
    26  	if nDays > 0 {
    27  		// "5."
    28  		s = fmt.Sprintf("%d.", nDays)
    29  	}
    30  	// "00:00:00"
    31  	s += fmt.Sprintf("%02d:%02d:%02d", nHours, nMins, nSecs)
    32  
    33  	millis := dur / time.Millisecond
    34  	if millis > 0 {
    35  		s += fmt.Sprintf(".%03d0000", millis)
    36  	}
    37  	s = `"` + s + `"`
    38  	return []byte(s), nil
    39  }
    40  
    41  // UnmarshalJSON decodes from string
    42  func (d *Duration) UnmarshalJSON(data []byte) error {
    43  	s := string(data)
    44  	errOut := fmt.Errorf("'%s' is not a valid JSON serializatioon of Duration", s)
    45  
    46  	if len(s) < 8+2 || s[0] != '"' || s[len(s)-1] != '"' {
    47  		// needs at least 00:00:00 + 2 * "
    48  		return errOut
    49  	}
    50  
    51  	// string string quotes (") from the beginning and end of string
    52  	s = s[1 : len(s)-1]
    53  	parts := strings.Split(s, ":")
    54  	if len(parts) != 3 {
    55  		return errOut
    56  	}
    57  	var nDays int
    58  	var err error
    59  	first := parts[0]
    60  	// this can either be ${hour} or ${day}.${hour}
    61  	parts2 := strings.Split(first, ".")
    62  	if len(parts2) > 2 {
    63  		return errOut
    64  	}
    65  	if len(parts2) == 2 {
    66  		nDays, err = strconv.Atoi(parts2[0])
    67  		if err != nil {
    68  			return errOut
    69  		}
    70  		parts[0] = parts2[1]
    71  	}
    72  	// TODO: be more strict i.e. only allow 2-digit NN as a valid number
    73  	nHours, err := strconv.Atoi(parts[0])
    74  	if err != nil {
    75  		return errOut
    76  	}
    77  	nMinutes, err := strconv.Atoi(parts[1])
    78  	if err != nil {
    79  		return errOut
    80  	}
    81  	parts2 = strings.Split(parts[2], ".")
    82  	if len(parts2) > 2 {
    83  		return errOut
    84  	}
    85  	secsStr := parts[2]
    86  	var nMS int
    87  	if len(parts2) == 2 {
    88  		secsStr = parts2[0]
    89  		msStr := parts2[1]
    90  		if len(msStr) > 3 {
    91  			msStr = msStr[:3]
    92  		}
    93  		// ".1" is really ".100"
    94  		for len(msStr) < 3 {
    95  			msStr = msStr + "0"
    96  		}
    97  		nMS, err = strconv.Atoi(msStr)
    98  		if err != nil {
    99  			return errOut
   100  		}
   101  	}
   102  	nSecs, err := strconv.Atoi(secsStr)
   103  	if err != nil {
   104  		return errOut
   105  	}
   106  	dur := time.Duration(nDays) * (time.Hour * 24)
   107  	dur += time.Duration(nHours) * time.Hour
   108  	dur += time.Duration(nMinutes) * time.Minute
   109  	dur += time.Duration(nSecs) * time.Second
   110  	dur += time.Duration(nMS) * time.Millisecond
   111  	*d = Duration(dur)
   112  	return nil
   113  }