github.com/crewjam/saml@v0.4.14/duration.go (about)

     1  package saml
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  )
    10  
    11  // Duration is a time.Duration that uses the xsd:duration format for text
    12  // marshalling and unmarshalling.
    13  type Duration time.Duration
    14  
    15  // MarshalText implements the encoding.TextMarshaler interface.
    16  func (d Duration) MarshalText() ([]byte, error) {
    17  	if d == 0 {
    18  		return nil, nil
    19  	}
    20  
    21  	out := "PT"
    22  	if d < 0 {
    23  		d *= -1
    24  		out = "-" + out
    25  	}
    26  
    27  	h := time.Duration(d) / time.Hour
    28  	m := time.Duration(d) % time.Hour / time.Minute
    29  	s := time.Duration(d) % time.Minute / time.Second
    30  	ns := time.Duration(d) % time.Second
    31  	if h > 0 {
    32  		out += fmt.Sprintf("%dH", h)
    33  	}
    34  	if m > 0 {
    35  		out += fmt.Sprintf("%dM", m)
    36  	}
    37  	if s > 0 || ns > 0 {
    38  		out += fmt.Sprintf("%d", s)
    39  		if ns > 0 {
    40  			out += strings.TrimRight(fmt.Sprintf(".%09d", ns), "0")
    41  		}
    42  		out += "S"
    43  	}
    44  
    45  	return []byte(out), nil
    46  }
    47  
    48  const (
    49  	day   = 24 * time.Hour
    50  	month = 30 * day  // Assumed to be 30 days.
    51  	year  = 365 * day // Assumed to be non-leap year.
    52  )
    53  
    54  var (
    55  	durationRegexp     = regexp.MustCompile(`^(-?)P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(.+))?$`)
    56  	durationTimeRegexp = regexp.MustCompile(`^(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?$`)
    57  )
    58  
    59  // UnmarshalText implements the encoding.TextUnmarshaler interface.
    60  func (d *Duration) UnmarshalText(text []byte) error {
    61  	if text == nil {
    62  		*d = 0
    63  		return nil
    64  	}
    65  
    66  	var (
    67  		out  time.Duration
    68  		sign time.Duration = 1
    69  	)
    70  	match := durationRegexp.FindStringSubmatch(string(text))
    71  	if match == nil || strings.Join(match[2:6], "") == "" {
    72  		return fmt.Errorf("invalid duration (%s)", text)
    73  	}
    74  	if match[1] == "-" {
    75  		sign = -1
    76  	}
    77  	if match[2] != "" {
    78  		y, err := strconv.Atoi(match[2])
    79  		if err != nil {
    80  			return fmt.Errorf("invalid duration years (%s): %s", text, err)
    81  		}
    82  		out += time.Duration(y) * year
    83  	}
    84  	if match[3] != "" {
    85  		m, err := strconv.Atoi(match[3])
    86  		if err != nil {
    87  			return fmt.Errorf("invalid duration months (%s): %s", text, err)
    88  		}
    89  		out += time.Duration(m) * month
    90  	}
    91  	if match[4] != "" {
    92  		d, err := strconv.Atoi(match[4])
    93  		if err != nil {
    94  			return fmt.Errorf("invalid duration days (%s): %s", text, err)
    95  		}
    96  		out += time.Duration(d) * day
    97  	}
    98  	if match[5] != "" {
    99  		match := durationTimeRegexp.FindStringSubmatch(match[5])
   100  		if match == nil {
   101  			return fmt.Errorf("invalid duration (%s)", text)
   102  		}
   103  		if match[1] != "" {
   104  			h, err := strconv.Atoi(match[1])
   105  			if err != nil {
   106  				return fmt.Errorf("invalid duration hours (%s): %s", text, err)
   107  			}
   108  			out += time.Duration(h) * time.Hour
   109  		}
   110  		if match[2] != "" {
   111  			m, err := strconv.Atoi(match[2])
   112  			if err != nil {
   113  				return fmt.Errorf("invalid duration minutes (%s): %s", text, err)
   114  			}
   115  			out += time.Duration(m) * time.Minute
   116  		}
   117  		if match[3] != "" {
   118  			s, err := strconv.ParseFloat(match[3], 64)
   119  			if err != nil {
   120  				return fmt.Errorf("invalid duration seconds (%s): %s", text, err)
   121  			}
   122  			out += time.Duration(s * float64(time.Second))
   123  		}
   124  	}
   125  
   126  	*d = Duration(sign * out)
   127  	return nil
   128  }