duration.go 2.9 KB

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