billingcycle.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package billing
  15. import (
  16. "errors"
  17. "fmt"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "yunion.io/x/log"
  22. )
  23. type TBillingCycleUnit string
  24. const (
  25. BillingCycleMinute = TBillingCycleUnit("I")
  26. BillingCycleHour = TBillingCycleUnit("H")
  27. BillingCycleDay = TBillingCycleUnit("D")
  28. BillingCycleWeek = TBillingCycleUnit("W")
  29. BillingCycleMonth = TBillingCycleUnit("M")
  30. BillingCycleYear = TBillingCycleUnit("Y")
  31. )
  32. var (
  33. ErrInvalidBillingCycle = errors.New("invalid billing cycle")
  34. ErrInvalidDuration = errors.New("invalid duration")
  35. )
  36. type SBillingCycle struct {
  37. AutoRenew bool
  38. Count int
  39. Unit TBillingCycleUnit
  40. }
  41. func ParseBillingCycle(cycleStr string) (SBillingCycle, error) {
  42. cycle := SBillingCycle{}
  43. if len(cycleStr) < 2 {
  44. return cycle, ErrInvalidBillingCycle
  45. }
  46. switch cycleStr[len(cycleStr)-1:] {
  47. case string(BillingCycleMinute), strings.ToLower(string(BillingCycleMinute)):
  48. cycle.Unit = BillingCycleMinute
  49. case string(BillingCycleHour), strings.ToLower(string(BillingCycleHour)):
  50. cycle.Unit = BillingCycleHour
  51. case string(BillingCycleDay), strings.ToLower(string(BillingCycleDay)):
  52. cycle.Unit = BillingCycleDay
  53. case string(BillingCycleWeek), strings.ToLower(string(BillingCycleWeek)):
  54. cycle.Unit = BillingCycleWeek
  55. case string(BillingCycleMonth), strings.ToLower(string(BillingCycleMonth)):
  56. cycle.Unit = BillingCycleMonth
  57. case string(BillingCycleYear), strings.ToLower(string(BillingCycleYear)):
  58. cycle.Unit = BillingCycleYear
  59. default:
  60. return cycle, ErrInvalidBillingCycle
  61. }
  62. val, err := strconv.Atoi(cycleStr[:len(cycleStr)-1])
  63. if err != nil {
  64. log.Errorf("invalid BillingCycle string %s: %v", cycleStr, err)
  65. return cycle, ErrInvalidBillingCycle
  66. }
  67. cycle.Count = val
  68. return cycle, nil
  69. }
  70. // parse duration to minute unit billing cycle
  71. func DurationToBillingCycle(dur time.Duration) SBillingCycle {
  72. return SBillingCycle{
  73. Unit: BillingCycleMinute,
  74. Count: int(dur.Minutes()),
  75. }
  76. }
  77. func (cycle *SBillingCycle) String() string {
  78. return fmt.Sprintf("%d%s", cycle.Count, cycle.Unit)
  79. }
  80. func (cycle SBillingCycle) EndAt(tm time.Time) time.Time {
  81. if tm.IsZero() {
  82. tm = time.Now().UTC()
  83. }
  84. switch cycle.Unit {
  85. case BillingCycleMinute:
  86. return tm.Add(time.Minute * time.Duration(cycle.Count))
  87. case BillingCycleHour:
  88. return tm.Add(time.Hour * time.Duration(cycle.Count))
  89. case BillingCycleDay:
  90. return tm.AddDate(0, 0, cycle.Count)
  91. case BillingCycleWeek:
  92. return tm.AddDate(0, 0, cycle.Count*7)
  93. case BillingCycleMonth:
  94. return tm.AddDate(0, cycle.Count, 0)
  95. case BillingCycleYear:
  96. return tm.AddDate(cycle.Count, 0, 0)
  97. default:
  98. return tm.Add(time.Hour * time.Duration(cycle.Count))
  99. }
  100. }
  101. func minuteStart(tm time.Time) time.Time {
  102. return time.Date(
  103. tm.Year(),
  104. tm.Month(),
  105. tm.Day(),
  106. tm.Hour(),
  107. tm.Minute(),
  108. 0,
  109. 0,
  110. tm.Location(),
  111. )
  112. }
  113. func hourStart(tm time.Time) time.Time {
  114. return time.Date(
  115. tm.Year(),
  116. tm.Month(),
  117. tm.Day(),
  118. tm.Hour(),
  119. 0,
  120. 0,
  121. 0,
  122. tm.Location(),
  123. )
  124. }
  125. func dayStart(tm time.Time) time.Time {
  126. return time.Date(
  127. tm.Year(),
  128. tm.Month(),
  129. tm.Day(),
  130. 0,
  131. 0,
  132. 0,
  133. 0,
  134. tm.Location(),
  135. )
  136. }
  137. func weekStart(tm time.Time) time.Time {
  138. tm = dayStart(tm)
  139. dayOff := int(tm.Weekday()) - 1
  140. if dayOff < 0 {
  141. dayOff += 7
  142. }
  143. return tm.Add(-time.Duration(dayOff) * time.Hour * 24)
  144. }
  145. func monthStart(tm time.Time) time.Time {
  146. return time.Date(
  147. tm.Year(),
  148. tm.Month(),
  149. 1,
  150. 0,
  151. 0,
  152. 0,
  153. 0,
  154. tm.Location(),
  155. )
  156. }
  157. func yearStart(tm time.Time) time.Time {
  158. return time.Date(
  159. tm.Year(),
  160. time.January,
  161. 1,
  162. 0,
  163. 0,
  164. 0,
  165. 0,
  166. tm.Location(),
  167. )
  168. }
  169. func (cycle SBillingCycle) LatestLastStart(tm time.Time) time.Time {
  170. if tm.IsZero() {
  171. tm = time.Now().UTC()
  172. }
  173. switch cycle.Unit {
  174. case BillingCycleMinute:
  175. return minuteStart(tm)
  176. case BillingCycleHour:
  177. return hourStart(tm)
  178. case BillingCycleDay:
  179. return dayStart(tm)
  180. case BillingCycleWeek:
  181. return weekStart(tm)
  182. case BillingCycleMonth:
  183. return monthStart(tm)
  184. case BillingCycleYear:
  185. return yearStart(tm)
  186. default: // hour
  187. return hourStart(tm)
  188. }
  189. }
  190. func (cycle SBillingCycle) TimeString(tm time.Time) string {
  191. if tm.IsZero() {
  192. tm = time.Now().UTC()
  193. }
  194. switch cycle.Unit {
  195. case BillingCycleMinute:
  196. return tm.Format("200601021504")
  197. case BillingCycleHour:
  198. return tm.Format("2006010215")
  199. case BillingCycleDay:
  200. return tm.Format("20060102")
  201. case BillingCycleWeek:
  202. return tm.Format("20060102w")
  203. case BillingCycleMonth:
  204. return tm.Format("200601")
  205. case BillingCycleYear:
  206. return tm.Format("2006")
  207. default: // hour
  208. return tm.Format("2006010215")
  209. }
  210. }
  211. func (cycle SBillingCycle) Duration() time.Duration {
  212. now := time.Now().UTC()
  213. endAt := cycle.EndAt(now)
  214. return endAt.Sub(now)
  215. }
  216. func (cycle *SBillingCycle) GetDays() int {
  217. switch cycle.Unit {
  218. case BillingCycleMinute:
  219. return cycle.Count / 24 / 60
  220. case BillingCycleHour:
  221. return cycle.Count / 24
  222. case BillingCycleDay:
  223. return cycle.Count
  224. case BillingCycleWeek:
  225. return cycle.Count * 7
  226. default:
  227. return 0
  228. }
  229. }
  230. func (cycle *SBillingCycle) GetWeeks() int {
  231. switch cycle.Unit {
  232. case BillingCycleMinute:
  233. return cycle.Count / 7 / 24 / 60
  234. case BillingCycleHour:
  235. return cycle.Count / 7 / 24
  236. case BillingCycleDay:
  237. return cycle.Count / 7
  238. case BillingCycleWeek:
  239. return cycle.Count
  240. default:
  241. return 0
  242. }
  243. }
  244. func (cycle *SBillingCycle) GetMonths() int {
  245. switch cycle.Unit {
  246. case BillingCycleMonth:
  247. return cycle.Count
  248. case BillingCycleYear:
  249. return cycle.Count * 12
  250. default:
  251. return 0
  252. }
  253. }
  254. func (cycle *SBillingCycle) GetYears() int {
  255. switch cycle.Unit {
  256. case BillingCycleMonth:
  257. if cycle.Count%12 == 0 {
  258. return cycle.Count / 12
  259. }
  260. return 0
  261. case BillingCycleYear:
  262. return cycle.Count
  263. default:
  264. return 0
  265. }
  266. }
  267. func (cycle *SBillingCycle) IsValid() bool {
  268. return cycle.Unit != "" && cycle.Count > 0
  269. }