mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-11 18:16:24 +05:00
284 lines
7.6 KiB
Go
284 lines
7.6 KiB
Go
/*
|
|
Real-time Charging System for Telecom & ISP environments
|
|
Copyright (C) 2012-2014 ITsysCOM GmbH
|
|
|
|
This program is free software: you can Storagetribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITH*out ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
*/
|
|
|
|
package utils
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"crypto/tls"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Returns first non empty string out of vals. Useful to extract defaults
|
|
func FirstNonEmpty(vals ...string) string {
|
|
for _, val := range vals {
|
|
if len(val) != 0 {
|
|
return val
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func Sha1(attrs ...string) string {
|
|
hasher := sha1.New()
|
|
for _, attr := range attrs {
|
|
hasher.Write([]byte(attr))
|
|
}
|
|
return fmt.Sprintf("%x", hasher.Sum(nil))
|
|
}
|
|
|
|
func NewTPid() string {
|
|
return Sha1(GenUUID())
|
|
}
|
|
|
|
// helper function for uuid generation
|
|
func GenUUID() string {
|
|
uuid := make([]byte, 16)
|
|
n, err := rand.Read(uuid)
|
|
if n != len(uuid) || err != nil {
|
|
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
|
}
|
|
// TODO: verify the two lines implement RFC 4122 correctly
|
|
uuid[8] = 0x80 // variant bits see page 5
|
|
uuid[4] = 0x40 // version 4 Pseudo Random, see page 7
|
|
|
|
return hex.EncodeToString(uuid)
|
|
}
|
|
|
|
// Round return rounded version of x with prec precision.
|
|
//
|
|
// Special cases are:
|
|
// Round(±0) = ±0
|
|
// Round(±Inf) = ±Inf
|
|
// Round(NaN) = NaN
|
|
func Round(x float64, prec int, method string) float64 {
|
|
var rounder float64
|
|
maxPrec := 7 // define a max precison to cut float errors
|
|
if maxPrec < prec {
|
|
maxPrec = prec
|
|
}
|
|
pow := math.Pow(10, float64(prec))
|
|
intermed := x * pow
|
|
_, frac := math.Modf(intermed)
|
|
|
|
switch method {
|
|
case ROUNDING_UP:
|
|
if frac >= math.Pow10(-maxPrec) { // Max precision we go, rest is float chaos
|
|
rounder = math.Ceil(intermed)
|
|
} else {
|
|
rounder = math.Floor(intermed)
|
|
}
|
|
case ROUNDING_DOWN:
|
|
rounder = math.Floor(intermed)
|
|
case ROUNDING_MIDDLE:
|
|
if frac >= 0.5 {
|
|
rounder = math.Ceil(intermed)
|
|
} else {
|
|
rounder = math.Floor(intermed)
|
|
}
|
|
default:
|
|
rounder = intermed
|
|
}
|
|
|
|
return rounder / pow
|
|
}
|
|
|
|
func ParseTimeDetectLayout(tmStr string) (time.Time, error) {
|
|
var nilTime time.Time
|
|
rfc3339Rule := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.+$`)
|
|
sqlRule := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$`)
|
|
gotimeRule := regexp.MustCompile(`^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\.?\d*\s[+,-]\d+\s\w+$`)
|
|
fsTimestamp := regexp.MustCompile(`^\d{16}$`)
|
|
unixTimestampRule := regexp.MustCompile(`^\d{10}$`)
|
|
oneLineTimestampRule := regexp.MustCompile(`^\d{14}$`)
|
|
oneSpaceTimestampRule := regexp.MustCompile(`^\d{2}\.\d{2}.\d{4}\s{1}\d{2}:\d{2}:\d{2}$`)
|
|
switch {
|
|
case rfc3339Rule.MatchString(tmStr):
|
|
return time.Parse(time.RFC3339, tmStr)
|
|
case gotimeRule.MatchString(tmStr):
|
|
return time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", tmStr)
|
|
case sqlRule.MatchString(tmStr):
|
|
return time.Parse("2006-01-02 15:04:05", tmStr)
|
|
case fsTimestamp.MatchString(tmStr):
|
|
if tmstmp, err := strconv.ParseInt(tmStr+"000", 10, 64); err != nil {
|
|
return nilTime, err
|
|
} else {
|
|
return time.Unix(0, tmstmp), nil
|
|
}
|
|
case unixTimestampRule.MatchString(tmStr):
|
|
if tmstmp, err := strconv.ParseInt(tmStr, 10, 64); err != nil {
|
|
return nilTime, err
|
|
} else {
|
|
return time.Unix(tmstmp, 0), nil
|
|
}
|
|
case tmStr == "0" || len(tmStr) == 0: // Time probably missing from request
|
|
return nilTime, nil
|
|
case oneLineTimestampRule.MatchString(tmStr):
|
|
return time.Parse("20060102150405", tmStr)
|
|
case oneSpaceTimestampRule.MatchString(tmStr):
|
|
return time.Parse("02.01.2006 15:04:05", tmStr)
|
|
}
|
|
return nilTime, errors.New("Unsupported time format")
|
|
}
|
|
|
|
func ParseDate(date string) (expDate time.Time, err error) {
|
|
date = strings.TrimSpace(date)
|
|
switch {
|
|
case date == "*unlimited" || date == "":
|
|
// leave it at zero
|
|
case strings.HasPrefix(date, "+"):
|
|
d, err := time.ParseDuration(date[1:])
|
|
if err != nil {
|
|
return expDate, err
|
|
}
|
|
expDate = time.Now().Add(d)
|
|
case date == "*daily":
|
|
expDate = time.Now().AddDate(0, 0, 1) // add one day
|
|
case date == "*monthly":
|
|
expDate = time.Now().AddDate(0, 1, 0) // add one month
|
|
case date == "*yearly":
|
|
expDate = time.Now().AddDate(1, 0, 0) // add one year
|
|
case strings.HasSuffix(date, "Z"):
|
|
expDate, err = time.Parse(time.RFC3339, date)
|
|
default:
|
|
unix, err := strconv.ParseInt(date, 10, 64)
|
|
if err != nil {
|
|
return expDate, err
|
|
}
|
|
expDate = time.Unix(unix, 0)
|
|
}
|
|
return expDate, err
|
|
}
|
|
|
|
// returns a number equeal or larger than the amount that exactly
|
|
// is divisible to whole
|
|
func RoundTo(whole, amount time.Duration) time.Duration {
|
|
a, w := float64(amount), float64(whole)
|
|
if math.Mod(a, w) == 0 {
|
|
return amount
|
|
}
|
|
return time.Duration((w - math.Mod(a, w)) + a)
|
|
}
|
|
|
|
func SplitPrefix(prefix string, minLength int) []string {
|
|
length := int(math.Max(float64(len(prefix)-(minLength-1)), 0))
|
|
subs := make([]string, length)
|
|
max := len(prefix)
|
|
for i := 0; i < length; i++ {
|
|
subs[i] = prefix[:max-i]
|
|
}
|
|
return subs
|
|
}
|
|
|
|
func CopyHour(src, dest time.Time) time.Time {
|
|
if src.Hour() == 0 && src.Minute() == 0 && src.Second() == 0 {
|
|
return src
|
|
}
|
|
return time.Date(dest.Year(), dest.Month(), dest.Day(), src.Hour(), src.Minute(), src.Second(), src.Nanosecond(), src.Location())
|
|
}
|
|
|
|
// Parses duration, considers s as time unit if not provided
|
|
func ParseDurationWithSecs(durStr string) (time.Duration, error) {
|
|
if _, err := strconv.Atoi(durStr); err == nil { // No suffix, default to seconds
|
|
durStr += "s"
|
|
}
|
|
return time.ParseDuration(durStr)
|
|
}
|
|
|
|
func ParseDurationWithNanosecs(durStr string) (time.Duration, error) {
|
|
if _, err := strconv.Atoi(durStr); err == nil { // No suffix, default to seconds
|
|
durStr += "ns"
|
|
}
|
|
return time.ParseDuration(durStr)
|
|
}
|
|
|
|
func AccountKey(tenant, account, direction string) string {
|
|
return fmt.Sprintf("%s:%s:%s", direction, tenant, account)
|
|
}
|
|
|
|
// returns the minimum duration between the two
|
|
func MinDuration(d1, d2 time.Duration) time.Duration {
|
|
if d1 < d2 {
|
|
return d1
|
|
}
|
|
return d2
|
|
}
|
|
|
|
func ParseZeroRatingSubject(rateSubj string) (time.Duration, error) {
|
|
if rateSubj == "" {
|
|
rateSubj = ZERO_RATING_SUBJECT_PREFIX + "1s"
|
|
}
|
|
if !strings.HasPrefix(rateSubj, ZERO_RATING_SUBJECT_PREFIX) {
|
|
return 0, errors.New("malformed rating subject: " + rateSubj)
|
|
}
|
|
durStr := rateSubj[len(ZERO_RATING_SUBJECT_PREFIX):]
|
|
return time.ParseDuration(durStr)
|
|
}
|
|
|
|
func ConcatenatedKey(keyVals ...string) string {
|
|
resKey := ""
|
|
for idx, key := range keyVals {
|
|
if idx != 0 {
|
|
resKey += CONCATENATED_KEY_SEP
|
|
}
|
|
resKey += key
|
|
}
|
|
return resKey
|
|
}
|
|
|
|
func RatingSubjectAliasKey(tenant, subject string) string {
|
|
return ConcatenatedKey(tenant, subject)
|
|
}
|
|
|
|
func AccountAliasKey(tenant, account string) string {
|
|
return ConcatenatedKey(tenant, account)
|
|
}
|
|
|
|
func HttpJsonPost(url string, skipTlsVerify bool, content interface{}) ([]byte, error) {
|
|
body, err := json.Marshal(content)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTlsVerify},
|
|
}
|
|
client := &http.Client{Transport: tr}
|
|
resp, err := client.Post(url, "application/json", bytes.NewBuffer(body))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
respBody, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return respBody, nil
|
|
}
|