Merge branch 'master' into develop

This commit is contained in:
jflyfox
2021-01-06 00:39:54 +08:00
14 changed files with 270 additions and 88 deletions

View File

@ -14,7 +14,7 @@ branches:
- staging
env:
- GF_DEBUG=1 GO111MODULE=on
- TZ=Asia/Shanghai GF_DEBUG=1 GO111MODULE=on
services:
- mysql

View File

@ -1,4 +1,4 @@
// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@ -7,7 +7,6 @@
package gdb
import (
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/util/gutil"
"strings"
"time"
@ -56,6 +55,7 @@ func (c *Core) convertFieldValueToLocalValue(fieldValue interface{}, fieldType s
return gconv.Int(gconv.String(fieldValue))
case
"int8", // For pgsql, int8 = bigint.
"big_int",
"bigint",
"bigserial":
@ -162,15 +162,11 @@ func (c *Core) mappingAndFilterData(schema, table string, data map[string]interf
if foundKey != "" {
data[foundKey] = dataValue
delete(data, dataKey)
} else if !filter {
if schema != "" {
return nil, gerror.Newf(`no column of name "%s" found for table "%s" in schema "%s"`, dataKey, table, schema)
}
return nil, gerror.Newf(`no column of name "%s" found for table "%s"`, dataKey, table)
}
}
}
// Data filtering.
// It deletes all key-value pairs that has incorrect field name.
if filter {
for dataKey, _ := range data {
if _, ok := fieldsMap[dataKey]; !ok {

View File

@ -1,4 +1,4 @@
// Copyright GoFrame Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@ -496,6 +496,16 @@ func Test_Model_Update(t *testing.T) {
n, _ := result.RowsAffected()
t.Assert(n, 1)
})
// Update + Fields(string)
gtest.C(t, func(t *gtest.T) {
result, err := db.Table(table).Fields("passport").Data(g.Map{
"passport": "user_44",
"none": "none",
}).Where("passport='user_4'").Update()
t.Assert(err, nil)
n, _ := result.RowsAffected()
t.Assert(n, 1)
})
}
func Test_Model_Clone(t *testing.T) {

View File

@ -8,6 +8,7 @@ package gdb_test
import (
"fmt"
"github.com/gogf/gf/os/gtime"
"testing"
"github.com/gogf/gf/frame/g"
@ -15,6 +16,7 @@ import (
"github.com/gogf/gf/test/gtest"
)
// All types testing.
func Test_Types(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
if _, err := db.Exec(fmt.Sprintf(`
@ -67,11 +69,34 @@ func Test_Types(t *testing.T) {
t.Assert(one["blob"].String(), data["blob"])
t.Assert(one["binary"].String(), data["binary"])
t.Assert(one["date"].String(), data["date"])
t.Assert(one["time"].String(), data["time"])
t.Assert(one["time"].String(), `0000-01-01 10:00:01`)
t.Assert(one["decimal"].String(), -123.46)
t.Assert(one["double"].String(), data["double"])
t.Assert(one["bit"].Int(), data["bit"])
t.Assert(one["tinyint"].Bool(), data["tinyint"])
t.Assert(one["tinyint"].Bool(), data["tinyint"])
type T struct {
Id int
Blob []byte
Binary []byte
Date *gtime.Time
Time *gtime.Time
Decimal float64
Double float64
Bit int8
TinyInt bool
}
var obj *T
err = db.Table("types").Struct(&obj)
t.Assert(err, nil)
t.Assert(obj.Id, 1)
t.Assert(obj.Blob, data["blob"])
t.Assert(obj.Binary, data["binary"])
t.Assert(obj.Date.Format("Y-m-d"), data["date"])
t.Assert(obj.Time.String(), `0000-01-01 10:00:01`)
t.Assert(obj.Decimal, -123.46)
t.Assert(obj.Double, data["double"])
t.Assert(obj.Bit, data["bit"])
t.Assert(obj.TinyInt, data["tinyint"])
})
}

View File

@ -8,6 +8,7 @@ package gins
import (
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/intlog"
"github.com/gogf/gf/text/gstr"
"github.com/gogf/gf/util/gutil"
@ -47,7 +48,25 @@ func Database(name ...string) gdb.DB {
configMap = Config().GetMap(configNodeKey)
}
if len(configMap) == 0 && !gdb.IsConfigured() {
panic(fmt.Sprintf(`database init failed: "%s" node not found, is config file or configuration missing?`, configNodeNameDatabase))
if !Config().Available() {
exampleFileName := "config.example.toml"
if Config().Available(exampleFileName) {
panic(gerror.Newf(
`configuration file "%s" not found, but found "%s", did you miss renaming the configuration example file?`,
Config().GetFileName(),
exampleFileName,
))
} else {
panic(gerror.Newf(
`configuration file "%s" not found, did you miss the configuration file or the file name setting?`,
Config().GetFileName(),
))
}
}
panic(gerror.Newf(
`database initialization failed: "%s" node not found, is configuration file or configuration node missing?`,
configNodeNameDatabase,
))
}
if len(configMap) == 0 {
configMap = make(map[string]interface{})

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@ -13,8 +13,6 @@ import (
"strings"
"sync"
"github.com/gogf/gf/os/glog"
"github.com/gogf/gf/os/gfsnotify"
"github.com/gogf/gf/text/gregex"
@ -235,11 +233,11 @@ func (m *Manager) init() {
m.data[lang] = make(map[string]string)
}
if j, err := gjson.LoadContent(file.Content()); err == nil {
for k, v := range j.ToMap() {
for k, v := range j.Map() {
m.data[lang][k] = gconv.String(v)
}
} else {
glog.Errorf("load i18n file '%s' failed: %v", name, err)
intlog.Errorf("load i18n file '%s' failed: %v", name, err)
}
}
}
@ -270,11 +268,11 @@ func (m *Manager) init() {
m.data[lang] = make(map[string]string)
}
if j, err := gjson.LoadContent(gfile.GetBytes(file)); err == nil {
for k, v := range j.ToMap() {
for k, v := range j.Map() {
m.data[lang][k] = gconv.String(v)
}
} else {
glog.Errorf("load i18n file '%s' failed: %v", file, err)
intlog.Errorf("load i18n file '%s' failed: %v", file, err)
}
}
// Monitor changes of i18n files for hot reload feature.

View File

@ -10,10 +10,10 @@
package gtime
import (
"errors"
"fmt"
"github.com/gogf/gf/errors/gerror"
"github.com/gogf/gf/internal/utils"
"os"
"regexp"
"strconv"
"strings"
@ -45,7 +45,7 @@ const (
// "2018/10/31 - 16:38:46"
// "2018-02-09",
// "2018.02.09",
TIME_REAGEX_PATTERN1 = `(\d{4}[-/\.]\d{2}[-/\.]\d{2})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
timeRegexPattern1 = `(\d{4}[-/\.]\d{2}[-/\.]\d{2})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
// Regular expression2(datetime separator supports '-', '/', '.').
// Eg:
@ -53,14 +53,21 @@ const (
// 01/Nov/2018 11:50:28
// 01.Nov.2018 11:50:28
// 01.Nov.2018:11:50:28
TIME_REAGEX_PATTERN2 = `(\d{1,2}[-/\.][A-Za-z]{3,}[-/\.]\d{4})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
timeRegexPattern2 = `(\d{1,2}[-/\.][A-Za-z]{3,}[-/\.]\d{4})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
// Regular expression3(time).
// Eg:
// 11:50:28
// 11:50:28.897
timeRegexPattern3 = `(\d{2}):(\d{2}):(\d{2})\.{0,1}(\d{0,9})`
)
var (
// It's more high performance using regular expression
// than time.ParseInLocation to parse the datetime string.
timeRegex1, _ = regexp.Compile(TIME_REAGEX_PATTERN1)
timeRegex2, _ = regexp.Compile(TIME_REAGEX_PATTERN2)
timeRegex1, _ = regexp.Compile(timeRegexPattern1)
timeRegex2, _ = regexp.Compile(timeRegexPattern2)
timeRegex3, _ = regexp.Compile(timeRegexPattern3)
// Month words to arabic numerals mapping.
monthMap = map[string]int{
@ -95,18 +102,14 @@ var (
// The parameter <zone> is an area string specifying corresponding time zone,
// eg: Asia/Shanghai.
//
// Note that the time zone database needed by LoadLocation may not be
// present on all systems, especially non-Unix systems.
// LoadLocation looks in the directory or uncompressed zip file
// named by the ZONEINFO environment variable, if any, then looks in
// known installation locations on Unix systems,
// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
// This should be called before package "time" import.
// Please refer to issue: https://github.com/golang/go/issues/34814
func SetTimeZone(zone string) error {
location, err := time.LoadLocation(zone)
if err == nil {
time.Local = location
if err != nil {
return err
}
return err
return os.Setenv("TZ", location.String())
}
// Timestamp retrieves and returns the timestamp in seconds.
@ -247,17 +250,33 @@ func StrToTime(str string, format ...string) (*Time, error) {
local = time.Local
)
if match = timeRegex1.FindStringSubmatch(str); len(match) > 0 && match[1] != "" {
for k, v := range match {
match[k] = strings.TrimSpace(v)
}
//for k, v := range match {
// match[k] = strings.TrimSpace(v)
//}
year, month, day = parseDateStr(match[1])
} else if match = timeRegex2.FindStringSubmatch(str); len(match) > 0 && match[1] != "" {
for k, v := range match {
match[k] = strings.TrimSpace(v)
}
//for k, v := range match {
// match[k] = strings.TrimSpace(v)
//}
year, month, day = parseDateStr(match[1])
} else if match = timeRegex3.FindStringSubmatch(str); len(match) > 0 && match[1] != "" {
//for k, v := range match {
// match[k] = strings.TrimSpace(v)
//}
s := strings.Replace(match[2], ":", "", -1)
if len(s) < 6 {
s += strings.Repeat("0", 6-len(s))
}
hour, _ = strconv.Atoi(match[1])
min, _ = strconv.Atoi(match[2])
sec, _ = strconv.Atoi(match[3])
nsec, _ = strconv.Atoi(match[4])
for i := 0; i < 9-len(match[4]); i++ {
nsec *= 10
}
return NewFromTime(time.Date(0, time.Month(1), 1, hour, min, sec, nsec, local)), nil
} else {
return nil, errors.New("unsupported time format")
return nil, gerror.New("unsupported time format")
}
// Time
@ -330,8 +349,8 @@ func StrToTime(str string, format ...string) (*Time, error) {
}
}
}
if year <= 0 {
return nil, errors.New("invalid time string:" + str)
if month <= 0 || day <= 0 {
return nil, gerror.New("invalid time string:" + str)
}
return NewFromTime(time.Date(year, time.Month(month), day, hour, min, sec, nsec, local)), nil
}
@ -347,7 +366,7 @@ func ConvertZone(strTime string, toZone string, fromZone ...string) (*Time, erro
if l, err := time.LoadLocation(fromZone[0]); err != nil {
return nil, err
} else {
t.Time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Time.Second(), t.Time.Nanosecond(), l)
t.Time = time.Date(t.Year(), time.Month(t.Month()), t.Day(), t.Hour(), t.Minute(), t.Time.Second(), t.Time.Nanosecond(), l)
}
}
if l, err := time.LoadLocation(toZone); err != nil {
@ -387,6 +406,8 @@ func ParseTimeFromContent(content string, format ...string) *Time {
return NewFromStr(strings.Trim(match[0], "./_- \n\r"))
} else if match := timeRegex2.FindStringSubmatch(content); len(match) >= 1 {
return NewFromStr(strings.Trim(match[0], "./_- \n\r"))
} else if match := timeRegex3.FindStringSubmatch(content); len(match) >= 1 {
return NewFromStr(strings.Trim(match[0], "./_- \n\r"))
}
}
return nil

View File

@ -1,4 +1,4 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@ -36,8 +36,24 @@ func New(param ...interface{}) *Time {
case *Time:
return r
case string:
if len(param) > 1 {
switch t := param[1].(type) {
case string:
return NewFromStrFormat(r, t)
case []byte:
return NewFromStrFormat(r, string(t))
}
}
return NewFromStr(r)
case []byte:
if len(param) > 1 {
switch t := param[1].(type) {
case string:
return NewFromStrFormat(string(r), t)
case []byte:
return NewFromStrFormat(string(r), string(t))
}
}
return NewFromStr(string(r))
case int:
return NewFromTimeStamp(int64(r))
@ -163,6 +179,11 @@ func (t *Time) TimestampNanoStr() string {
return strconv.FormatInt(t.TimestampNano(), 10)
}
// Month returns the month of the year specified by t.
func (t *Time) Month() int {
return int(t.Time.Month())
}
// Second returns the second offset within the minute specified by t,
// in the range [0, 59].
func (t *Time) Second() int {
@ -219,22 +240,6 @@ func (t *Time) AddStr(duration string) (*Time, error) {
}
}
// ToLocation converts current time to specified location.
func (t *Time) ToLocation(location *time.Location) *Time {
newTime := t.Clone()
newTime.Time = newTime.Time.In(location)
return newTime
}
// ToZone converts current time to specified zone like: Asia/Shanghai.
func (t *Time) ToZone(zone string) (*Time, error) {
if l, err := time.LoadLocation(zone); err == nil {
return t.ToLocation(l), nil
} else {
return nil, err
}
}
// UTC converts current time to UTC timezone.
func (t *Time) UTC() *Time {
newTime := t.Clone()
@ -252,13 +257,6 @@ func (t *Time) RFC822() string {
return t.Layout("Mon, 02 Jan 06 15:04 MST")
}
// Local converts the time to local timezone.
func (t *Time) Local() *Time {
newTime := t.Clone()
newTime.Time = newTime.Time.Local()
return newTime
}
// AddDate adds year, month and day to the time.
func (t *Time) AddDate(years int, months int, days int) *Time {
newTime := t.Clone()

View File

@ -0,0 +1,58 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gtime
import (
"sync"
"time"
)
var (
// locationMap is time zone name to its location object.
// Time zone name is like: Asia/Shanghai.
locationMap = make(map[string]*time.Location)
// locationMu is used for concurrent safety for `locationMap`.
locationMu = sync.RWMutex{}
)
// ToLocation converts current time to specified location.
func (t *Time) ToLocation(location *time.Location) *Time {
newTime := t.Clone()
newTime.Time = newTime.Time.In(location)
return newTime
}
// ToZone converts current time to specified zone like: Asia/Shanghai.
func (t *Time) ToZone(zone string) (*Time, error) {
if location, err := t.getLocationByZoneName(zone); err == nil {
return t.ToLocation(location), nil
} else {
return nil, err
}
}
func (t *Time) getLocationByZoneName(name string) (location *time.Location, err error) {
locationMu.RLock()
location = locationMap[name]
locationMu.RUnlock()
if location == nil {
location, err = time.LoadLocation(name)
if err == nil && location != nil {
locationMu.Lock()
locationMap[name] = location
locationMu.Unlock()
}
}
return
}
// Local converts the time to local timezone.
func (t *Time) Local() *Time {
newTime := t.Clone()
newTime.Time = newTime.Time.Local()
return newTime
}

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@ -8,6 +8,7 @@ package gtime_test
import (
"testing"
"time"
"github.com/gogf/gf/os/gtime"
)
@ -42,6 +43,18 @@ func Benchmark_StrToTime(b *testing.B) {
}
}
func Benchmark_StrToTime_Format(b *testing.B) {
for i := 0; i < b.N; i++ {
gtime.StrToTime("2018-02-09 20:46:17.897", "Y-m-d H:i:su")
}
}
func Benchmark_StrToTime_Layout(b *testing.B) {
for i := 0; i < b.N; i++ {
gtime.StrToTimeLayout("2018-02-09T20:46:17.897Z", time.RFC3339)
}
}
func Benchmark_ParseTimeFromContent(b *testing.B) {
for i := 0; i < b.N; i++ {
gtime.ParseTimeFromContent("2018-02-09T20:46:17.897Z")

View File

@ -1,4 +1,4 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@ -7,6 +7,7 @@
package gtime_test
import (
"github.com/gogf/gf/frame/g"
"testing"
"time"
@ -16,8 +17,8 @@ import (
func Test_SetTimeZone(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
gtime.SetTimeZone("Asia/Shanghai")
t.Assert(time.Local.String(), "Asia/Shanghai")
t.Assert(gtime.SetTimeZone("Asia/Shanghai"), nil)
//t.Assert(time.Local.String(), "Asia/Shanghai")
})
}
@ -86,6 +87,7 @@ func Test_RFC822(t *testing.T) {
func Test_StrToTime(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Correct datetime string.
var testDateTimes = []string{
"2006-01-02 15:04:05",
"2006/01/02 15:04:05",
@ -103,13 +105,11 @@ func Test_StrToTime(t *testing.T) {
for _, item := range testDateTimes {
timeTemp, err := gtime.StrToTime(item)
if err != nil {
t.Error("test fail")
}
t.Assert(err, nil)
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), "2006-01-02 15:04:05")
}
//正常日期列表时间00:00:00
// Correct date string,.
var testDates = []string{
"2006.01.02",
"2006.01.02 00:00",
@ -118,13 +118,25 @@ func Test_StrToTime(t *testing.T) {
for _, item := range testDates {
timeTemp, err := gtime.StrToTime(item)
if err != nil {
t.Error("test fail")
}
t.Assert(err, nil)
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05"), "2006-01-02 00:00:00")
}
//测试格式化formatToStdLayout
// Correct time string.
var testTimes = g.MapStrStr{
"16:12:01": "15:04:05",
"16:12:01.789": "15:04:05.000",
}
for k, v := range testTimes {
time1, err := gtime.StrToTime(k)
t.Assert(err, nil)
time2, err := time.ParseInLocation(v, k, time.Local)
t.Assert(err, nil)
t.Assert(time1.Time, time2)
}
// formatToStdLayout
var testDateFormats = []string{
"Y-m-d H:i:s",
"\\T\\i\\m\\e Y-m-d H:i:s",
@ -149,7 +161,7 @@ func Test_StrToTime(t *testing.T) {
t.Assert(timeTemp.Time.Format("2006-01-02 15:04:05.000"), "2007-01-02 15:04:05.000")
}
//异常日期列表
// 异常日期列表
var testDatesFail = []string{
"2006.01",
"06..02",

View File

@ -1,4 +1,4 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@ -51,11 +51,11 @@ func (view *View) buildInFuncMaps(value ...interface{}) []map[string]interface{}
func (view *View) buildInFuncEq(value interface{}, others ...interface{}) bool {
s := gconv.String(value)
for _, v := range others {
if strings.Compare(s, gconv.String(v)) != 0 {
return false
if strings.Compare(s, gconv.String(v)) == 0 {
return true
}
}
return true
return false
}
// buildInFuncNe implements build-in template function: ne

View File

@ -175,6 +175,19 @@ func Test_Func(t *testing.T) {
t.Assert(err, nil)
t.Assert(result, `ILoveGoFrame`)
})
// eq: multiple values.
gtest.C(t, func(t *gtest.T) {
str := `{{eq 1 2 1 3 4 5}}`
result, err := gview.ParseContent(str, nil)
t.Assert(err != nil, false)
t.Assert(result, `true`)
})
gtest.C(t, func(t *gtest.T) {
str := `{{eq 6 2 1 3 4 5}}`
result, err := gview.ParseContent(str, nil)
t.Assert(err != nil, false)
t.Assert(result, `false`)
})
}
func Test_FuncNl2Br(t *testing.T) {

View File

@ -1,4 +1,4 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
@ -17,9 +17,28 @@ import (
func Test_Time(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
t1 := "2011-10-10 01:02:03.456"
t.AssertEQ(gconv.GTime(t1), gtime.NewFromStr(t1))
t.AssertEQ(gconv.Time(t1), gtime.NewFromStr(t1).Time)
s := "2011-10-10 01:02:03.456"
t.AssertEQ(gconv.GTime(s), gtime.NewFromStr(s))
t.AssertEQ(gconv.Time(s), gtime.NewFromStr(s).Time)
t.AssertEQ(gconv.Duration(100), 100*time.Nanosecond)
})
gtest.C(t, func(t *gtest.T) {
s := "01:02:03.456"
t.AssertEQ(gconv.GTime(s).Hour(), 1)
t.AssertEQ(gconv.GTime(s).Minute(), 2)
t.AssertEQ(gconv.GTime(s).Second(), 3)
t.AssertEQ(gconv.GTime(s), gtime.NewFromStr(s))
t.AssertEQ(gconv.Time(s), gtime.NewFromStr(s).Time)
})
gtest.C(t, func(t *gtest.T) {
s := "0000-01-01 01:02:03"
t.AssertEQ(gconv.GTime(s).Year(), 0)
t.AssertEQ(gconv.GTime(s).Month(), 1)
t.AssertEQ(gconv.GTime(s).Day(), 1)
t.AssertEQ(gconv.GTime(s).Hour(), 1)
t.AssertEQ(gconv.GTime(s).Minute(), 2)
t.AssertEQ(gconv.GTime(s).Second(), 3)
t.AssertEQ(gconv.GTime(s), gtime.NewFromStr(s))
t.AssertEQ(gconv.Time(s), gtime.NewFromStr(s).Time)
})
}