diff --git a/g/container/gpool/gpool.go b/g/container/gpool/gpool.go index bbade019a..5b2e4c73a 100644 --- a/g/container/gpool/gpool.go +++ b/g/container/gpool/gpool.go @@ -65,6 +65,11 @@ func (p *Pool) Put(value interface{}) { p.list.PushBack(item) } +// 清空对象池 +func (p *Pool) Clear() { + p.list.RemoveAll() +} + // 从池中获得一个临时对象 func (p *Pool) Get() (interface{}, error) { for !p.closed.Val() { @@ -93,11 +98,6 @@ func (p *Pool) Close() { p.closed.Set(true) } -// 池是否已关闭 -func (p *Pool) IsClosed() bool { - return p.closed.Val() -} - // 超时检测循环 func (p *Pool) expireCheckingLoop() { for !p.closed.Val() { diff --git a/g/os/gfpool/gfpool.go b/g/os/gfpool/gfpool.go index b31441a7f..143c902ce 100644 --- a/g/os/gfpool/gfpool.go +++ b/g/os/gfpool/gfpool.go @@ -19,6 +19,7 @@ import ( // 文件指针池 type Pool struct { + id *gtype.Int // 指针池ID,用以识别指针池是否重建 pool *gpool.Pool // 底层对象池 inited *gtype.Bool // 是否初始化(在执行第一次File方法后初始化) watcher *fsnotify.Watcher // 文件监控对象 @@ -31,6 +32,7 @@ type File struct { os.File // 底层文件指针 mu sync.RWMutex // 互斥锁 pool *Pool // 所属池 + poolid int // 所属池ID,如果池ID不同表示池已经重建,那么该文件指针也应当销毁,不能重新丢到原有的池中 flag int // 打开标志 perm os.FileMode // 打开权限 path string // 绝对路径 @@ -63,6 +65,7 @@ func New(path string, flag int, perm os.FileMode, expire...int) *Pool { fpExpire = expire[0] } p := &Pool { + id : gtype.NewInt(), expire : fpExpire, inited : gtype.NewBool(), closeChan : make(chan struct{}), @@ -84,11 +87,12 @@ func newFilePool(p *Pool, path string, flag int, perm os.FileMode, expire int) * return nil, err } return &File{ - File : *file, - pool : p, - flag : flag, - perm : perm, - path : path, + File : *file, + pool : p, + poolid : p.id.Val(), + flag : flag, + perm : perm, + path : path, }, nil }) pool.SetExpireFunc(func(i interface{}) { @@ -136,18 +140,22 @@ func (p *Pool) File() (*File, error) { go func() { for { select { - // 关闭事件 - case <- p.closeChan: - return + // 关闭事件 + case <- p.closeChan: + return // 监听事件 - case ev := <- p.watcher.Events: - // 如果文件被删除或者重命名,重建指针池 - if ev.Op & fsnotify.Remove == fsnotify.Remove || ev.Op & fsnotify.Rename == fsnotify.Rename { - p.pool.Close() - p.pool = newFilePool(p, f.Name(), f.flag, f.perm, f.pool.expire) - p.watcher.Remove(ev.Name) - } + case ev := <- p.watcher.Events: + // 如果文件被删除或者重命名,立即重建指针池 + if ev.Op & fsnotify.Remove == fsnotify.Remove || ev.Op & fsnotify.Rename == fsnotify.Rename { + // 原有的指针都不要了 + p.id.Add(1) + // Clear相当于重建指针池 + p.pool.Clear() + // 为保证原子操作,但又不想加锁, + // 这里再执行一次原子Add,将在两次Add中间可能分配出去的文件指针丢弃掉 + p.id.Add(1) + } } } }() @@ -165,6 +173,8 @@ func (p *Pool) Close() error { // 获得底层文件指针(返回error是标准库io.ReadWriteCloser接口实现) func (f *File) Close() error { - f.pool.pool.Put(f) + if f.poolid == f.pool.id.Val() { + f.pool.pool.Put(f) + } return nil } diff --git a/g/os/gfsnotify/gfsnotify.go b/g/os/gfsnotify/gfsnotify.go index 6ba1ecc7c..10cc71f6f 100644 --- a/g/os/gfsnotify/gfsnotify.go +++ b/g/os/gfsnotify/gfsnotify.go @@ -29,8 +29,8 @@ type Watcher struct { // 监听事件对象 type Event struct { - Path string // 文件绝对路径 - Op Op // 触发监听的文件操作 + Path string // 文件绝对路径 + Op Op // 触发监听的文件操作 Watcher *Watcher // 时间对应的监听对象 } @@ -201,7 +201,7 @@ func (w *Watcher) startEventLoop() { // 如果是文件删除事件,判断该文件是否存在,如果存在,那么将此事件认为“假删除”, // 并重新添加监控(底层fsnotify会自动删除掉监控,这里重新添加回去) w.watcher.Add(event.Path) - // 修改时间操作为重命名(相当于重命名为自身名称,最终名称没变) + // 修改事件操作为重命名(相当于重命名为自身名称,最终名称没变) event.Op = RENAME } else { // 如果是真实删除,那么递归删除监控信息 diff --git a/g/os/gtime/gtime.go b/g/os/gtime/gtime.go index cc1f6d619..a6f9fa528 100644 --- a/g/os/gtime/gtime.go +++ b/g/os/gtime/gtime.go @@ -8,11 +8,12 @@ package gtime import ( - "time" - "regexp" - "strings" - "strconv" "errors" + "gitee.com/johng/gf/g/util/gregex" + "regexp" + "strconv" + "strings" + "time" ) const ( @@ -28,12 +29,45 @@ const ( // "2018/10/31 - 16:38:46" // "2018-02-09", // 日期连接符号支持'-'或者'/' - TIME_REAGEX_PATTERN = `(\d{2,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]*)` + TIME_REAGEX_PATTERN1 = `(\d{2,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})([:\d]*)` + // 01-Nov-2018 11:50:28 + // 01/Nov/2018 11:50:28 + // 01/Nov/2018:11:50:28 + // 01/Nov/18 11:50:28 + TIME_REAGEX_PATTERN2 = `(\d{1,2}[-/][A-Za-z]{3,}[-/]\d{2,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})([:\d]*)` ) var ( // 使用正则判断会比直接使用ParseInLocation挨个轮训判断要快很多 - timeRegex, _ = regexp.Compile(TIME_REAGEX_PATTERN) + timeRegex1, _ = regexp.Compile(TIME_REAGEX_PATTERN1) + timeRegex2, _ = regexp.Compile(TIME_REAGEX_PATTERN2) + // 月份英文与阿拉伯数字对应关系 + monthMap = map[string]int { + "jan" : 1, + "feb" : 2, + "mar" : 3, + "apr" : 4, + "may" : 5, + "jun" : 6, + "jul" : 7, + "aug" : 8, + "sep" : 9, + "sept" : 9, + "oct" : 10, + "nov" : 11, + "dec" : 12, + "january" : 1, + "february" : 2, + "march" : 3, + "april" : 4, + "june" : 6, + "july" : 7, + "august" : 8, + "september" : 9, + "october" : 10, + "november" : 11, + "december" : 12, + } ) // 类似与js中的SetTimeout,一段时间后执行回调函数 @@ -96,13 +130,19 @@ func Datetime() string { return time.Now().Format("2006-01-02 15:04:05") } -// 字符串转换为时间对象 -func StrToTime(str string) (time.Time, error) { - var result time.Time - var local = time.Local - if match := timeRegex.FindStringSubmatch(str); len(match) > 0 { - var year, month, day, hour, min, sec, nsec int - var array []string +// 字符串转换为时间对象,第二个参数指定格式的format(如: Y-m-d H:i:s),当指定第二个参数时同StrToTimeFormat方法 +func StrToTime(str string, format...string) (*Time, error) { + if len(format) > 0 { + return StrToTimeFormat(str, format[0]) + } + var year, month, day, hour, min, sec, nsec int + var array, match []string + var local = time.Local + match = timeRegex1.FindStringSubmatch(str) + if len(match) > 0 { + for k, v := range match { + match[k] = strings.TrimSpace(v) + } // 日期(支持'-'或'/'连接符号) array = strings.Split(match[1], "-") if len(array) < 3 { @@ -117,32 +157,63 @@ func StrToTime(str string) (time.Time, error) { month, _ = strconv.Atoi(array[1]) day, _ = strconv.Atoi(array[2]) } - // 时间 - if len(match[2]) > 0 { - array = strings.Split(match[2], ":") - hour, _ = strconv.Atoi(array[0]) - if len(array) >= 2 { - min, _ = strconv.Atoi(array[1]) + } else { + match = timeRegex2.FindStringSubmatch(str) + if len(match) > 0 { + for k, v := range match { + match[k] = strings.TrimSpace(v) + } + // 日期(支持'-'或'/'连接符号) + array = strings.Split(match[1], "-") + if len(array) < 3 { + array = strings.Split(match[1], "/") } if len(array) >= 3 { - sec, _ = strconv.Atoi(array[2]) + day, _ = strconv.Atoi(array[0]) + if v, ok := monthMap[strings.ToLower(array[1])]; ok { + month = v + } else { + return nil, errors.New("invalid month:" + array[1]) + } + // 年是否为缩写,如果是,那么需要补上前缀 + year, _ = strconv.Atoi(array[2]) + if year < 100 { + year = int(time.Now().Year()/100)*100 + year + } } } - // 纳秒,检查并执行位补齐 - if len(match[3]) > 0 { - nsec, _ = strconv.Atoi(match[3]) - for i := 0; i < 9 - len(match[3]); i++ { - nsec *= 10 - } + } + if len(match) == 0 { + return nil, errors.New("unsupported time format") + } + + // 时间 + if len(match[2]) > 0 { + array = strings.Split(match[2], ":") + hour, _ = strconv.Atoi(array[0]) + if len(array) >= 2 { + min, _ = strconv.Atoi(array[1]) } - // 如果字符串中有时区信息(具体时间信息),那么执行时区转换,将时区转成UTC - if match[4] != "" && match[6] == "" { - match[6] = "000000" + if len(array) >= 3 { + sec, _ = strconv.Atoi(array[2]) } - // 如果offset有值优先处理offset,否则处理后面的时区名称 - if match[6] != "" { - zone := strings.Replace(match[6], ":", "", -1) - zone = strings.TrimLeft(zone, "+-") + } + // 纳秒,检查并执行位补齐 + if len(match[3]) > 0 { + nsec, _ = strconv.Atoi(match[3]) + for i := 0; i < 9 - len(match[3]); i++ { + nsec *= 10 + } + } + // 如果字符串中有时区信息(具体时间信息),那么执行时区转换,将时区转成UTC + if match[4] != "" && match[6] == "" { + match[6] = "000000" + } + // 如果offset有值优先处理offset,否则处理后面的时区名称 + if match[6] != "" { + zone := strings.Replace(match[6], ":", "", -1) + zone = strings.TrimLeft(zone, "+-") + if len(zone) <= 6 { zone += strings.Repeat("0", 6 - len(zone)) h, _ := strconv.Atoi(zone[0 : 2]) m, _ := strconv.Atoi(zone[2 : 4]) @@ -158,65 +229,81 @@ func StrToTime(str string) (time.Time, error) { operation = "-" } switch operation { - case "+": - if h > 0 { - hour -= h - } - if m > 0 { - min -= m - } - if s > 0 { - sec -= s - } - case "-": - if h > 0 { - hour += h - } - if m > 0 { - min += m - } - if s > 0 { - sec += s - } + case "+": + if h > 0 { + hour -= h + } + if m > 0 { + min -= m + } + if s > 0 { + sec -= s + } + case "-": + if h > 0 { + hour += h + } + if m > 0 { + min += m + } + if s > 0 { + sec += s + } } } } - // 生成UTC时间对象 - result = time.Date(year, time.Month(month), day, hour, min, sec, nsec, local) - return result, nil } - return result, errors.New("unsupported time format") + // 统一生成UTC时间对象 + return NewFromTime(time.Date(year, time.Month(month), day, hour, min, sec, nsec, local)), nil } // 时区转换 -func ConvertZone(strTime string, toZone string, fromZone...string) (time.Time, error) { +func ConvertZone(strTime string, toZone string, fromZone...string) (*Time, error) { t, err := StrToTime(strTime) if err != nil { - return time.Time{}, err + return nil, err } if len(fromZone) > 0 { if l, err := time.LoadLocation(fromZone[0]); err != nil { - return time.Time{}, err + return nil, err } else { - t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), l) + t.Time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Time.Second(), t.Time.Nanosecond(), l) } } if l, err := time.LoadLocation(toZone); err != nil { - return time.Time{}, err + return nil, err } else { - return t.In(l), nil + return t.ToLocation(l), nil } } // 字符串转换为时间对象,指定字符串时间格式,format格式形如:Y-m-d H:i:s -func StrToTimeFormat(str string, format string) (time.Time, error) { +func StrToTimeFormat(str string, format string) (*Time, error) { return StrToTimeLayout(str, formatToStdLayout(format)) } + // 字符串转换为时间对象,通过标准库layout格式进行解析,layout格式形如:2006-01-02 15:04:05 -func StrToTimeLayout(str string, layout string) (time.Time, error) { +func StrToTimeLayout(str string, layout string) (*Time, error) { if t, err := time.ParseInLocation(layout, str, time.Local); err == nil { - return t, nil + return NewFromTime(t), nil } else { - return time.Time{}, err + return nil, err } } + +// 从文本内容中解析时间,并返回解析成功的时间对象。注意当文本中存在多个时间时,会解析第一个。 +// format参数可以指定需要解析的时间格式。 +func ParseTimeFromContent(content string, format...string) *Time { + if len(format) > 0 { + if match, err := gregex.MatchString(formatToRegexPattern(format[0]), content); err == nil && len(match) > 0 { + return NewFromStrFormat(match[0], format[0]) + } + } else { + if match := timeRegex1.FindStringSubmatch(content); len(match) >= 1 { + return NewFromStr(match[0]) + } else if match := timeRegex2.FindStringSubmatch(content); len(match) >= 1 { + return NewFromStr(match[0]) + } + } + return nil +} \ No newline at end of file diff --git a/g/os/gtime/gtime_format.go b/g/os/gtime/gtime_format.go index 6b32cf9b4..2005cac54 100644 --- a/g/os/gtime/gtime_format.go +++ b/g/os/gtime/gtime_format.go @@ -7,8 +7,9 @@ package gtime import ( - "strings" "bytes" + "gitee.com/johng/gf/g/util/gregex" + "strings" ) var ( @@ -68,6 +69,7 @@ func formatToStdLayout(format string) string { default: if f, ok := formats[format[i]]; ok { + // 有几个转换的符号需要特殊处理 switch format[i] { case 'j': b.WriteString("02") @@ -92,6 +94,14 @@ func formatToStdLayout(format string) string { return b.String() } +// 将format格式转换为正则表达式规则 +func formatToRegexPattern(format string) string { + s := gregex.Quote(formatToStdLayout(format)) + s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s) + s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s) + return s +} + // 格式化,使用自定义日期格式 func (t *Time) Format(format string) string { s := "" @@ -109,6 +119,7 @@ func (t *Time) Format(format string) string { default: if f, ok := formats[format[i]]; ok { r := t.Time.Format(f) + // 有几个转换的符号需要特殊处理 switch format[i] { case 'j': s += strings.Replace(r, "=j=0", "", -1) diff --git a/g/os/gtime/gtime_time.go b/g/os/gtime/gtime_time.go index 9d99dabda..cb9c074f7 100644 --- a/g/os/gtime/gtime_time.go +++ b/g/os/gtime/gtime_time.go @@ -39,9 +39,7 @@ func NewFromTime (t time.Time) *Time { // 从字符串转换为时间对象,复杂的时间字符串需要给定格式 func NewFromStr (str string) *Time { if t, err := StrToTime(str); err == nil { - return &Time{ - t, - } + return t } return nil } @@ -49,9 +47,7 @@ func NewFromStr (str string) *Time { // 从字符串转换为时间对象,指定字符串时间格式,format格式形如:Y-m-d H:i:s func NewFromStrFormat (str string, format string) *Time { if t, err := StrToTimeFormat(str, format); err == nil { - return &Time{ - t, - } + return t } return nil } @@ -59,9 +55,7 @@ func NewFromStrFormat (str string, format string) *Time { // 从字符串转换为时间对象,通过标准库layout格式进行解析,layout格式形如:2006-01-02 15:04:05 func NewFromStrLayout (str string, layout string) *Time { if t, err := StrToTimeLayout(str, layout); err == nil { - return &Time{ - t, - } + return t } return nil } diff --git a/g/util/gconv/gconv_time.go b/g/util/gconv/gconv_time.go index be1bc94f9..fefc07b83 100644 --- a/g/util/gconv/gconv_time.go +++ b/g/util/gconv/gconv_time.go @@ -18,13 +18,13 @@ func Time(i interface{}, format...string) time.Time { // 优先使用用户输入日期格式进行转换 if len(format) > 0 { t, _ := gtime.StrToTimeFormat(s, format[0]) - return t + return t.Time } if gstr.IsNumeric(s) { return gtime.NewFromTimeStamp(Int64(s)).Time } else { t, _ := gtime.StrToTime(s) - return t + return t.Time } } diff --git a/geg/os/gfile/gfile_contents.go b/geg/os/gfile/gfile_contents.go index f6f5ac31a..a2cc426c2 100644 --- a/geg/os/gfile/gfile_contents.go +++ b/geg/os/gfile/gfile_contents.go @@ -12,7 +12,9 @@ func main() { 789 ` gfile.PutContents(path, content) + fmt.Println(gfile.Size(path)) fmt.Println(gfile.GetBinContentsTilCharByPath(path, '\n', 0)) fmt.Println(gfile.GetBinContentsTilCharByPath(path, '\n', 3)) fmt.Println(gfile.GetBinContentsTilCharByPath(path, '\n', 8)) + fmt.Println(gfile.GetBinContentsTilCharByPath(path, '\n', 12)) } diff --git a/geg/os/gtime/gtime_parsertime.go b/geg/os/gtime/gtime_parsertime.go new file mode 100644 index 000000000..935999856 --- /dev/null +++ b/geg/os/gtime/gtime_parsertime.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "gitee.com/johng/gf/g/os/gtime" +) + +func main() { + content := ` + +2018-11-01 14:30:39 -- 67 -- assignDoctor:request -- {"问诊ID":"1017467","操作类型":2,"操作ID":52339491,"医生ID":52339491,"是否主动接单":"是"} +2018-11-01 14:35:55 -- 73 -- throwIntoPool:request -- {"问诊Id":1017474,"当前Id":null,"当前角色":null} +` + if t := gtime.ParseTimeFromContent(content); t != nil { + fmt.Println(t.String()) + fmt.Println(t.UTC()) + fmt.Println(gtime.Now().UTC()) + } else { + panic("cannot parse time from content") + } + + //if t := gtime.ParseTimeFromContent(content, "d/M/Y:H:i:s +0800"); t != nil { + // fmt.Println(t.String()) + //} else { + // panic("cannot parse time from content") + //} +} diff --git a/geg/os/gtime/gtime_regex.go b/geg/os/gtime/gtime_regex1.go similarity index 92% rename from geg/os/gtime/gtime_regex.go rename to geg/os/gtime/gtime_regex1.go index 03f0d51fa..1cc0f2055 100644 --- a/geg/os/gtime/gtime_regex.go +++ b/geg/os/gtime/gtime_regex1.go @@ -7,7 +7,7 @@ import ( ) func main() { - timeRegex, err := regexp.Compile(gtime.TIME_REAGEX_PATTERN) + timeRegex, err := regexp.Compile(gtime.TIME_REAGEX_PATTERN1) if err != nil { panic(err) } diff --git a/geg/os/gtime/gtime_regex2.go b/geg/os/gtime/gtime_regex2.go new file mode 100644 index 000000000..3693e449f --- /dev/null +++ b/geg/os/gtime/gtime_regex2.go @@ -0,0 +1,32 @@ +package main + +import ( + "regexp" + "fmt" + "gitee.com/johng/gf/g/os/gtime" +) + +func main() { + timeRegex, err := regexp.Compile(gtime.TIME_REAGEX_PATTERN2) + if err != nil { + panic(err) + } + array := []string{ + "01-Nov-2018 11:50:28 +0805 LMT", + "01-Nov-2018T15:04:05Z07:00", + "01-Nov-2018T01:19:15+08:00", + "01-Nov-2018 11:50:28 +0805 LMT", + "01/Nov/18 11:50:28", + "01/Nov/2018 11:50:28", + "01/Nov/2018:11:50:28", + "01/Nov/2018", + } + for _, s := range array { + fmt.Println(s) + match := timeRegex.FindStringSubmatch(s) + for k, v := range match { + fmt.Println(k, v) + } + fmt.Println() + } +} diff --git a/geg/os/gtime/gtime_strtotime.go b/geg/os/gtime/gtime_strtotime.go index e5c3237a4..09e51c3a8 100644 --- a/geg/os/gtime/gtime_strtotime.go +++ b/geg/os/gtime/gtime_strtotime.go @@ -21,6 +21,15 @@ func main() { "18/02/09 12:16", "18/02/09 12", "18/02/09 +0805 LMT", + "01/Nov/2018:13:28:13 +0800", + "01-Nov-2018 11:50:28 +0805 LMT", + "01-Nov-2018T15:04:05Z07:00", + "01-Nov-2018T01:19:15+08:00", + "01-Nov-2018 11:50:28 +0805 LMT", + "01/Nov/18 11:50:28", + "01/Nov/2018 11:50:28", + "01/Nov/2018:11:50:28", + "01/Nov/2018", } cstLocal, _ := time.LoadLocation("Asia/Shanghai") for _, s := range array { diff --git a/geg/other/test2.go b/geg/other/test2.go index 4b007ae67..cc4c3f73b 100644 --- a/geg/other/test2.go +++ b/geg/other/test2.go @@ -2,19 +2,11 @@ package main import ( "fmt" - "gitee.com/johng/gf/g/os/gfpool" - "os" - "time" + "gitee.com/johng/gf/g/util/gregex" ) func main() { - for { - time.Sleep(time.Second) - if f, err := gfpool.Open("/home/john/temp/log.log", os.O_RDONLY, 0666, 60000000*1000); err == nil { - s, _ := f.Stat() - fmt.Println(f.Name(), s.Name()) - } else { - fmt.Println(err) - } - } + s := `[0-9][0-9]/Jan/[0-9][0-9][0-9][0-9]:[0-9][0-9]:[0-9][0-9]:[0-9][0-9] \+[0-9][0-9][0-9][0-9]` + s,_ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s) + fmt.Println(s) } \ No newline at end of file