Explorar o código

更新初始化失败处理函数

将多个文件中的 `InitFailedError` 函数调用替换为 `InitFailed`,并添加了对 Windows 时区的支持。
SongZihuan hai 6 días
pai
achega
c219ab6bde

+ 8 - 0
CHANGELOG.md

@@ -6,11 +6,19 @@
 
 ## [未发布]
 
+### 新增
+
+- 在`Windows`平台上可以使用`Windows`时区信息(最终转换为`IANA`时区信息呈现)。
+
 ### 修复
 
 - 修复`format`中遗漏的测试函数。
 - 在`Console API`调用前进行`HasConsole`判断,以避免一些潜在的错误。
 
+### 重构
+
+- 完善时区系统,获取本地时间时可以读取到`IANA`时区信息。
+
 ### 文档
 
 - 添加行为准则。

+ 8 - 3
src/cmd/catv1/main.go

@@ -4,8 +4,13 @@
 
 package main
 
+// 必须明确导入 prerun 包 (虽然下面的import确实导入了 prerun 包,但此处重复写一次表示冗余,以免在某些情况下本包不适用 prerun 后,下方的导入被自动删除)
 import (
-	"github.com/SongZihuan/BackendServerTemplate/src/cmd/globalmain"
+	_ "github.com/SongZihuan/BackendServerTemplate/src/cmd/prerun"
+)
+
+import (
+	"github.com/SongZihuan/BackendServerTemplate/src/cmd/prerun"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/check"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/license"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/report"
@@ -30,11 +35,11 @@ var name string = global.Name
 var inputConfigFilePath string = "config.yaml"
 
 func main() {
-	err := globalmain.PreRun()
+	err := prerun.PreRun()
 	if err != nil {
 		exitutils.Exit(err)
 	}
-	defer globalmain.PostRun()
+	defer prerun.PostRun()
 
 	cmd := &cobra.Command{
 		Use:           global.Name,

+ 0 - 32
src/cmd/globalmain/main.go

@@ -1,32 +0,0 @@
-// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package globalmain
-
-import (
-	"github.com/SongZihuan/BackendServerTemplate/src/logger"
-	"github.com/SongZihuan/BackendServerTemplate/src/utils/consoleutils"
-	"github.com/SongZihuan/BackendServerTemplate/src/utils/exitutils"
-)
-
-func PreRun() (exitCode error) {
-	var err error
-
-	err = consoleutils.SetConsoleCPSafe(consoleutils.CodePageUTF8)
-	if err != nil {
-		return exitutils.InitFailedErrorForWin32ConsoleModule(err.Error())
-	}
-
-	err = logger.InitBaseLogger()
-	if err != nil {
-		return exitutils.InitFailedErrorForLoggerModule(err.Error())
-	}
-
-	return nil
-}
-
-func PostRun() {
-	logger.CloseLogger()
-	logger.Recover()
-}

+ 8 - 3
src/cmd/lionv1/main.go

@@ -4,9 +4,14 @@
 
 package main
 
+// 必须明确导入 prerun 包 (虽然下面的import确实导入了 prerun 包,但此处重复写一次表示冗余,以免在某些情况下本包不适用 prerun 后,下方的导入被自动删除)
+import (
+	_ "github.com/SongZihuan/BackendServerTemplate/src/cmd/prerun"
+)
+
 import (
 	"fmt"
-	"github.com/SongZihuan/BackendServerTemplate/src/cmd/globalmain"
+	"github.com/SongZihuan/BackendServerTemplate/src/cmd/prerun"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/check"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/license"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/report"
@@ -27,11 +32,11 @@ var reload bool = false
 var ppid int = 0
 
 func main() {
-	err := globalmain.PreRun()
+	err := prerun.PreRun()
 	if err != nil {
 		exitutils.Exit(err)
 	}
-	defer globalmain.PostRun()
+	defer prerun.PostRun()
 
 	cmd := &cobra.Command{
 		Use:           global.Name,

+ 46 - 0
src/cmd/prerun/main.go

@@ -0,0 +1,46 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package prerun
+
+// 必须明确导入 global 包 (虽然下面的import确实导入了 global 包,但此处重复写一次表示冗余,以免在某些情况下本包不适用 global 后,下方的导入被自动删除)
+import (
+	_ "github.com/SongZihuan/BackendServerTemplate/src/global"
+)
+
+import (
+	"github.com/SongZihuan/BackendServerTemplate/src/global"
+	"github.com/SongZihuan/BackendServerTemplate/src/logger"
+	"github.com/SongZihuan/BackendServerTemplate/src/utils/consoleutils"
+	"github.com/SongZihuan/BackendServerTemplate/src/utils/exitutils"
+)
+
+func PreRun() (exitCode error) {
+	var err error
+
+	if global.UTCLocation == nil {
+		return exitutils.InitFailedForTimeLocationModule("can not get utc location")
+	}
+
+	if global.LocalLocation == nil {
+		return exitutils.InitFailedForTimeLocationModule("can not get local location")
+	}
+
+	err = consoleutils.SetConsoleCPSafe(consoleutils.CodePageUTF8)
+	if err != nil {
+		return exitutils.InitFailedForWin32ConsoleModule(err.Error())
+	}
+
+	err = logger.InitBaseLogger()
+	if err != nil {
+		return exitutils.InitFailedForLoggerModule(err.Error())
+	}
+
+	return nil
+}
+
+func PostRun() {
+	logger.CloseLogger()
+	logger.Recover()
+}

+ 8 - 3
src/cmd/tigerv1/main.go

@@ -4,9 +4,14 @@
 
 package main
 
+// 必须明确导入 prerun 包 (虽然下面的import确实导入了 prerun 包,但此处重复写一次表示冗余,以免在某些情况下本包不适用 prerun 后,下方的导入被自动删除)
+import (
+	_ "github.com/SongZihuan/BackendServerTemplate/src/cmd/prerun"
+)
+
 import (
 	"fmt"
-	"github.com/SongZihuan/BackendServerTemplate/src/cmd/globalmain"
+	"github.com/SongZihuan/BackendServerTemplate/src/cmd/prerun"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/check"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/license"
 	"github.com/SongZihuan/BackendServerTemplate/src/cmdparser/report"
@@ -27,11 +32,11 @@ var reload bool = false
 var ppid int = 0
 
 func main() {
-	err := globalmain.PreRun()
+	err := prerun.PreRun()
 	if err != nil {
 		exitutils.Exit(err)
 	}
-	defer globalmain.PostRun()
+	defer prerun.PostRun()
 
 	cmd := &cobra.Command{
 		Use:           global.Name,

+ 2 - 2
src/cmdparser/check/cmd.go

@@ -5,7 +5,7 @@
 package check
 
 import (
-	"github.com/SongZihuan/BackendServerTemplate/src/cmd/globalmain"
+	"github.com/SongZihuan/BackendServerTemplate/src/cmd/prerun"
 	checkv1 "github.com/SongZihuan/BackendServerTemplate/src/mainfunc/check/v1"
 	"github.com/spf13/cobra"
 )
@@ -21,7 +21,7 @@ var CMD = &cobra.Command{
 		cmd.SilenceUsage = true
 		cmd.SilenceErrors = true
 
-		err := globalmain.PreRun()
+		err := prerun.PreRun()
 		if err != nil {
 			return err
 		}

+ 3 - 4
src/cmdparser/version/cmd.go

@@ -38,10 +38,9 @@ func init() {
 func printVersion(writer io.Writer) (int, error) {
 	res := new(strings.Builder)
 	res.WriteString(fmt.Sprintf("Version: %s\n", global.Version))
-	res.WriteString(fmt.Sprintf("Build Date (UTC): %s\n", global.BuildTime.In(time.UTC).Format(time.DateTime)))
-	if time.Local != nil && time.Local.String() != time.UTC.String() {
-		zone, _ := time.Now().Local().Zone()
-		res.WriteString(fmt.Sprintf("Build Date (%s): %s\n", zone, global.BuildTime.In(time.Local).Format(time.DateTime)))
+	res.WriteString(fmt.Sprintf("Build Date (UTC): %s\n", global.BuildTime.In(global.UTCLocation).Format(time.DateTime)))
+	if global.LocalLocation.String() != global.UTCLocation.String() {
+		res.WriteString(fmt.Sprintf("Build Date (%s): %s\n", global.LocalLocation.String(), global.BuildTime.In(global.LocalLocation).Format(time.DateTime)))
 	}
 	res.WriteString(fmt.Sprintf("Compiler: %s\n", runtime.Version()))
 	res.WriteString(fmt.Sprintf("OS: %s\n", runtime.GOOS))

+ 12 - 12
src/config/global_config.go

@@ -9,6 +9,7 @@ import (
 	"github.com/SongZihuan/BackendServerTemplate/src/config/configparser"
 	"github.com/SongZihuan/BackendServerTemplate/src/global"
 	"github.com/SongZihuan/BackendServerTemplate/src/utils/cleanstringutils"
+	"github.com/SongZihuan/BackendServerTemplate/src/utils/timeutils"
 	"strings"
 	"time"
 )
@@ -49,7 +50,7 @@ func (d *GlobalConfig) setDefault(c *configInfo) configerror.Error {
 		d.Timezone = strings.ToLower(d.Timezone)
 	}
 
-	d.Time = time.Now().In(time.UTC)
+	d.Time = time.Now().In(global.UTCLocation)
 	d.UTCDate = d.Time.Format(time.DateTime)
 	d.Timestamp = d.Time.Unix()
 
@@ -72,37 +73,36 @@ func (d *GlobalConfig) process(c *configInfo) (cfgErr configerror.Error) {
 
 	var location *time.Location
 	if strings.ToLower(d.Timezone) == "utc" {
-		location = time.UTC
+		location = global.UTCLocation
 		if location == nil {
-			location = time.Local
+			location = timeutils.GetLocalTimezone()
 		}
 	} else if strings.ToLower(d.Timezone) == "local" {
-		location = time.Local
+		location = timeutils.GetLocalTimezone()
 		if location == nil {
-			location = time.UTC
+			location = global.UTCLocation
 		}
 	} else {
 		var err error
-		location, err = time.LoadLocation(d.Timezone)
+		location, err = timeutils.LoadLocation(d.Timezone)
 		if err != nil || location == nil {
-			location = time.UTC
+			location = global.UTCLocation
 		}
 
 		if location != nil {
-			location = time.Local
+			location = timeutils.GetLocalTimezone()
 		}
 	}
 
-	if location == nil {
+	if location == nil || strings.ToLower(location.String()) == "local" {
 		if d.Timezone == "utc" || d.Timezone == "local" {
 			return configerror.NewErrorf("can not get location UTC or Local")
 		}
-
 		return configerror.NewErrorf("can not get location UTC, Local or %s", d.Timezone)
-	} else {
-		global.Location = location
 	}
 
+	global.Location = location
+
 	return nil
 }
 

+ 13 - 0
src/global/tzdata.go

@@ -0,0 +1,13 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build !systemtzdata
+
+package global
+
+import (
+	_ "github.com/SongZihuan/BackendServerTemplate/src/utils/timeutils/tzdata"
+)
+
+// 默认情况下加载 go 自带的时区包,除非使用 systemtzdata 明确使用系统的时区包

+ 4 - 1
src/global/variabl.go

@@ -6,6 +6,7 @@ package global
 
 import (
 	resource "github.com/SongZihuan/BackendServerTemplate"
+	"github.com/SongZihuan/BackendServerTemplate/src/utils/timeutils"
 	"time"
 )
 
@@ -28,5 +29,7 @@ var (
 
 // Location 以下变量需要在配置文件加载完毕后才可调用
 var (
-	Location = time.UTC
+	UTCLocation   = time.UTC
+	LocalLocation = timeutils.GetLocalTimezone()
+	Location      = time.UTC
 )

+ 2 - 5
src/logger/logformat/logdata.go

@@ -37,16 +37,13 @@ type LogData struct {
 func GetLogData(level loglevel.LoggerLevel, msg string, now time.Time) *LogData {
 	var res = new(LogData)
 
-	zone := global.Location.String()
-	if strings.ToLower(zone) == "local" {
-		zone, _ = now.Zone()
-	}
+	now = now.In(global.Location)
 
 	res.LogLevel = level
 	res.Level = strings.ToUpper(string(level))
 	res.Now = now
 	res.Date = now.Format(time.DateTime)
-	res.Zone = zone
+	res.Zone = global.Location.String()
 	res.Timestamp = now.Unix()
 	res.Name = global.Name
 	res.Version = global.Version

+ 17 - 17
src/mainfunc/cat/v1/main.go

@@ -24,7 +24,7 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string) (exit
 
 	err = initServiceConfig()
 	if err != nil {
-		return exitutils.InitFailedError("service config", err.Error())
+		return exitutils.InitFailed("service config", err.Error())
 	}
 
 	// 定义服务配置
@@ -39,7 +39,7 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string) (exit
 	prg := NewRunProgram(inputConfigFilePath)
 	s, err := service.New(prg, svcConfig)
 	if err != nil {
-		return exitutils.InitFailedError("Service New", err.Error())
+		return exitutils.InitFailed("Service New", err.Error())
 	}
 
 	_ = s.Run()
@@ -51,7 +51,7 @@ func MainV1Install(cmd *cobra.Command, args []string) (exitCode error) {
 
 	err = initInstallServiceConfig(args)
 	if err != nil {
-		return exitutils.InitFailedError("service config", err.Error())
+		return exitutils.InitFailed("service config", err.Error())
 	}
 
 	// 定义服务配置
@@ -66,13 +66,13 @@ func MainV1Install(cmd *cobra.Command, args []string) (exitCode error) {
 	prg := NewProgram()
 	s, err := service.New(prg, svcConfig)
 	if err != nil {
-		return exitutils.InitFailedError("Service New", err.Error())
+		return exitutils.InitFailed("Service New", err.Error())
 	}
 
 	// 安装服务
 	err = s.Install()
 	if err != nil {
-		return exitutils.InitFailedError("Service Install", err.Error())
+		return exitutils.InitFailed("Service Install", err.Error())
 	}
 
 	return exitutils.SuccessExitSimple("Service Install Success")
@@ -83,7 +83,7 @@ func MainV1UnInstall(cmd *cobra.Command, args []string) (exitCode error) {
 
 	err = initServiceConfig()
 	if err != nil {
-		return exitutils.InitFailedError("service config", err.Error())
+		return exitutils.InitFailed("service config", err.Error())
 	}
 
 	// 定义服务配置
@@ -98,13 +98,13 @@ func MainV1UnInstall(cmd *cobra.Command, args []string) (exitCode error) {
 	prg := NewProgram()
 	s, err := service.New(prg, svcConfig)
 	if err != nil {
-		return exitutils.InitFailedError("Service New", err.Error())
+		return exitutils.InitFailed("Service New", err.Error())
 	}
 
 	// 卸载服务
 	err = s.Uninstall()
 	if err != nil {
-		return exitutils.InitFailedError("Service Remove", err.Error())
+		return exitutils.InitFailed("Service Remove", err.Error())
 	}
 
 	return exitutils.SuccessExitSimple("Service Remove Success")
@@ -115,7 +115,7 @@ func MainV1Start(cmd *cobra.Command, args []string) (exitCode error) {
 
 	err = initServiceConfig()
 	if err != nil {
-		return exitutils.InitFailedError("service config", err.Error())
+		return exitutils.InitFailed("service config", err.Error())
 	}
 
 	// 定义服务配置
@@ -130,13 +130,13 @@ func MainV1Start(cmd *cobra.Command, args []string) (exitCode error) {
 	prg := NewProgram()
 	s, err := service.New(prg, svcConfig)
 	if err != nil {
-		return exitutils.InitFailedError("Service New", err.Error())
+		return exitutils.InitFailed("Service New", err.Error())
 	}
 
 	// 启动服务
 	err = s.Start()
 	if err != nil {
-		return exitutils.InitFailedError("Service Start", err.Error())
+		return exitutils.InitFailed("Service Start", err.Error())
 	}
 
 	return exitutils.SuccessExitSimple("Service Start Success")
@@ -147,7 +147,7 @@ func MainV1Stop(cmd *cobra.Command, args []string) (exitCode error) {
 
 	err = initServiceConfig()
 	if err != nil {
-		return exitutils.InitFailedError("service config", err.Error())
+		return exitutils.InitFailed("service config", err.Error())
 	}
 
 	// 定义服务配置
@@ -162,13 +162,13 @@ func MainV1Stop(cmd *cobra.Command, args []string) (exitCode error) {
 	prg := NewProgram()
 	s, err := service.New(prg, svcConfig)
 	if err != nil {
-		return exitutils.InitFailedError("Service New", err.Error())
+		return exitutils.InitFailed("Service New", err.Error())
 	}
 
 	// 停止服务
 	err = s.Stop()
 	if err != nil {
-		return exitutils.InitFailedError("Service Stop", err.Error())
+		return exitutils.InitFailed("Service Stop", err.Error())
 	}
 
 	return exitutils.SuccessExitSimple("Service Stop Success")
@@ -179,7 +179,7 @@ func MainV1Restart(cmd *cobra.Command, args []string) (exitCode error) {
 
 	err = initServiceConfig()
 	if err != nil {
-		return exitutils.InitFailedError("service config", err.Error())
+		return exitutils.InitFailed("service config", err.Error())
 	}
 
 	// 定义服务配置
@@ -194,13 +194,13 @@ func MainV1Restart(cmd *cobra.Command, args []string) (exitCode error) {
 	prg := NewProgram()
 	s, err := service.New(prg, svcConfig)
 	if err != nil {
-		return exitutils.InitFailedError("Service New", err.Error())
+		return exitutils.InitFailed("Service New", err.Error())
 	}
 
 	// 重启服务
 	err = s.Restart()
 	if err != nil {
-		return exitutils.InitFailedError("Service Restart", err.Error())
+		return exitutils.InitFailed("Service Restart", err.Error())
 	}
 
 	return exitutils.SuccessExitSimple("Service Restart Success")

+ 3 - 3
src/mainfunc/cat/v1/service.go

@@ -55,7 +55,7 @@ func (p *Program) Start(s service.Service) error {
 
 	configProvider, err := configparser.NewProvider(p.configPath, nil)
 	if err != nil {
-		p.exitCode = exitutils.InitFailedError("Get config file provider", err.Error())
+		p.exitCode = exitutils.InitFailed("Get config file provider", err.Error())
 		return err
 	}
 
@@ -65,7 +65,7 @@ func (p *Program) Start(s service.Service) error {
 		Provider:       configProvider,
 	})
 	if err != nil {
-		p.exitCode = exitutils.InitFailedError("Config file read and parser", err.Error())
+		p.exitCode = exitutils.InitFailed("Config file read and parser", err.Error())
 		return err
 	}
 
@@ -75,7 +75,7 @@ func (p *Program) Start(s service.Service) error {
 		StopWaitTime: config.Data().Server.StopWaitTimeDuration,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Server Example1", err.Error())
+		return exitutils.InitFailed("Server Example1", err.Error())
 	}
 
 	logger.Infof("Start to run server example 3")

+ 8 - 8
src/mainfunc/lion/v1/main.go

@@ -28,7 +28,7 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string, outpu
 		AutoReload: ppid != 0,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Get config file provider", err.Error())
+		return exitutils.InitFailed("Get config file provider", err.Error())
 	}
 
 	err = config.InitConfig(&config.ConfigOption{
@@ -37,14 +37,14 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string, outpu
 		Provider:       configProvider,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Config file read and parser", err.Error())
+		return exitutils.InitFailed("Config file read and parser", err.Error())
 	}
 
 	sigchan := signalwatcher.NewSignalExitChannel()
 
 	consolechan, consolewaitexitchan, err := consolewatcher.NewWin32ConsoleExitChannel()
 	if err != nil {
-		return exitutils.InitFailedError("Win32 console channel", err.Error())
+		return exitutils.InitFailed("Win32 console channel", err.Error())
 	}
 
 	ppidchan := restart.PpidWatcher(ppid)
@@ -53,29 +53,29 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string, outpu
 		StopWaitTime: config.Data().Server.StopWaitTimeDuration,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Server Controller", err.Error())
+		return exitutils.InitFailed("Server Controller", err.Error())
 	}
 
 	ser1, _, err := example1.NewServerExample1(&example1.ServerExample1Option{
 		LockThread: true,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Server Example1", err.Error())
+		return exitutils.InitFailed("Server Example1", err.Error())
 	}
 
 	err = ctrl.AddServer(ser1)
 	if err != nil {
-		return exitutils.InitFailedError("Add Server Example1", err.Error())
+		return exitutils.InitFailed("Add Server Example1", err.Error())
 	}
 
 	ser2, _, err := example2.NewServerExample2(nil)
 	if err != nil {
-		return exitutils.InitFailedError("Server Example2", err.Error())
+		return exitutils.InitFailed("Server Example2", err.Error())
 	}
 
 	err = ctrl.AddServer(ser2)
 	if err != nil {
-		return exitutils.InitFailedError("Add Server Example2", err.Error())
+		return exitutils.InitFailed("Add Server Example2", err.Error())
 	}
 
 	logger.Infof("Start to run server controller")

+ 3 - 3
src/mainfunc/restart/v1/main.go

@@ -22,7 +22,7 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string, outpu
 		AutoReload: false,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Get config file provider", err.Error())
+		return exitutils.InitFailed("Get config file provider", err.Error())
 	}
 
 	err = config.InitConfig(&config.ConfigOption{
@@ -31,14 +31,14 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string, outpu
 		Provider:       configProvider,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Config file read and parser", err.Error())
+		return exitutils.InitFailed("Config file read and parser", err.Error())
 	}
 
 	sigchan := signalwatcher.NewSignalExitChannel()
 
 	consolechan, consolewaitexitchan, err := consolewatcher.NewWin32ConsoleExitChannel()
 	if err != nil {
-		return exitutils.InitFailedError("Win32 console channel", err.Error())
+		return exitutils.InitFailed("Win32 console channel", err.Error())
 	}
 
 	stopchan := restart.RunRestart()

+ 4 - 4
src/mainfunc/tiger/v1/main.go

@@ -26,7 +26,7 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string, outpu
 		AutoReload: ppid != 0,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Get config file provider", err.Error())
+		return exitutils.InitFailed("Get config file provider", err.Error())
 	}
 
 	err = config.InitConfig(&config.ConfigOption{
@@ -35,14 +35,14 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string, outpu
 		Provider:       configProvider,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Config file read and parser", err.Error())
+		return exitutils.InitFailed("Config file read and parser", err.Error())
 	}
 
 	sigchan := signalwatcher.NewSignalExitChannel()
 
 	consolechan, consolewaitexitchan, err := consolewatcher.NewWin32ConsoleExitChannel()
 	if err != nil {
-		return exitutils.InitFailedError("Win32 console channel", err.Error())
+		return exitutils.InitFailed("Win32 console channel", err.Error())
 	}
 
 	ppidchan := restart.PpidWatcher(ppid)
@@ -51,7 +51,7 @@ func MainV1(cmd *cobra.Command, args []string, inputConfigFilePath string, outpu
 		StopWaitTime: config.Data().Server.StopWaitTimeDuration,
 	})
 	if err != nil {
-		return exitutils.InitFailedError("Server Example1", err.Error())
+		return exitutils.InitFailed("Server Example1", err.Error())
 	}
 
 	logger.Infof("Start to run server example 1")

+ 1 - 0
src/utils/cleanstringutils/oneline.go

@@ -7,6 +7,7 @@ package cleanstringutils
 import "strings"
 
 func GetStringOneLine(data string) (res string) {
+	res = strings.Replace(data, "\t", "    ", -1)
 	res = strings.Replace(data, "\r", "", -1)
 	res = strings.Split(res, "\n")[0]
 	res = strings.TrimSpace(res)

+ 16 - 3
src/utils/exitutils/exit.go

@@ -48,7 +48,7 @@ func getExitCode(defaultExitCode int, exitCode ...int) (ec ExitCode) {
 	return ec
 }
 
-func InitFailedErrorForWin32ConsoleModule(reason string, exitCode ...int) ExitCode {
+func InitFailedForWin32ConsoleModule(reason string, exitCode ...int) ExitCode {
 	if reason == "" {
 		reason = "no reason"
 	}
@@ -61,7 +61,20 @@ func InitFailedErrorForWin32ConsoleModule(reason string, exitCode ...int) ExitCo
 	return ec
 }
 
-func InitFailedErrorForLoggerModule(reason string, exitCode ...int) ExitCode {
+func InitFailedForTimeLocationModule(reason string, exitCode ...int) ExitCode {
+	if reason == "" {
+		reason = "no reason"
+	}
+
+	ec := getExitCode(exitCodeDefaultError, exitCode...)
+
+	log.Printf("The module `Time Location` init failed (reason: `%s`) .", reason)
+	log.Printf("Now we should exit with code %d.", ec)
+
+	return ec
+}
+
+func InitFailedForLoggerModule(reason string, exitCode ...int) ExitCode {
 	if reason == "" {
 		reason = "no reason"
 	}
@@ -74,7 +87,7 @@ func InitFailedErrorForLoggerModule(reason string, exitCode ...int) ExitCode {
 	return ec
 }
 
-func InitFailedError(module string, reason string, exitCode ...int) ExitCode {
+func InitFailed(module string, reason string, exitCode ...int) ExitCode {
 	if !logger.IsReady() {
 		return exitCodeErrorLogMustBeReady
 	}

+ 13 - 0
src/utils/timeutils/load_tzdata.go

@@ -0,0 +1,13 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build !systemtzdata
+
+package timeutils
+
+import (
+	_ "github.com/SongZihuan/BackendServerTemplate/src/utils/timeutils/tzdata"
+)
+
+// 默认情况下加载 go 自带的时区包,除非使用 systemtzdata 明确使用系统的时区包

+ 73 - 0
src/utils/timeutils/local_timezone_posix.go

@@ -0,0 +1,73 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build !windows
+
+package timeutils
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/SongZihuan/BackendServerTemplate/src/utils/cleanstringutils"
+	"os"
+	"os/exec"
+	"time"
+)
+
+func LoadLocation(name string) (*time.Location, error) {
+	return time.LoadLocation(name)
+}
+
+func GetLocalTimezone() *time.Location {
+	loc, err := getLocalTimezoneFromEnvTZ()
+	if err == nil && loc != nil {
+		return loc
+	}
+
+	loc, err = getLocalTimezoneFromEtcTimezone()
+	if err == nil && loc != nil {
+		return loc
+	}
+
+	loc, err = getLocalTimeZoneFromTimedatectl()
+	if err == nil && loc != nil {
+		return loc
+	}
+
+	return time.UTC
+}
+
+func getLocalTimezoneFromEnvTZ() (*time.Location, error) {
+	tz := cleanstringutils.GetStringOneLine(os.Getenv("TZ"))
+	if tz == "" {
+		return nil, fmt.Errorf("TZ not found")
+	}
+	return time.LoadLocation(tz)
+}
+
+func getLocalTimezoneFromEtcTimezone() (*time.Location, error) {
+	dat, err := os.ReadFile("/etc/timezone")
+	if err != nil {
+		return nil, err
+	}
+
+	return time.LoadLocation(cleanstringutils.GetStringOneLine(string(dat)))
+}
+
+func getLocalTimeZoneFromTimedatectl() (*time.Location, error) {
+	// 定义要执行的命令和参数
+	cmd := exec.Command("timedatectl", "show", "--property=Timezone", "--value")
+
+	// 捕获命令的标准输出
+	var out bytes.Buffer
+	cmd.Stdout = &out
+
+	// 执行命令
+	err := cmd.Run()
+	if err != nil {
+		return nil, fmt.Errorf("failed to execute timedatectl: %w", err)
+	}
+
+	return time.LoadLocation(cleanstringutils.GetStringOneLine(out.String()))
+}

+ 101 - 0
src/utils/timeutils/local_timezone_win32.go

@@ -0,0 +1,101 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package timeutils
+
+import (
+	"fmt"
+	"github.com/SongZihuan/BackendServerTemplate/src/utils/cleanstringutils"
+	"os"
+	"os/exec"
+	"time"
+)
+
+func LoadLocation(name string) (*time.Location, error) {
+	loc, err1 := time.LoadLocation(name)
+	if err1 == nil && loc != nil {
+		return loc, nil
+	}
+
+	loc, err2 := time.LoadLocation(mapWindowsToIANA(name))
+	if err2 == nil && loc != nil {
+		return loc, nil
+	}
+
+	if err1 == nil {
+		return nil, fmt.Errorf("location not found")
+	}
+
+	return nil, err1
+}
+
+func GetLocalTimezone() *time.Location {
+	loc, err := getLocalTimezoneFromEnvTZ()
+	if err == nil && loc != nil {
+		return loc
+	}
+
+	loc, err = getLocalTimezoneFromTZutil()
+	if err == nil && loc != nil {
+		return loc
+	}
+
+	return time.UTC
+}
+
+func getLocalTimezoneFromEnvTZ() (*time.Location, error) {
+	tz := cleanstringutils.GetStringOneLine(os.Getenv("TZ"))
+	if tz == "" {
+		return nil, fmt.Errorf("TZ not found")
+	}
+	return time.LoadLocation(tz)
+}
+
+func getLocalTimezoneFromTZutil() (*time.Location, error) {
+	return time.LoadLocation(mapWindowsToIANA(getWindowsTimeZone()))
+}
+
+func getWindowsTimeZone() string {
+	windowsTZ, err := _getWindowsTimeZone()
+	if err != nil {
+		return UTC
+	}
+	return windowsTZ
+}
+
+func _getWindowsTimeZone() (string, error) {
+	// 使用环境变量或命令行工具获取 Windows 时区名称
+	cmd := "tzutil /g"
+	out, err := exec.Command("cmd", "/c", cmd).Output()
+	if err != nil {
+		return "", err
+	}
+
+	windowsTZ := cleanstringutils.GetStringOneLine(string(out))
+	_, ok := WindowsTimeZoneMap[windowsTZ]
+	if !ok {
+		return "", fmt.Errorf("unknown windows timezone: %s", windowsTZ)
+	}
+
+	return windowsTZ, nil
+}
+
+func mapWindowsToIANA(windowsTZ string) string {
+	ianaTZ, err := _mapWindowsToIANA(windowsTZ)
+	if err != nil {
+		return time.UTC.String()
+	}
+
+	return ianaTZ
+}
+
+func _mapWindowsToIANA(windowsTZ string) (string, error) {
+	ianaTZ, exists := WindowsTimeZoneMap[windowsTZ]
+	if !exists {
+		return "", fmt.Errorf("unknown windows timezone: %s", windowsTZ)
+	}
+	return ianaTZ, nil
+}

+ 163 - 0
src/utils/timeutils/timezone_map_win32.go

@@ -0,0 +1,163 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//go:build windows
+
+package timeutils
+
+import "time"
+
+const UTC = "UTC"
+
+var WindowsTimeZoneMap = map[string]string{
+	"Dateline Standard Time":          "Etc/GMT+12",
+	"UTC-11":                          "Etc/GMT+11",
+	"Aleutian Standard Time":          "America/Adak",
+	"Hawaiian Standard Time":          "Pacific/Honolulu",
+	"Marquesas Standard Time":         "Pacific/Marquesas",
+	"Alaskan Standard Time":           "America/Anchorage",
+	"UTC-09":                          "Etc/GMT+9",
+	"Pacific Standard Time (Mexico)":  "America/Tijuana",
+	"Pacific Standard Time":           "America/Los_Angeles",
+	"UTC-08":                          "Etc/GMT+8",
+	"US Mountain Standard Time":       "America/Phoenix",
+	"Mountain Standard Time (Mexico)": "America/Chihuahua",
+	"Mountain Standard Time":          "America/Denver",
+	"Central America Standard Time":   "America/Guatemala",
+	"Central Standard Time":           "America/Chicago",
+	"Easter Island Standard Time":     "Pacific/Easter",
+	"Central Standard Time (Mexico)":  "America/Mexico_City",
+	"Canada Central Standard Time":    "America/Regina",
+	"SA Pacific Standard Time":        "America/Bogota",
+	"Eastern Standard Time (Mexico)":  "America/Cancun",
+	"Eastern Standard Time":           "America/New_York",
+	"Haiti Standard Time":             "America/Port-au-Prince",
+	"Cuba Standard Time":              "America/Havana",
+	"Turks And Caicos Standard Time":  "America/Grand_Turk",
+	"US Eastern Standard Time":        "America/Indiana/Indianapolis",
+	"Tocantins Standard Time":         "America/Araguaina",
+	"Central Brazilian Standard Time": "America/Cuiaba",
+	"SA Western Standard Time":        "America/La_Paz",
+	"Newfoundland Standard Time":      "America/St_Johns",
+	"Argentina Standard Time":         "America/Buenos_Aires",
+	"SA Eastern Standard Time":        "America/Fortaleza",
+	"Greenland Standard Time":         "America/Godthab",
+	"Montevideo Standard Time":        "America/Montevideo",
+	"Magallanes Standard Time":        "America/Punta_Arenas",
+	"Saint Pierre Standard Time":      "America/Miquelon",
+	"Bahia Standard Time":             "America/Bahia",
+	"UTC-03":                          "Etc/GMT+3",
+	"Caribbean Standard Time":         "America/Caracas", // 注意:此映射可能不完全准确,具体取决于上下文
+	"SA Antarctica Standard Time":     "Antarctica/Palmer",
+	"UTC-04":                          "Etc/GMT+4",
+	"Paraguay Standard Time":          "America/Asuncion",
+	"Atlantic Brazil Summer Time":     "Brazil/East", // 假设为巴西东部时间的夏令时版本
+	"Atlantic Standard Time":          "America/Halifax",
+	"Venezuela Standard Time":         "America/Caracas",
+	"Central Africa Standard Time":    "Africa/Luanda",
+	"W. Central Africa Standard Time": "Africa/Lagos",
+	"Morocco Standard Time":           "Africa/Casablanca",
+	"GMT Standard Time":               "Europe/London",
+	"Greenwich Standard Time":         "Atlantic/Reykjavik",
+	"W. Europe Standard Time":         "Europe/Berlin",
+	"Romance Standard Time":           "Europe/Paris",
+	"Central European Standard Time":  "Europe/Warsaw",
+	"South Africa Standard Time":      "Africa/Johannesburg",
+	"GTB Standard Time":               "Europe/Bucharest",
+	"Middle East Standard Time":       "Asia/Beirut",
+	"Egypt Standard Time":             "Africa/Cairo",
+	"E. Europe Standard Time":         "Europe/Bucharest",
+	"Syria Standard Time":             "Asia/Damascus",
+	"West Bank Standard Time":         "Asia/Hebron",
+	"South Sudan Standard Time":       "Africa/Juba",
+	"Libya Standard Time":             "Africa/Tripoli",
+	"Namibia Standard Time":           "Africa/Windhoek",
+	"Jordan Standard Time":            "Asia/Amman",
+	"Arabic Standard Time":            "Asia/Baghdad",
+	"Georgian Standard Time":          "Asia/Tbilisi",
+	"Armenian Standard Time":          "Asia/Yerevan",
+	"Time in Iran":                    "Asia/Tehran",
+	"Arab Standard Time":              "Asia/Riyadh",
+	"Azerbaijan Standard Time":        "Asia/Baku",
+	"Russia Time Zone 3":              "Europe/Samara",
+	"Mauritius Standard Time":         "Indian/Mauritius",
+	"Saratov Standard Time":           "Europe/Saratov",
+	"Caucasus Standard Time":          "Asia/Yerevan",
+	"Afghanistan Standard Time":       "Asia/Kabul",
+	"West Asia Standard Time":         "Asia/Tashkent",
+	"Ekaterinburg Standard Time":      "Asia/Yekaterinburg",
+	"Pakistan Standard Time":          "Asia/Karachi",
+	"India Standard Time":             "Asia/Kolkata",
+	"Sri Lanka Standard Time":         "Asia/Colombo",
+	"Nepal Standard Time":             "Asia/Kathmandu",
+	"Central Asia Standard Time":      "Asia/Almaty",
+	"Bangladesh Standard Time":        "Asia/Dhaka",
+	"Omsk Standard Time":              "Asia/Omsk",
+	"Myanmar Standard Time":           "Asia/Yangon",
+	"SE Asia Standard Time":           "Asia/Bangkok",
+	"Altai Standard Time":             "Asia/Barnaul",
+	"W. Mongolia Standard Time":       "Asia/Hovd",
+	"North Asia Standard Time":        "Asia/Krasnoyarsk",
+	"N. Central Asia Standard Time":   "Asia/Novosibirsk",
+	"Tomsk Standard Time":             "Asia/Tomsk",
+	"China Standard Time":             "Asia/Shanghai",
+	"North Asia East Standard Time":   "Asia/Irkutsk",
+	"Singapore Standard Time":         "Asia/Singapore",
+	"W. Australia Standard Time":      "Australia/Perth",
+	"Taipei Standard Time":            "Asia/Taipei",
+	"Ulaanbaatar Standard Time":       "Asia/Ulaanbaatar",
+	"AUS Central W. Standard Time":    "Australia/Eucla",
+	"Transbaikal Standard Time":       "Asia/Chita",
+	"Tokyo Standard Time":             "Asia/Tokyo",
+	"North Korea Standard Time":       "Asia/Pyongyang",
+	"Korea Standard Time":             "Asia/Seoul",
+	"Yakutsk Standard Time":           "Asia/Yakutsk",
+	"Cen. Australia Standard Time":    "Australia/Adelaide",
+	"AUS Central Standard Time":       "Australia/Darwin",
+	"E. Australia Standard Time":      "Australia/Brisbane",
+	"AUS Eastern Standard Time":       "Australia/Sydney",
+	"Tasmania Standard Time":          "Australia/Hobart",
+	"Vladivostok Standard Time":       "Asia/Vladivostok",
+	"Lord Howe Standard Time":         "Australia/Lord_Howe",
+	"Bougainville Standard Time":      "Pacific/Bougainville",
+	"Russia Time Zone 10":             "Asia/Srednekolymsk",
+	"Magadan Standard Time":           "Asia/Magadan",
+	"Norfolk Standard Time":           "Pacific/Norfolk",
+	"New Zealand Standard Time":       "Pacific/Auckland",
+	"Fiji Standard Time":              "Pacific/Fiji",
+	"Kamchatka Standard Time":         "Asia/Kamchatka",
+	"Chatham Islands Standard Time":   "Pacific/Chatham",
+	"Samoa Standard Time":             "Pacific/Apia",
+	"Line Islands Standard Time":      "Pacific/Kiritimati",
+	"Yukon Standard Time":             "America/Whitehorse",
+	"Pacific SA Standard Time":        "America/Santiago",
+	"E. South America Standard Time":  "America/Sao_Paulo",
+	"UTC-02":                          "Etc/GMT+2",
+	"Cape Verde Standard Time":        "Atlantic/Cape_Verde",
+	"Azores Standard Time":            "Atlantic/Azores",
+	UTC:                               time.UTC.String(),
+	"Sao Tome Standard Time":          "Africa/Sao_Tome",
+	"Central Europe Standard Time":    "Europe/Belgrade",
+	"FLE Standard Time":               "Europe/Helsinki",
+	"Kaliningrad Standard Time":       "Europe/Kaliningrad",
+	"Sudan Standard Time":             "Africa/Khartoum",
+	"Israel Standard Time":            "Asia/Jerusalem",
+	"Volgograd Standard Time":         "Europe/Volgograd",
+	"Belarus Standard Time":           "Europe/Minsk",
+	"Russian Standard Time":           "Europe/Moscow",
+	"E. Africa Standard Time":         "Africa/Nairobi",
+	"Turkey Standard Time":            "Europe/Istanbul",
+	"Iran Standard Time":              "Asia/Tehran",
+	"Arabian Standard Time":           "Asia/Dubai",
+	"Astrakhan Standard Time":         "Europe/Astrakhan",
+	"Qyzylorda Standard Time":         "Asia/Qyzylorda",
+	"Aus Central W. Standard Time":    "Australia/Eucla",
+	"West Pacific Standard Time":      "Pacific/Port_Moresby",
+	"Sakhalin Standard Time":          "Asia/Sakhalin",
+	"Central Pacific Standard Time":   "Pacific/Guadalcanal",
+	"Russia Time Zone 11":             "Asia/Srednekolymsk",
+	"UTC+12":                          "Etc/GMT-12",
+	"Tonga Standard Time":             "Pacific/Tongatapu",
+	"UTC+13":                          "Etc/GMT-13",
+}

+ 11 - 0
src/utils/timeutils/tzdata/data.go

@@ -0,0 +1,11 @@
+// Copyright 2025 BackendServerTemplate Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package tzdata
+
+import (
+	_ "time/tzdata"
+)
+
+// 导入 Go 内置的时区数据(用于系统没有时区数据时,例如:Docker)