diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample
index d5530f6a30..73ca5e0030 100644
--- a/custom/conf/app.ini.sample
+++ b/custom/conf/app.ini.sample
@@ -547,6 +547,9 @@ MAX_FILES = 5
 ; Special supported values are ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, RFC3339, RFC3339Nano, Kitchen, Stamp, StampMilli, StampMicro and StampNano
 ; For more information about the format see http://golang.org/pkg/time/#pkg-constants
 FORMAT =
+; Location the UI time display i.e. Asia/Shanghai
+; Empty means server's location setting
+DEFAULT_UI_LOCATION =
 
 [log]
 ROOT_PATH =
diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
index 3c6a306b95..ac309cecfa 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -503,8 +503,12 @@ Two special environment variables are passed to the render command:
 - `GITEA_PREFIX_SRC`, which contains the current URL prefix in the `src` path tree. To be used as prefix for links.
 - `GITEA_PREFIX_RAW`, which contains the current URL prefix in the `raw` path tree. To be used as prefix for image paths.
 
+## Time (`time`)
+- `FORMAT`: Time format to diplay on UI. i.e. RFC1123 or 2006-01-02 15:04:05
+- `DEFAULT_UI_LOCATION`: Default location of time on the UI, so that we can display correct user's time on UI. i.e. Shanghai/Asia
+
 ## Other (`other`)
 
 - `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer.
 - `SHOW_FOOTER_VERSION`: **true**: Show Gitea version information in the footer.
-- `SHOW_FOOTER_TEMPLATE_LOAD_TIME`: **true**: Show time of template execution in the footer.
+- `SHOW_FOOTER_TEMPLATE_LOAD_TIME`: **true**: Show time of template execution in the footer.
\ No newline at end of file
diff --git a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md
index b9a16dd844..7a00216b9f 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.zh-cn.md
@@ -237,7 +237,9 @@ IS_INPUT_FILE = false
 - RENDER_COMMAND: 工具的命令行命令及参数。
 - IS_INPUT_FILE: 输入方式是最后一个参数为文件路径还是从标准输入读取。
 
-
+## Time (`time`)
+- `FORMAT`: 显示在界面上的时间格式。比如: RFC1123 或者 2006-01-02 15:04:05
+- `DEFAULT_UI_LOCATION`: 默认显示在界面上的时区,默认为本地时区。比如: Asia/Shanghai
 
 ## Other (`other`)
 
diff --git a/models/action.go b/models/action.go
index ab8a657e0d..4b0a55921b 100644
--- a/models/action.go
+++ b/models/action.go
@@ -20,7 +20,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/Unknwon/com"
 	"xorm.io/builder"
@@ -91,9 +91,9 @@ type Action struct {
 	Comment     *Comment    `xorm:"-"`
 	IsDeleted   bool        `xorm:"INDEX NOT NULL DEFAULT false"`
 	RefName     string
-	IsPrivate   bool           `xorm:"INDEX NOT NULL DEFAULT false"`
-	Content     string         `xorm:"TEXT"`
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+	IsPrivate   bool               `xorm:"INDEX NOT NULL DEFAULT false"`
+	Content     string             `xorm:"TEXT"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 }
 
 // GetOpType gets the ActionType of this action.
diff --git a/models/admin.go b/models/admin.go
index 4480d11480..4585657e9f 100644
--- a/models/admin.go
+++ b/models/admin.go
@@ -9,7 +9,7 @@ import (
 	"os"
 
 	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/Unknwon/com"
 )
@@ -26,8 +26,8 @@ const (
 type Notice struct {
 	ID          int64 `xorm:"pk autoincr"`
 	Type        NoticeType
-	Description string         `xorm:"TEXT"`
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+	Description string             `xorm:"TEXT"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 }
 
 // TrStr returns a translation format string.
diff --git a/models/attachment.go b/models/attachment.go
index 1740f065d0..7fbf9dde99 100644
--- a/models/attachment.go
+++ b/models/attachment.go
@@ -12,7 +12,7 @@ import (
 
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 	gouuid "github.com/satori/go.uuid"
@@ -27,9 +27,9 @@ type Attachment struct {
 	UploaderID    int64  `xorm:"INDEX DEFAULT 0"` // Notice: will be zero before this column added
 	CommentID     int64
 	Name          string
-	DownloadCount int64          `xorm:"DEFAULT 0"`
-	Size          int64          `xorm:"DEFAULT 0"`
-	CreatedUnix   util.TimeStamp `xorm:"created"`
+	DownloadCount int64              `xorm:"DEFAULT 0"`
+	Size          int64              `xorm:"DEFAULT 0"`
+	CreatedUnix   timeutil.TimeStamp `xorm:"created"`
 }
 
 // IncreaseDownloadCount is update download count + 1
diff --git a/models/branches.go b/models/branches.go
index 3d3cff84d3..62b1c208f6 100644
--- a/models/branches.go
+++ b/models/branches.go
@@ -11,6 +11,7 @@ import (
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/Unknwon/com"
@@ -30,16 +31,16 @@ type ProtectedBranch struct {
 	BranchName                string `xorm:"UNIQUE(s)"`
 	CanPush                   bool   `xorm:"NOT NULL DEFAULT false"`
 	EnableWhitelist           bool
-	WhitelistUserIDs          []int64        `xorm:"JSON TEXT"`
-	WhitelistTeamIDs          []int64        `xorm:"JSON TEXT"`
-	EnableMergeWhitelist      bool           `xorm:"NOT NULL DEFAULT false"`
-	MergeWhitelistUserIDs     []int64        `xorm:"JSON TEXT"`
-	MergeWhitelistTeamIDs     []int64        `xorm:"JSON TEXT"`
-	ApprovalsWhitelistUserIDs []int64        `xorm:"JSON TEXT"`
-	ApprovalsWhitelistTeamIDs []int64        `xorm:"JSON TEXT"`
-	RequiredApprovals         int64          `xorm:"NOT NULL DEFAULT 0"`
-	CreatedUnix               util.TimeStamp `xorm:"created"`
-	UpdatedUnix               util.TimeStamp `xorm:"updated"`
+	WhitelistUserIDs          []int64            `xorm:"JSON TEXT"`
+	WhitelistTeamIDs          []int64            `xorm:"JSON TEXT"`
+	EnableMergeWhitelist      bool               `xorm:"NOT NULL DEFAULT false"`
+	MergeWhitelistUserIDs     []int64            `xorm:"JSON TEXT"`
+	MergeWhitelistTeamIDs     []int64            `xorm:"JSON TEXT"`
+	ApprovalsWhitelistUserIDs []int64            `xorm:"JSON TEXT"`
+	ApprovalsWhitelistTeamIDs []int64            `xorm:"JSON TEXT"`
+	RequiredApprovals         int64              `xorm:"NOT NULL DEFAULT 0"`
+	CreatedUnix               timeutil.TimeStamp `xorm:"created"`
+	UpdatedUnix               timeutil.TimeStamp `xorm:"updated"`
 }
 
 // IsProtected returns if the branch is protected
@@ -374,13 +375,13 @@ func (repo *Repository) DeleteProtectedBranch(id int64) (err error) {
 
 // DeletedBranch struct
 type DeletedBranch struct {
-	ID          int64          `xorm:"pk autoincr"`
-	RepoID      int64          `xorm:"UNIQUE(s) INDEX NOT NULL"`
-	Name        string         `xorm:"UNIQUE(s) NOT NULL"`
-	Commit      string         `xorm:"UNIQUE(s) NOT NULL"`
-	DeletedByID int64          `xorm:"INDEX"`
-	DeletedBy   *User          `xorm:"-"`
-	DeletedUnix util.TimeStamp `xorm:"INDEX created"`
+	ID          int64              `xorm:"pk autoincr"`
+	RepoID      int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
+	Name        string             `xorm:"UNIQUE(s) NOT NULL"`
+	Commit      string             `xorm:"UNIQUE(s) NOT NULL"`
+	DeletedByID int64              `xorm:"INDEX"`
+	DeletedBy   *User              `xorm:"-"`
+	DeletedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 }
 
 // AddDeletedBranch adds a deleted branch to the database
diff --git a/models/commit_status.go b/models/commit_status.go
index e864dc3036..9f0a32cdfb 100644
--- a/models/commit_status.go
+++ b/models/commit_status.go
@@ -13,7 +13,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 )
@@ -66,8 +66,8 @@ type CommitStatus struct {
 	Creator     *User             `xorm:"-"`
 	CreatorID   int64
 
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 }
 
 func (status *CommitStatus) loadRepo(e Engine) (err error) {
diff --git a/models/gpg_key.go b/models/gpg_key.go
index 8300cdbd21..72c6891d4d 100644
--- a/models/gpg_key.go
+++ b/models/gpg_key.go
@@ -17,7 +17,7 @@ import (
 
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 	"github.com/keybase/go-crypto/openpgp"
@@ -27,14 +27,14 @@ import (
 
 // GPGKey represents a GPG key.
 type GPGKey struct {
-	ID                int64          `xorm:"pk autoincr"`
-	OwnerID           int64          `xorm:"INDEX NOT NULL"`
-	KeyID             string         `xorm:"INDEX CHAR(16) NOT NULL"`
-	PrimaryKeyID      string         `xorm:"CHAR(16)"`
-	Content           string         `xorm:"TEXT NOT NULL"`
-	CreatedUnix       util.TimeStamp `xorm:"created"`
-	ExpiredUnix       util.TimeStamp
-	AddedUnix         util.TimeStamp
+	ID                int64              `xorm:"pk autoincr"`
+	OwnerID           int64              `xorm:"INDEX NOT NULL"`
+	KeyID             string             `xorm:"INDEX CHAR(16) NOT NULL"`
+	PrimaryKeyID      string             `xorm:"CHAR(16)"`
+	Content           string             `xorm:"TEXT NOT NULL"`
+	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
+	ExpiredUnix       timeutil.TimeStamp
+	AddedUnix         timeutil.TimeStamp
 	SubsKey           []*GPGKey `xorm:"-"`
 	Emails            []*EmailAddress
 	CanSign           bool
@@ -51,7 +51,7 @@ type GPGKeyImport struct {
 
 // BeforeInsert will be invoked by XORM before inserting a record
 func (key *GPGKey) BeforeInsert() {
-	key.AddedUnix = util.TimeStampNow()
+	key.AddedUnix = timeutil.TimeStampNow()
 }
 
 // AfterLoad is invoked from XORM after setting the values of all fields of this object.
@@ -223,8 +223,8 @@ func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, e
 		KeyID:             pubkey.KeyIdString(),
 		PrimaryKeyID:      primaryID,
 		Content:           content,
-		CreatedUnix:       util.TimeStamp(pubkey.CreationTime.Unix()),
-		ExpiredUnix:       util.TimeStamp(expiry.Unix()),
+		CreatedUnix:       timeutil.TimeStamp(pubkey.CreationTime.Unix()),
+		ExpiredUnix:       timeutil.TimeStamp(expiry.Unix()),
 		CanSign:           pubkey.CanSign(),
 		CanEncryptComms:   pubkey.PubKeyAlgo.CanEncrypt(),
 		CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
@@ -301,8 +301,8 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
 		KeyID:             pubkey.KeyIdString(),
 		PrimaryKeyID:      "",
 		Content:           content,
-		CreatedUnix:       util.TimeStamp(pubkey.CreationTime.Unix()),
-		ExpiredUnix:       util.TimeStamp(expiry.Unix()),
+		CreatedUnix:       timeutil.TimeStamp(pubkey.CreationTime.Unix()),
+		ExpiredUnix:       timeutil.TimeStamp(expiry.Unix()),
 		Emails:            emails,
 		SubsKey:           subkeys,
 		CanSign:           pubkey.CanSign(),
diff --git a/models/gpg_key_test.go b/models/gpg_key_test.go
index 7880046b2e..e2f92e7f81 100644
--- a/models/gpg_key_test.go
+++ b/models/gpg_key_test.go
@@ -8,7 +8,7 @@ import (
 	"testing"
 	"time"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -112,7 +112,7 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
 	key := &GPGKey{
 		KeyID:             pubkey.KeyIdString(),
 		Content:           content,
-		CreatedUnix:       util.TimeStamp(pubkey.CreationTime.Unix()),
+		CreatedUnix:       timeutil.TimeStamp(pubkey.CreationTime.Unix()),
 		CanSign:           pubkey.CanSign(),
 		CanEncryptComms:   pubkey.PubKeyAlgo.CanEncrypt(),
 		CanEncryptStorage: pubkey.PubKeyAlgo.CanEncrypt(),
@@ -122,7 +122,7 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
 	cannotsignkey := &GPGKey{
 		KeyID:             pubkey.KeyIdString(),
 		Content:           content,
-		CreatedUnix:       util.TimeStamp(pubkey.CreationTime.Unix()),
+		CreatedUnix:       timeutil.TimeStamp(pubkey.CreationTime.Unix()),
 		CanSign:           false,
 		CanEncryptComms:   false,
 		CanEncryptStorage: false,
diff --git a/models/issue.go b/models/issue.go
index 7561083e0f..ddfa2a2e14 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/Unknwon/com"
@@ -49,11 +50,11 @@ type Issue struct {
 	NumComments      int
 	Ref              string
 
-	DeadlineUnix util.TimeStamp `xorm:"INDEX"`
+	DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`
 
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
-	ClosedUnix  util.TimeStamp `xorm:"INDEX"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+	ClosedUnix  timeutil.TimeStamp `xorm:"INDEX"`
 
 	Attachments      []*Attachment `xorm:"-"`
 	Comments         []*Comment    `xorm:"-"`
@@ -90,7 +91,7 @@ func (issue *Issue) loadTotalTimes(e Engine) (err error) {
 
 // IsOverdue checks if the issue is overdue
 func (issue *Issue) IsOverdue() bool {
-	return util.TimeStampNow() >= issue.DeadlineUnix
+	return timeutil.TimeStampNow() >= issue.DeadlineUnix
 }
 
 // LoadRepo loads issue's repository
@@ -744,7 +745,7 @@ func (issue *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (er
 
 	issue.IsClosed = isClosed
 	if isClosed {
-		issue.ClosedUnix = util.TimeStampNow()
+		issue.ClosedUnix = timeutil.TimeStampNow()
 	} else {
 		issue.ClosedUnix = 0
 	}
@@ -991,7 +992,7 @@ func (issue *Issue) GetTasksDone() int {
 }
 
 // GetLastEventTimestamp returns the last user visible event timestamp, either the creation of this issue or the close.
-func (issue *Issue) GetLastEventTimestamp() util.TimeStamp {
+func (issue *Issue) GetLastEventTimestamp() timeutil.TimeStamp {
 	if issue.IsClosed {
 		return issue.ClosedUnix
 	}
@@ -1794,7 +1795,7 @@ func UpdateIssue(issue *Issue) error {
 }
 
 // UpdateIssueDeadline updates an issue deadline and adds comments. Setting a deadline to 0 means deleting it.
-func UpdateIssueDeadline(issue *Issue, deadlineUnix util.TimeStamp, doer *User) (err error) {
+func UpdateIssueDeadline(issue *Issue, deadlineUnix timeutil.TimeStamp, doer *User) (err error) {
 
 	// if the deadline hasn't changed do nothing
 	if issue.DeadlineUnix == deadlineUnix {
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 32bb925e9b..3a94a6b5be 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -12,17 +12,16 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/timeutil"
+
 	"github.com/Unknwon/com"
 	"github.com/go-xorm/xorm"
 	"xorm.io/builder"
-
-	api "code.gitea.io/gitea/modules/structs"
-
-	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/markup"
-	"code.gitea.io/gitea/modules/util"
 )
 
 // CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
@@ -130,8 +129,8 @@ type Comment struct {
 	// Path represents the 4 lines of code cemented by this comment
 	Patch string `xorm:"TEXT"`
 
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 
 	// Reference issue in commit message
 	CommitSHA string `xorm:"VARCHAR(40)"`
@@ -711,7 +710,7 @@ func createAssigneeComment(e *xorm.Session, doer *User, repo *Repository, issue
 	})
 }
 
-func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlineUnix util.TimeStamp) (*Comment, error) {
+func createDeadlineComment(e *xorm.Session, doer *User, issue *Issue, newDeadlineUnix timeutil.TimeStamp) (*Comment, error) {
 
 	var content string
 	var commentType CommentType
diff --git a/models/issue_dependency.go b/models/issue_dependency.go
index ffa972c106..c880bd59c5 100644
--- a/models/issue_dependency.go
+++ b/models/issue_dependency.go
@@ -7,17 +7,17 @@ package models
 import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // IssueDependency represents an issue dependency
 type IssueDependency struct {
-	ID           int64          `xorm:"pk autoincr"`
-	UserID       int64          `xorm:"NOT NULL"`
-	IssueID      int64          `xorm:"UNIQUE(issue_dependency) NOT NULL"`
-	DependencyID int64          `xorm:"UNIQUE(issue_dependency) NOT NULL"`
-	CreatedUnix  util.TimeStamp `xorm:"created"`
-	UpdatedUnix  util.TimeStamp `xorm:"updated"`
+	ID           int64              `xorm:"pk autoincr"`
+	UserID       int64              `xorm:"NOT NULL"`
+	IssueID      int64              `xorm:"UNIQUE(issue_dependency) NOT NULL"`
+	DependencyID int64              `xorm:"UNIQUE(issue_dependency) NOT NULL"`
+	CreatedUnix  timeutil.TimeStamp `xorm:"created"`
+	UpdatedUnix  timeutil.TimeStamp `xorm:"updated"`
 }
 
 // DependencyType Defines Dependency Type Constants
diff --git a/models/issue_milestone.go b/models/issue_milestone.go
index f279dda195..b1505c12cb 100644
--- a/models/issue_milestone.go
+++ b/models/issue_milestone.go
@@ -10,7 +10,8 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
+
 	"github.com/go-xorm/xorm"
 )
 
@@ -29,8 +30,8 @@ type Milestone struct {
 	IsOverdue       bool `xorm:"-"`
 
 	DeadlineString string `xorm:"-"`
-	DeadlineUnix   util.TimeStamp
-	ClosedDateUnix util.TimeStamp
+	DeadlineUnix   timeutil.TimeStamp
+	ClosedDateUnix timeutil.TimeStamp
 
 	TotalTrackedTime int64 `xorm:"-"`
 }
@@ -53,7 +54,7 @@ func (m *Milestone) AfterLoad() {
 	}
 
 	m.DeadlineString = m.DeadlineUnix.Format("2006-01-02")
-	if util.TimeStampNow() >= m.DeadlineUnix {
+	if timeutil.TimeStampNow() >= m.DeadlineUnix {
 		m.IsOverdue = true
 	}
 }
diff --git a/models/issue_milestone_test.go b/models/issue_milestone_test.go
index f9e51aff31..09c6ff7595 100644
--- a/models/issue_milestone_test.go
+++ b/models/issue_milestone_test.go
@@ -10,7 +10,7 @@ import (
 	"time"
 
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -29,7 +29,7 @@ func TestMilestone_APIFormat(t *testing.T) {
 		IsClosed:        false,
 		NumOpenIssues:   5,
 		NumClosedIssues: 6,
-		DeadlineUnix:    util.TimeStamp(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()),
+		DeadlineUnix:    timeutil.TimeStamp(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC).Unix()),
 	}
 	assert.Equal(t, api.Milestone{
 		ID:           milestone.ID,
@@ -237,7 +237,7 @@ func TestChangeMilestoneIssueStats(t *testing.T) {
 		"is_closed=0").(*Issue)
 
 	issue.IsClosed = true
-	issue.ClosedUnix = util.TimeStampNow()
+	issue.ClosedUnix = timeutil.TimeStampNow()
 	_, err := x.Cols("is_closed", "closed_unix").Update(issue)
 	assert.NoError(t, err)
 	assert.NoError(t, changeMilestoneIssueStats(x.NewSession(), issue))
diff --git a/models/issue_reaction.go b/models/issue_reaction.go
index e0df6f757b..ab644b4b3e 100644
--- a/models/issue_reaction.go
+++ b/models/issue_reaction.go
@@ -9,7 +9,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 	"xorm.io/builder"
@@ -17,13 +17,13 @@ import (
 
 // Reaction represents a reactions on issues and comments.
 type Reaction struct {
-	ID          int64          `xorm:"pk autoincr"`
-	Type        string         `xorm:"INDEX UNIQUE(s) NOT NULL"`
-	IssueID     int64          `xorm:"INDEX UNIQUE(s) NOT NULL"`
-	CommentID   int64          `xorm:"INDEX UNIQUE(s)"`
-	UserID      int64          `xorm:"INDEX UNIQUE(s) NOT NULL"`
-	User        *User          `xorm:"-"`
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
+	ID          int64              `xorm:"pk autoincr"`
+	Type        string             `xorm:"INDEX UNIQUE(s) NOT NULL"`
+	IssueID     int64              `xorm:"INDEX UNIQUE(s) NOT NULL"`
+	CommentID   int64              `xorm:"INDEX UNIQUE(s)"`
+	UserID      int64              `xorm:"INDEX UNIQUE(s) NOT NULL"`
+	User        *User              `xorm:"-"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 }
 
 // FindReactionsOptions describes the conditions to Find reactions
diff --git a/models/issue_stopwatch.go b/models/issue_stopwatch.go
index d754e7e19e..d7c3a9f73b 100644
--- a/models/issue_stopwatch.go
+++ b/models/issue_stopwatch.go
@@ -8,15 +8,15 @@ import (
 	"fmt"
 	"time"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // Stopwatch represents a stopwatch for time tracking.
 type Stopwatch struct {
-	ID          int64          `xorm:"pk autoincr"`
-	IssueID     int64          `xorm:"INDEX"`
-	UserID      int64          `xorm:"INDEX"`
-	CreatedUnix util.TimeStamp `xorm:"created"`
+	ID          int64              `xorm:"pk autoincr"`
+	IssueID     int64              `xorm:"INDEX"`
+	UserID      int64              `xorm:"INDEX"`
+	CreatedUnix timeutil.TimeStamp `xorm:"created"`
 }
 
 func getStopwatch(e Engine, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
diff --git a/models/issue_stopwatch_test.go b/models/issue_stopwatch_test.go
index 7983240476..41d93c8a14 100644
--- a/models/issue_stopwatch_test.go
+++ b/models/issue_stopwatch_test.go
@@ -3,7 +3,7 @@ package models
 import (
 	"testing"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/stretchr/testify/assert"
 )
@@ -63,7 +63,7 @@ func TestCreateOrStopIssueStopwatch(t *testing.T) {
 
 	assert.NoError(t, CreateOrStopIssueStopwatch(user3, issue1))
 	sw := AssertExistsAndLoadBean(t, &Stopwatch{UserID: 3, IssueID: 1}).(*Stopwatch)
-	assert.Equal(t, true, sw.CreatedUnix <= util.TimeStampNow())
+	assert.Equal(t, true, sw.CreatedUnix <= timeutil.TimeStampNow())
 
 	assert.NoError(t, CreateOrStopIssueStopwatch(user2, issue2))
 	AssertNotExistsBean(t, &Stopwatch{UserID: 2, IssueID: 2})
diff --git a/models/issue_tracked_time.go b/models/issue_tracked_time.go
index 5482a45f2a..f9313b7653 100644
--- a/models/issue_tracked_time.go
+++ b/models/issue_tracked_time.go
@@ -26,7 +26,7 @@ type TrackedTime struct {
 
 // AfterLoad is invoked from XORM after setting the values of all fields of this object.
 func (t *TrackedTime) AfterLoad() {
-	t.Created = time.Unix(t.CreatedUnix, 0).In(setting.UILocation)
+	t.Created = time.Unix(t.CreatedUnix, 0).In(setting.DefaultUILocation)
 }
 
 // APIFormat converts TrackedTime to API format
diff --git a/models/issue_watch.go b/models/issue_watch.go
index 579c915476..2f55c6a84d 100644
--- a/models/issue_watch.go
+++ b/models/issue_watch.go
@@ -4,16 +4,16 @@
 
 package models
 
-import "code.gitea.io/gitea/modules/util"
+import "code.gitea.io/gitea/modules/timeutil"
 
 // IssueWatch is connection request for receiving issue notification.
 type IssueWatch struct {
-	ID          int64          `xorm:"pk autoincr"`
-	UserID      int64          `xorm:"UNIQUE(watch) NOT NULL"`
-	IssueID     int64          `xorm:"UNIQUE(watch) NOT NULL"`
-	IsWatching  bool           `xorm:"NOT NULL"`
-	CreatedUnix util.TimeStamp `xorm:"created NOT NULL"`
-	UpdatedUnix util.TimeStamp `xorm:"updated NOT NULL"`
+	ID          int64              `xorm:"pk autoincr"`
+	UserID      int64              `xorm:"UNIQUE(watch) NOT NULL"`
+	IssueID     int64              `xorm:"UNIQUE(watch) NOT NULL"`
+	IsWatching  bool               `xorm:"NOT NULL"`
+	CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"updated NOT NULL"`
 }
 
 // CreateOrUpdateIssueWatch set watching for a user and issue
diff --git a/models/lfs.go b/models/lfs.go
index 94d3f57905..9b20642777 100644
--- a/models/lfs.go
+++ b/models/lfs.go
@@ -7,17 +7,17 @@ import (
 	"fmt"
 	"io"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // LFSMetaObject stores metadata for LFS tracked files.
 type LFSMetaObject struct {
-	ID           int64          `xorm:"pk autoincr"`
-	Oid          string         `xorm:"UNIQUE(s) INDEX NOT NULL"`
-	Size         int64          `xorm:"NOT NULL"`
-	RepositoryID int64          `xorm:"UNIQUE(s) INDEX NOT NULL"`
-	Existing     bool           `xorm:"-"`
-	CreatedUnix  util.TimeStamp `xorm:"created"`
+	ID           int64              `xorm:"pk autoincr"`
+	Oid          string             `xorm:"UNIQUE(s) INDEX NOT NULL"`
+	Size         int64              `xorm:"NOT NULL"`
+	RepositoryID int64              `xorm:"UNIQUE(s) INDEX NOT NULL"`
+	Existing     bool               `xorm:"-"`
+	CreatedUnix  timeutil.TimeStamp `xorm:"created"`
 }
 
 // Pointer returns the string representation of an LFS pointer file
diff --git a/models/login_source.go b/models/login_source.go
index 26544588c1..f52679dab6 100644
--- a/models/login_source.go
+++ b/models/login_source.go
@@ -14,16 +14,16 @@ import (
 	"regexp"
 	"strings"
 
-	"github.com/Unknwon/com"
-	"github.com/go-xorm/xorm"
-	"xorm.io/core"
-
 	"code.gitea.io/gitea/modules/auth/ldap"
 	"code.gitea.io/gitea/modules/auth/oauth2"
 	"code.gitea.io/gitea/modules/auth/pam"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"github.com/Unknwon/com"
+	"github.com/go-xorm/xorm"
+	"xorm.io/core"
 )
 
 // LoginType represents an login type.
@@ -148,8 +148,8 @@ type LoginSource struct {
 	IsSyncEnabled bool            `xorm:"INDEX NOT NULL DEFAULT false"`
 	Cfg           core.Conversion `xorm:"TEXT"`
 
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 }
 
 // Cell2Int64 converts a xorm.Cell type to int64,
diff --git a/models/mail.go b/models/mail.go
index cd4e4bc804..31f22519cf 100644
--- a/models/mail.go
+++ b/models/mail.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 	"gopkg.in/gomail.v2"
 )
 
@@ -47,8 +48,8 @@ func SendTestMail(email string) error {
 func SendUserMail(language string, u *User, tpl base.TplName, code, subject, info string) {
 	data := map[string]interface{}{
 		"DisplayName":       u.DisplayName(),
-		"ActiveCodeLives":   base.MinutesToFriendly(setting.Service.ActiveCodeLives, language),
-		"ResetPwdCodeLives": base.MinutesToFriendly(setting.Service.ResetPwdCodeLives, language),
+		"ActiveCodeLives":   timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, language),
+		"ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, language),
 		"Code":              code,
 	}
 
@@ -85,7 +86,7 @@ func SendResetPasswordMail(locale Locale, u *User) {
 func SendActivateEmailMail(locale Locale, u *User, email *EmailAddress) {
 	data := map[string]interface{}{
 		"DisplayName":     u.DisplayName(),
-		"ActiveCodeLives": base.MinutesToFriendly(setting.Service.ActiveCodeLives, locale.Language()),
+		"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale.Language()),
 		"Code":            u.GenerateEmailActivateCode(email.Email),
 		"Email":           email.Email,
 	}
diff --git a/models/migrations/v54.go b/models/migrations/v54.go
index 96c26739c6..5194624f69 100644
--- a/models/migrations/v54.go
+++ b/models/migrations/v54.go
@@ -7,7 +7,7 @@ package migrations
 import (
 	"fmt"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 )
@@ -19,7 +19,7 @@ func addPullRequestOptions(x *xorm.Engine) error {
 		RepoID      int64                  `xorm:"INDEX(s)"`
 		Type        int                    `xorm:"INDEX(s)"`
 		Config      map[string]interface{} `xorm:"JSON"`
-		CreatedUnix util.TimeStamp         `xorm:"INDEX CREATED"`
+		CreatedUnix timeutil.TimeStamp     `xorm:"INDEX CREATED"`
 	}
 
 	sess := x.NewSession()
diff --git a/models/migrations/v57.go b/models/migrations/v57.go
index 3a79a5cca7..fe4bf6b0ee 100644
--- a/models/migrations/v57.go
+++ b/models/migrations/v57.go
@@ -7,7 +7,7 @@ package migrations
 import (
 	"fmt"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 )
@@ -15,7 +15,7 @@ import (
 func addIssueClosedTime(x *xorm.Engine) error {
 	// Issue see models/issue.go
 	type Issue struct {
-		ClosedUnix util.TimeStamp `xorm:"INDEX"`
+		ClosedUnix timeutil.TimeStamp `xorm:"INDEX"`
 	}
 
 	if err := x.Sync2(new(Issue)); err != nil {
diff --git a/models/migrations/v64.go b/models/migrations/v64.go
index e4a360f578..00637ca046 100644
--- a/models/migrations/v64.go
+++ b/models/migrations/v64.go
@@ -5,7 +5,7 @@
 package migrations
 
 import (
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 )
@@ -27,10 +27,10 @@ func addMultipleAssignees(x *xorm.Engine) error {
 		IsPull      bool  `xorm:"INDEX"` // Indicates whether is a pull request or not.
 		NumComments int
 
-		DeadlineUnix util.TimeStamp `xorm:"INDEX"`
-		CreatedUnix  util.TimeStamp `xorm:"INDEX created"`
-		UpdatedUnix  util.TimeStamp `xorm:"INDEX updated"`
-		ClosedUnix   util.TimeStamp `xorm:"INDEX"`
+		DeadlineUnix timeutil.TimeStamp `xorm:"INDEX"`
+		CreatedUnix  timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix  timeutil.TimeStamp `xorm:"INDEX updated"`
+		ClosedUnix   timeutil.TimeStamp `xorm:"INDEX"`
 	}
 
 	// Updated the comment table
@@ -53,8 +53,8 @@ func addMultipleAssignees(x *xorm.Engine) error {
 		Content         string `xorm:"TEXT"`
 		RenderedContent string `xorm:"-"`
 
-		CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-		UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+		CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 
 		// Reference issue in commit message
 		CommitSHA string `xorm:"VARCHAR(40)"`
diff --git a/models/migrations/v65.go b/models/migrations/v65.go
index f73e632877..cc199d34e2 100644
--- a/models/migrations/v65.go
+++ b/models/migrations/v65.go
@@ -1,7 +1,8 @@
 package migrations
 
 import (
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
+
 	"github.com/go-xorm/xorm"
 )
 
@@ -12,8 +13,8 @@ func addU2FReg(x *xorm.Engine) error {
 		UserID      int64 `xorm:"INDEX"`
 		Raw         []byte
 		Counter     uint32
-		CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-		UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+		CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 	}
 	return x.Sync2(&U2FRegistration{})
 }
diff --git a/models/migrations/v71.go b/models/migrations/v71.go
index 8594460370..004f0a3f51 100644
--- a/models/migrations/v71.go
+++ b/models/migrations/v71.go
@@ -8,11 +8,11 @@ import (
 	"crypto/sha256"
 	"fmt"
 
+	"code.gitea.io/gitea/modules/generate"
+	"code.gitea.io/gitea/modules/timeutil"
+
 	"github.com/go-xorm/xorm"
 	"golang.org/x/crypto/pbkdf2"
-
-	"code.gitea.io/gitea/modules/generate"
-	"code.gitea.io/gitea/modules/util"
 )
 
 func addScratchHash(x *xorm.Engine) error {
@@ -24,9 +24,9 @@ func addScratchHash(x *xorm.Engine) error {
 		ScratchToken     string
 		ScratchSalt      string
 		ScratchHash      string
-		LastUsedPasscode string         `xorm:"VARCHAR(10)"`
-		CreatedUnix      util.TimeStamp `xorm:"INDEX created"`
-		UpdatedUnix      util.TimeStamp `xorm:"INDEX updated"`
+		LastUsedPasscode string             `xorm:"VARCHAR(10)"`
+		CreatedUnix      timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix      timeutil.TimeStamp `xorm:"INDEX updated"`
 	}
 
 	if err := x.Sync2(new(TwoFactor)); err != nil {
diff --git a/models/migrations/v72.go b/models/migrations/v72.go
index 4924b41ceb..c99b46afd2 100644
--- a/models/migrations/v72.go
+++ b/models/migrations/v72.go
@@ -7,7 +7,7 @@ package migrations
 import (
 	"fmt"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 )
@@ -20,8 +20,8 @@ func addReview(x *xorm.Engine) error {
 		ReviewerID  int64 `xorm:"index"`
 		IssueID     int64 `xorm:"index"`
 		Content     string
-		CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-		UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+		CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 	}
 
 	if err := x.Sync2(new(Review)); err != nil {
diff --git a/models/migrations/v76.go b/models/migrations/v76.go
index efab7e0cf0..e1fd6f100b 100644
--- a/models/migrations/v76.go
+++ b/models/migrations/v76.go
@@ -7,7 +7,7 @@ package migrations
 import (
 	"fmt"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 )
@@ -19,7 +19,7 @@ func addPullRequestRebaseWithMerge(x *xorm.Engine) error {
 		RepoID      int64                  `xorm:"INDEX(s)"`
 		Type        int                    `xorm:"INDEX(s)"`
 		Config      map[string]interface{} `xorm:"JSON"`
-		CreatedUnix util.TimeStamp         `xorm:"INDEX CREATED"`
+		CreatedUnix timeutil.TimeStamp     `xorm:"INDEX CREATED"`
 	}
 
 	sess := x.NewSession()
diff --git a/models/migrations/v83.go b/models/migrations/v83.go
index 947645153c..cdc59292ab 100644
--- a/models/migrations/v83.go
+++ b/models/migrations/v83.go
@@ -5,7 +5,7 @@
 package migrations
 
 import (
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 )
@@ -19,9 +19,9 @@ func addUploaderIDForAttachment(x *xorm.Engine) error {
 		UploaderID    int64  `xorm:"INDEX DEFAULT 0"`
 		CommentID     int64
 		Name          string
-		DownloadCount int64          `xorm:"DEFAULT 0"`
-		Size          int64          `xorm:"DEFAULT 0"`
-		CreatedUnix   util.TimeStamp `xorm:"created"`
+		DownloadCount int64              `xorm:"DEFAULT 0"`
+		Size          int64              `xorm:"DEFAULT 0"`
+		CreatedUnix   timeutil.TimeStamp `xorm:"created"`
 	}
 
 	return x.Sync2(new(Attachment))
diff --git a/models/migrations/v85.go b/models/migrations/v85.go
index 157c06d7ba..6066d5ebe9 100644
--- a/models/migrations/v85.go
+++ b/models/migrations/v85.go
@@ -7,11 +7,11 @@ package migrations
 import (
 	"fmt"
 
-	"github.com/go-xorm/xorm"
-
 	"code.gitea.io/gitea/modules/generate"
 	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"github.com/go-xorm/xorm"
 )
 
 func hashAppToken(x *xorm.Engine) error {
@@ -26,10 +26,10 @@ func hashAppToken(x *xorm.Engine) error {
 		TokenSalt      string
 		TokenLastEight string `xorm:"token_last_eight"`
 
-		CreatedUnix       util.TimeStamp `xorm:"INDEX created"`
-		UpdatedUnix       util.TimeStamp `xorm:"INDEX updated"`
-		HasRecentActivity bool           `xorm:"-"`
-		HasUsed           bool           `xorm:"-"`
+		CreatedUnix       timeutil.TimeStamp `xorm:"INDEX created"`
+		UpdatedUnix       timeutil.TimeStamp `xorm:"INDEX updated"`
+		HasRecentActivity bool               `xorm:"-"`
+		HasUsed           bool               `xorm:"-"`
 	}
 
 	// First remove the index
diff --git a/models/notification.go b/models/notification.go
index f83fe63e5a..5b6ce597d1 100644
--- a/models/notification.go
+++ b/models/notification.go
@@ -7,7 +7,7 @@ package models
 import (
 	"fmt"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 type (
@@ -52,8 +52,8 @@ type Notification struct {
 	Issue      *Issue      `xorm:"-"`
 	Repository *Repository `xorm:"-"`
 
-	CreatedUnix util.TimeStamp `xorm:"created INDEX NOT NULL"`
-	UpdatedUnix util.TimeStamp `xorm:"updated INDEX NOT NULL"`
+	CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"updated INDEX NOT NULL"`
 }
 
 // CreateOrUpdateIssueNotifications creates an issue notification
diff --git a/models/oauth2_application.go b/models/oauth2_application.go
index 63d2e7ce5e..04c3d7721c 100644
--- a/models/oauth2_application.go
+++ b/models/oauth2_application.go
@@ -11,15 +11,14 @@ import (
 	"net/url"
 	"time"
 
-	"github.com/go-xorm/xorm"
-	uuid "github.com/satori/go.uuid"
-
 	"code.gitea.io/gitea/modules/secret"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/Unknwon/com"
 	"github.com/dgrijalva/jwt-go"
+	"github.com/go-xorm/xorm"
+	uuid "github.com/satori/go.uuid"
 	"golang.org/x/crypto/bcrypt"
 )
 
@@ -36,8 +35,8 @@ type OAuth2Application struct {
 
 	RedirectURIs []string `xorm:"redirect_uris JSON TEXT"`
 
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 }
 
 // TableName sets the table name to `oauth2_application`
@@ -264,7 +263,7 @@ type OAuth2AuthorizationCode struct {
 	CodeChallenge       string
 	CodeChallengeMethod string
 	RedirectURI         string
-	ValidUntil          util.TimeStamp `xorm:"index"`
+	ValidUntil          timeutil.TimeStamp `xorm:"index"`
 }
 
 // TableName sets the table name to `oauth2_authorization_code`
@@ -348,8 +347,8 @@ type OAuth2Grant struct {
 	Application   *OAuth2Application `xorm:"-"`
 	ApplicationID int64              `xorm:"INDEX unique(user_application)"`
 	Counter       int64              `xorm:"NOT NULL DEFAULT 1"`
-	CreatedUnix   util.TimeStamp     `xorm:"created"`
-	UpdatedUnix   util.TimeStamp     `xorm:"updated"`
+	CreatedUnix   timeutil.TimeStamp `xorm:"created"`
+	UpdatedUnix   timeutil.TimeStamp `xorm:"updated"`
 }
 
 // TableName sets the table name to `oauth2_grant`
diff --git a/models/pull.go b/models/pull.go
index 7dd6050c67..8728fa11cb 100644
--- a/models/pull.go
+++ b/models/pull.go
@@ -23,7 +23,7 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/sync"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/Unknwon/com"
 	"github.com/go-xorm/xorm"
@@ -72,11 +72,11 @@ type PullRequest struct {
 	ProtectedBranch *ProtectedBranch `xorm:"-"`
 	MergeBase       string           `xorm:"VARCHAR(40)"`
 
-	HasMerged      bool           `xorm:"INDEX"`
-	MergedCommitID string         `xorm:"VARCHAR(40)"`
-	MergerID       int64          `xorm:"INDEX"`
-	Merger         *User          `xorm:"-"`
-	MergedUnix     util.TimeStamp `xorm:"updated INDEX"`
+	HasMerged      bool               `xorm:"INDEX"`
+	MergedCommitID string             `xorm:"VARCHAR(40)"`
+	MergerID       int64              `xorm:"INDEX"`
+	Merger         *User              `xorm:"-"`
+	MergedUnix     timeutil.TimeStamp `xorm:"updated INDEX"`
 }
 
 // Note: don't try to get Issue because will end up recursive querying.
@@ -443,7 +443,7 @@ func (pr *PullRequest) manuallyMerged() bool {
 	}
 	if commit != nil {
 		pr.MergedCommitID = commit.ID.String()
-		pr.MergedUnix = util.TimeStamp(commit.Author.When.Unix())
+		pr.MergedUnix = timeutil.TimeStamp(commit.Author.When.Unix())
 		pr.Status = PullRequestStatusManuallyMerged
 		merger, _ := GetUserByEmail(commit.Author.Email)
 
diff --git a/models/release.go b/models/release.go
index ecb9d1e44a..7ef0134c9d 100644
--- a/models/release.go
+++ b/models/release.go
@@ -14,7 +14,7 @@ import (
 	"code.gitea.io/gitea/modules/process"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"xorm.io/builder"
 )
@@ -32,13 +32,13 @@ type Release struct {
 	Title            string
 	Sha1             string `xorm:"VARCHAR(40)"`
 	NumCommits       int64
-	NumCommitsBehind int64          `xorm:"-"`
-	Note             string         `xorm:"TEXT"`
-	IsDraft          bool           `xorm:"NOT NULL DEFAULT false"`
-	IsPrerelease     bool           `xorm:"NOT NULL DEFAULT false"`
-	IsTag            bool           `xorm:"NOT NULL DEFAULT false"`
-	Attachments      []*Attachment  `xorm:"-"`
-	CreatedUnix      util.TimeStamp `xorm:"INDEX"`
+	NumCommitsBehind int64              `xorm:"-"`
+	Note             string             `xorm:"TEXT"`
+	IsDraft          bool               `xorm:"NOT NULL DEFAULT false"`
+	IsPrerelease     bool               `xorm:"NOT NULL DEFAULT false"`
+	IsTag            bool               `xorm:"NOT NULL DEFAULT false"`
+	Attachments      []*Attachment      `xorm:"-"`
+	CreatedUnix      timeutil.TimeStamp `xorm:"INDEX"`
 }
 
 func (r *Release) loadAttributes(e Engine) error {
@@ -137,13 +137,13 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
 		}
 
 		rel.Sha1 = commit.ID.String()
-		rel.CreatedUnix = util.TimeStamp(commit.Author.When.Unix())
+		rel.CreatedUnix = timeutil.TimeStamp(commit.Author.When.Unix())
 		rel.NumCommits, err = commit.CommitsCount()
 		if err != nil {
 			return fmt.Errorf("CommitsCount: %v", err)
 		}
 	} else {
-		rel.CreatedUnix = util.TimeStampNow()
+		rel.CreatedUnix = timeutil.TimeStampNow()
 	}
 	return nil
 }
diff --git a/models/repo.go b/models/repo.go
index b0e59f26b6..50e1ad0bb7 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -34,7 +34,7 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/sync"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/Unknwon/com"
 	"github.com/go-xorm/xorm"
@@ -175,8 +175,8 @@ type Repository struct {
 	// Avatar: ID(10-20)-md5(32) - must fit into 64 symbols
 	Avatar string `xorm:"VARCHAR(64)"`
 
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 }
 
 // ColorFormat returns a colored string to represent this repo
@@ -971,7 +971,7 @@ func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, err
 			RepoID:         repo.ID,
 			Interval:       setting.Mirror.DefaultInterval,
 			EnablePrune:    true,
-			NextUpdateUnix: util.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
+			NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
 		}); err != nil {
 			return repo, fmt.Errorf("InsertOne: %v", err)
 		}
diff --git a/models/repo_mirror.go b/models/repo_mirror.go
index 528e9daa8b..7f703a1c97 100644
--- a/models/repo_mirror.go
+++ b/models/repo_mirror.go
@@ -16,6 +16,7 @@ import (
 	"code.gitea.io/gitea/modules/process"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/sync"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/Unknwon/com"
@@ -34,8 +35,8 @@ type Mirror struct {
 	Interval    time.Duration
 	EnablePrune bool `xorm:"NOT NULL DEFAULT true"`
 
-	UpdatedUnix    util.TimeStamp `xorm:"INDEX"`
-	NextUpdateUnix util.TimeStamp `xorm:"INDEX"`
+	UpdatedUnix    timeutil.TimeStamp `xorm:"INDEX"`
+	NextUpdateUnix timeutil.TimeStamp `xorm:"INDEX"`
 
 	address string `xorm:"-"`
 }
@@ -43,8 +44,8 @@ type Mirror struct {
 // BeforeInsert will be invoked by XORM before inserting a record
 func (m *Mirror) BeforeInsert() {
 	if m != nil {
-		m.UpdatedUnix = util.TimeStampNow()
-		m.NextUpdateUnix = util.TimeStampNow()
+		m.UpdatedUnix = timeutil.TimeStampNow()
+		m.NextUpdateUnix = timeutil.TimeStampNow()
 	}
 }
 
@@ -64,7 +65,7 @@ func (m *Mirror) AfterLoad(session *xorm.Session) {
 // ScheduleNextUpdate calculates and sets next update time.
 func (m *Mirror) ScheduleNextUpdate() {
 	if m.Interval != 0 {
-		m.NextUpdateUnix = util.TimeStampNow().AddDuration(m.Interval)
+		m.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(m.Interval)
 	} else {
 		m.NextUpdateUnix = 0
 	}
@@ -277,7 +278,7 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
 		cache.Remove(m.Repo.GetCommitsCountCacheKey(branches[i].Name, true))
 	}
 
-	m.UpdatedUnix = util.TimeStampNow()
+	m.UpdatedUnix = timeutil.TimeStampNow()
 	return parseRemoteUpdateOutput(output), true
 }
 
diff --git a/models/repo_unit.go b/models/repo_unit.go
index 80126270de..b2c88d3193 100644
--- a/models/repo_unit.go
+++ b/models/repo_unit.go
@@ -7,7 +7,7 @@ package models
 import (
 	"encoding/json"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/Unknwon/com"
 	"github.com/go-xorm/xorm"
@@ -17,10 +17,10 @@ import (
 // RepoUnit describes all units of a repository
 type RepoUnit struct {
 	ID          int64
-	RepoID      int64           `xorm:"INDEX(s)"`
-	Type        UnitType        `xorm:"INDEX(s)"`
-	Config      core.Conversion `xorm:"TEXT"`
-	CreatedUnix util.TimeStamp  `xorm:"INDEX CREATED"`
+	RepoID      int64              `xorm:"INDEX(s)"`
+	Type        UnitType           `xorm:"INDEX(s)"`
+	Config      core.Conversion    `xorm:"TEXT"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
 }
 
 // UnitConfig describes common unit config
diff --git a/models/review.go b/models/review.go
index e31e936bcc..454d16ee88 100644
--- a/models/review.go
+++ b/models/review.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 
 	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-xorm/xorm"
 	"xorm.io/builder"
@@ -56,8 +56,8 @@ type Review struct {
 	IssueID    int64  `xorm:"index"`
 	Content    string
 
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 
 	// CodeComments are the initial code comments of the review
 	CodeComments CodeComments `xorm:"-"`
@@ -279,7 +279,7 @@ func UpdateReview(r *Review) error {
 type PullReviewersWithType struct {
 	User              `xorm:"extends"`
 	Type              ReviewType
-	ReviewUpdatedUnix util.TimeStamp `xorm:"review_updated_unix"`
+	ReviewUpdatedUnix timeutil.TimeStamp `xorm:"review_updated_unix"`
 }
 
 // GetReviewersByPullID gets all reviewers for a pull request with the statuses
diff --git a/models/ssh_key.go b/models/ssh_key.go
index 4f93b5c44f..874f6b19f7 100644
--- a/models/ssh_key.go
+++ b/models/ssh_key.go
@@ -26,7 +26,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/process"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/Unknwon/com"
 	"github.com/go-xorm/xorm"
@@ -62,16 +62,16 @@ type PublicKey struct {
 	Type          KeyType    `xorm:"NOT NULL DEFAULT 1"`
 	LoginSourceID int64      `xorm:"NOT NULL DEFAULT 0"`
 
-	CreatedUnix       util.TimeStamp `xorm:"created"`
-	UpdatedUnix       util.TimeStamp `xorm:"updated"`
-	HasRecentActivity bool           `xorm:"-"`
-	HasUsed           bool           `xorm:"-"`
+	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
+	UpdatedUnix       timeutil.TimeStamp `xorm:"updated"`
+	HasRecentActivity bool               `xorm:"-"`
+	HasUsed           bool               `xorm:"-"`
 }
 
 // AfterLoad is invoked from XORM after setting the values of all fields of this object.
 func (key *PublicKey) AfterLoad() {
 	key.HasUsed = key.UpdatedUnix > key.CreatedUnix
-	key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow()
+	key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
 }
 
 // OmitEmail returns content of public key without email address.
@@ -581,7 +581,7 @@ func UpdatePublicKeyUpdated(id int64) error {
 	}
 
 	_, err := x.ID(id).Cols("updated_unix").Update(&PublicKey{
-		UpdatedUnix: util.TimeStampNow(),
+		UpdatedUnix: timeutil.TimeStampNow(),
 	})
 	if err != nil {
 		return err
@@ -714,16 +714,16 @@ type DeployKey struct {
 
 	Mode AccessMode `xorm:"NOT NULL DEFAULT 1"`
 
-	CreatedUnix       util.TimeStamp `xorm:"created"`
-	UpdatedUnix       util.TimeStamp `xorm:"updated"`
-	HasRecentActivity bool           `xorm:"-"`
-	HasUsed           bool           `xorm:"-"`
+	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
+	UpdatedUnix       timeutil.TimeStamp `xorm:"updated"`
+	HasRecentActivity bool               `xorm:"-"`
+	HasUsed           bool               `xorm:"-"`
 }
 
 // AfterLoad is invoked from XORM after setting the values of all fields of this object.
 func (key *DeployKey) AfterLoad() {
 	key.HasUsed = key.UpdatedUnix > key.CreatedUnix
-	key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow()
+	key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
 }
 
 // GetContent gets associated public key content.
diff --git a/models/token.go b/models/token.go
index 030bff8e1b..8bd20a6916 100644
--- a/models/token.go
+++ b/models/token.go
@@ -9,11 +9,11 @@ import (
 	"crypto/subtle"
 	"time"
 
-	gouuid "github.com/satori/go.uuid"
-
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/generate"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
+
+	gouuid "github.com/satori/go.uuid"
 )
 
 // AccessToken represents a personal access token.
@@ -26,16 +26,16 @@ type AccessToken struct {
 	TokenSalt      string
 	TokenLastEight string `xorm:"token_last_eight"`
 
-	CreatedUnix       util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix       util.TimeStamp `xorm:"INDEX updated"`
-	HasRecentActivity bool           `xorm:"-"`
-	HasUsed           bool           `xorm:"-"`
+	CreatedUnix       timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix       timeutil.TimeStamp `xorm:"INDEX updated"`
+	HasRecentActivity bool               `xorm:"-"`
+	HasUsed           bool               `xorm:"-"`
 }
 
 // AfterLoad is invoked from XORM after setting the values of all fields of this object.
 func (t *AccessToken) AfterLoad() {
 	t.HasUsed = t.UpdatedUnix > t.CreatedUnix
-	t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > util.TimeStampNow()
+	t.HasRecentActivity = t.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
 }
 
 // NewAccessToken creates new access token.
diff --git a/models/topic.go b/models/topic.go
index 666196ba8e..8a587acc3a 100644
--- a/models/topic.go
+++ b/models/topic.go
@@ -9,7 +9,7 @@ import (
 	"regexp"
 	"strings"
 
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"xorm.io/builder"
 )
@@ -28,8 +28,8 @@ type Topic struct {
 	ID          int64
 	Name        string `xorm:"UNIQUE VARCHAR(25)"`
 	RepoCount   int
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 }
 
 // RepoTopic represents associated repositories and topics
diff --git a/models/twofactor.go b/models/twofactor.go
index 7f260248bc..00a465353a 100644
--- a/models/twofactor.go
+++ b/models/twofactor.go
@@ -16,12 +16,12 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/pquerna/otp/totp"
-	"golang.org/x/crypto/pbkdf2"
-
 	"code.gitea.io/gitea/modules/generate"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
+
+	"github.com/pquerna/otp/totp"
+	"golang.org/x/crypto/pbkdf2"
 )
 
 // TwoFactor represents a two-factor authentication token.
@@ -31,9 +31,9 @@ type TwoFactor struct {
 	Secret           string
 	ScratchSalt      string
 	ScratchHash      string
-	LastUsedPasscode string         `xorm:"VARCHAR(10)"`
-	CreatedUnix      util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix      util.TimeStamp `xorm:"INDEX updated"`
+	LastUsedPasscode string             `xorm:"VARCHAR(10)"`
+	CreatedUnix      timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix      timeutil.TimeStamp `xorm:"INDEX updated"`
 }
 
 // GenerateScratchToken recreates the scratch token the user is using.
diff --git a/models/u2f.go b/models/u2f.go
index 1224b4a5fb..28341906fa 100644
--- a/models/u2f.go
+++ b/models/u2f.go
@@ -6,7 +6,7 @@ package models
 
 import (
 	"code.gitea.io/gitea/modules/log"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/tstranex/u2f"
 )
@@ -17,9 +17,9 @@ type U2FRegistration struct {
 	Name        string
 	UserID      int64 `xorm:"INDEX"`
 	Raw         []byte
-	Counter     uint32         `xorm:"BIGINT"`
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	Counter     uint32             `xorm:"BIGINT"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 }
 
 // TableName returns a better table name for U2FRegistration
diff --git a/models/update.go b/models/update.go
index a19df70b3b..c6ea1a845e 100644
--- a/models/update.go
+++ b/models/update.go
@@ -11,7 +11,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/modules/git"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // env keys for git hooks need
@@ -140,7 +140,7 @@ func PushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string)
 			IsDraft:      false,
 			IsPrerelease: false,
 			IsTag:        true,
-			CreatedUnix:  util.TimeStamp(createdAt.Unix()),
+			CreatedUnix:  timeutil.TimeStamp(createdAt.Unix()),
 		}
 		if author != nil {
 			rel.PublisherID = author.ID
@@ -151,7 +151,7 @@ func PushUpdateAddTag(repo *Repository, gitRepo *git.Repository, tagName string)
 		}
 	} else {
 		rel.Sha1 = commit.ID.String()
-		rel.CreatedUnix = util.TimeStamp(createdAt.Unix())
+		rel.CreatedUnix = timeutil.TimeStamp(createdAt.Unix())
 		rel.NumCommits = commitsCount
 		rel.IsDraft = false
 		if rel.IsTag && author != nil {
diff --git a/models/user.go b/models/user.go
index 2e4f971662..3c0c581a1d 100644
--- a/models/user.go
+++ b/models/user.go
@@ -29,6 +29,7 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
 	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/Unknwon/com"
@@ -110,9 +111,9 @@ type User struct {
 	Language    string `xorm:"VARCHAR(5)"`
 	Description string
 
-	CreatedUnix   util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix   util.TimeStamp `xorm:"INDEX updated"`
-	LastLoginUnix util.TimeStamp `xorm:"INDEX"`
+	CreatedUnix   timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix   timeutil.TimeStamp `xorm:"INDEX updated"`
+	LastLoginUnix timeutil.TimeStamp `xorm:"INDEX"`
 
 	// Remember visibility choice for convenience, true for private
 	LastRepoVisibility bool
@@ -190,7 +191,7 @@ func (u *User) AfterLoad() {
 
 // SetLastLogin set time to last login
 func (u *User) SetLastLogin() {
-	u.LastLoginUnix = util.TimeStampNow()
+	u.LastLoginUnix = timeutil.TimeStampNow()
 }
 
 // UpdateDiffViewStyle updates the users diff view style
diff --git a/models/user_heatmap.go b/models/user_heatmap.go
index 3fdabbbf79..b511b5fa62 100644
--- a/models/user_heatmap.go
+++ b/models/user_heatmap.go
@@ -6,13 +6,13 @@ package models
 
 import (
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // UserHeatmapData represents the data needed to create a heatmap
 type UserHeatmapData struct {
-	Timestamp     util.TimeStamp `json:"timestamp"`
-	Contributions int64          `json:"contributions"`
+	Timestamp     timeutil.TimeStamp `json:"timestamp"`
+	Contributions int64              `json:"contributions"`
 }
 
 // GetUserHeatmapDataByUser returns an array of UserHeatmapData
@@ -35,7 +35,7 @@ func GetUserHeatmapDataByUser(user *User) ([]*UserHeatmapData, error) {
 	sess := x.Select(groupBy+" AS timestamp, count(user_id) as contributions").
 		Table("action").
 		Where("user_id = ?", user.ID).
-		And("created_unix > ?", (util.TimeStampNow() - 31536000))
+		And("created_unix > ?", (timeutil.TimeStampNow() - 31536000))
 
 	// * Heatmaps for individual users only include actions that the user themself
 	//   did.
diff --git a/models/webhook.go b/models/webhook.go
index ac39501ca1..1d41f2a969 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -23,7 +23,8 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/sync"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
+
 	"github.com/Unknwon/com"
 	gouuid "github.com/satori/go.uuid"
 )
@@ -118,8 +119,8 @@ type Webhook struct {
 	Meta         string     `xorm:"TEXT"` // store hook-specific attributes
 	LastStatus   HookStatus // Last delivery status
 
-	CreatedUnix util.TimeStamp `xorm:"INDEX created"`
-	UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
+	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
 }
 
 // AfterLoad updates the webhook object upon setting a column
diff --git a/modules/auth/auth.go b/modules/auth/auth.go
index 2a2ee40492..1013628073 100644
--- a/modules/auth/auth.go
+++ b/modules/auth/auth.go
@@ -20,7 +20,7 @@ import (
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/validation"
 )
 
@@ -68,7 +68,7 @@ func SignedInID(ctx *macaron.Context, sess session.Store) int64 {
 				}
 				return 0
 			}
-			t.UpdatedUnix = util.TimeStampNow()
+			t.UpdatedUnix = timeutil.TimeStampNow()
 			if err = models.UpdateAccessToken(t); err != nil {
 				log.Error("UpdateAccessToken: %v", err)
 			}
@@ -210,7 +210,7 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
 						return nil, false
 					}
 				}
-				token.UpdatedUnix = util.TimeStampNow()
+				token.UpdatedUnix = timeutil.TimeStampNow()
 				if err = models.UpdateAccessToken(token); err != nil {
 					log.Error("UpdateAccessToken:  %v", err)
 				}
diff --git a/modules/base/tool.go b/modules/base/tool.go
index 2bb39dbfea..43678979a5 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -12,7 +12,6 @@ import (
 	"encoding/base64"
 	"encoding/hex"
 	"fmt"
-	"html/template"
 	"io"
 	"math"
 	"net/http"
@@ -29,10 +28,8 @@ import (
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
 
 	"github.com/Unknwon/com"
-	"github.com/Unknwon/i18n"
 )
 
 // EncodeMD5 encodes string to md5 hex value.
@@ -217,154 +214,6 @@ func AvatarLink(email string) string {
 	return SizedAvatarLink(email, DefaultAvatarSize)
 }
 
-// Seconds-based time units
-const (
-	Minute = 60
-	Hour   = 60 * Minute
-	Day    = 24 * Hour
-	Week   = 7 * Day
-	Month  = 30 * Day
-	Year   = 12 * Month
-)
-
-func computeTimeDiff(diff int64, lang string) (int64, string) {
-	diffStr := ""
-	switch {
-	case diff <= 0:
-		diff = 0
-		diffStr = i18n.Tr(lang, "tool.now")
-	case diff < 2:
-		diff = 0
-		diffStr = i18n.Tr(lang, "tool.1s")
-	case diff < 1*Minute:
-		diffStr = i18n.Tr(lang, "tool.seconds", diff)
-		diff = 0
-
-	case diff < 2*Minute:
-		diff -= 1 * Minute
-		diffStr = i18n.Tr(lang, "tool.1m")
-	case diff < 1*Hour:
-		diffStr = i18n.Tr(lang, "tool.minutes", diff/Minute)
-		diff -= diff / Minute * Minute
-
-	case diff < 2*Hour:
-		diff -= 1 * Hour
-		diffStr = i18n.Tr(lang, "tool.1h")
-	case diff < 1*Day:
-		diffStr = i18n.Tr(lang, "tool.hours", diff/Hour)
-		diff -= diff / Hour * Hour
-
-	case diff < 2*Day:
-		diff -= 1 * Day
-		diffStr = i18n.Tr(lang, "tool.1d")
-	case diff < 1*Week:
-		diffStr = i18n.Tr(lang, "tool.days", diff/Day)
-		diff -= diff / Day * Day
-
-	case diff < 2*Week:
-		diff -= 1 * Week
-		diffStr = i18n.Tr(lang, "tool.1w")
-	case diff < 1*Month:
-		diffStr = i18n.Tr(lang, "tool.weeks", diff/Week)
-		diff -= diff / Week * Week
-
-	case diff < 2*Month:
-		diff -= 1 * Month
-		diffStr = i18n.Tr(lang, "tool.1mon")
-	case diff < 1*Year:
-		diffStr = i18n.Tr(lang, "tool.months", diff/Month)
-		diff -= diff / Month * Month
-
-	case diff < 2*Year:
-		diff -= 1 * Year
-		diffStr = i18n.Tr(lang, "tool.1y")
-	default:
-		diffStr = i18n.Tr(lang, "tool.years", diff/Year)
-		diff -= (diff / Year) * Year
-	}
-	return diff, diffStr
-}
-
-// MinutesToFriendly returns a user friendly string with number of minutes
-// converted to hours and minutes.
-func MinutesToFriendly(minutes int, lang string) string {
-	duration := time.Duration(minutes) * time.Minute
-	return TimeSincePro(time.Now().Add(-duration), lang)
-}
-
-// TimeSincePro calculates the time interval and generate full user-friendly string.
-func TimeSincePro(then time.Time, lang string) string {
-	return timeSincePro(then, time.Now(), lang)
-}
-
-func timeSincePro(then, now time.Time, lang string) string {
-	diff := now.Unix() - then.Unix()
-
-	if then.After(now) {
-		return i18n.Tr(lang, "tool.future")
-	}
-	if diff == 0 {
-		return i18n.Tr(lang, "tool.now")
-	}
-
-	var timeStr, diffStr string
-	for {
-		if diff == 0 {
-			break
-		}
-
-		diff, diffStr = computeTimeDiff(diff, lang)
-		timeStr += ", " + diffStr
-	}
-	return strings.TrimPrefix(timeStr, ", ")
-}
-
-func timeSince(then, now time.Time, lang string) string {
-	return timeSinceUnix(then.Unix(), now.Unix(), lang)
-}
-
-func timeSinceUnix(then, now int64, lang string) string {
-	lbl := "tool.ago"
-	diff := now - then
-	if then > now {
-		lbl = "tool.from_now"
-		diff = then - now
-	}
-	if diff <= 0 {
-		return i18n.Tr(lang, "tool.now")
-	}
-
-	_, diffStr := computeTimeDiff(diff, lang)
-	return i18n.Tr(lang, lbl, diffStr)
-}
-
-// RawTimeSince retrieves i18n key of time since t
-func RawTimeSince(t time.Time, lang string) string {
-	return timeSince(t, time.Now(), lang)
-}
-
-// TimeSince calculates the time interval and generate user-friendly string.
-func TimeSince(then time.Time, lang string) template.HTML {
-	return htmlTimeSince(then, time.Now(), lang)
-}
-
-func htmlTimeSince(then, now time.Time, lang string) template.HTML {
-	return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`,
-		then.Format(setting.TimeFormat),
-		timeSince(then, now, lang)))
-}
-
-// TimeSinceUnix calculates the time interval and generate user-friendly string.
-func TimeSinceUnix(then util.TimeStamp, lang string) template.HTML {
-	return htmlTimeSinceUnix(then, util.TimeStamp(time.Now().Unix()), lang)
-}
-
-func htmlTimeSinceUnix(then, now util.TimeStamp, lang string) template.HTML {
-	return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`,
-		then.Format(setting.TimeFormat),
-		timeSinceUnix(int64(then), int64(now), lang)))
-}
-
 // Storage space size types
 const (
 	Byte  = 1
diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go
index 3f1eebfc4b..3aa86c5cbf 100644
--- a/modules/base/tool_test.go
+++ b/modules/base/tool_test.go
@@ -2,43 +2,13 @@ package base
 
 import (
 	"net/url"
-	"os"
 	"testing"
-	"time"
 
 	"code.gitea.io/gitea/modules/setting"
 
-	"github.com/Unknwon/i18n"
-	macaroni18n "github.com/go-macaron/i18n"
 	"github.com/stretchr/testify/assert"
 )
 
-var BaseDate time.Time
-
-// time durations
-const (
-	DayDur   = 24 * time.Hour
-	WeekDur  = 7 * DayDur
-	MonthDur = 30 * DayDur
-	YearDur  = 12 * MonthDur
-)
-
-func TestMain(m *testing.M) {
-	// setup
-	macaroni18n.I18n(macaroni18n.Options{
-		Directory:   "../../options/locale/",
-		DefaultLang: "en-US",
-		Langs:       []string{"en-US"},
-		Names:       []string{"english"},
-	})
-	BaseDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
-
-	// run the tests
-	retVal := m.Run()
-
-	os.Exit(retVal)
-}
-
 func TestEncodeMD5(t *testing.T) {
 	assert.Equal(t,
 		"3858f62230ac3c915f300c664312c63f",
@@ -131,125 +101,6 @@ func TestAvatarLink(t *testing.T) {
 	)
 }
 
-func TestComputeTimeDiff(t *testing.T) {
-	// test that for each offset in offsets,
-	// computeTimeDiff(base + offset) == (offset, str)
-	test := func(base int64, str string, offsets ...int64) {
-		for _, offset := range offsets {
-			diff, diffStr := computeTimeDiff(base+offset, "en")
-			assert.Equal(t, offset, diff)
-			assert.Equal(t, str, diffStr)
-		}
-	}
-	test(0, "now", 0)
-	test(1, "1 second", 0)
-	test(2, "2 seconds", 0)
-	test(Minute, "1 minute", 0, 1, 30, Minute-1)
-	test(2*Minute, "2 minutes", 0, Minute-1)
-	test(Hour, "1 hour", 0, 1, Hour-1)
-	test(5*Hour, "5 hours", 0, Hour-1)
-	test(Day, "1 day", 0, 1, Day-1)
-	test(5*Day, "5 days", 0, Day-1)
-	test(Week, "1 week", 0, 1, Week-1)
-	test(3*Week, "3 weeks", 0, 4*Day+25000)
-	test(Month, "1 month", 0, 1, Month-1)
-	test(10*Month, "10 months", 0, Month-1)
-	test(Year, "1 year", 0, Year-1)
-	test(3*Year, "3 years", 0, Year-1)
-}
-
-func TestMinutesToFriendly(t *testing.T) {
-	// test that a number of minutes yields the expected string
-	test := func(expected string, minutes int) {
-		actual := MinutesToFriendly(minutes, "en")
-		assert.Equal(t, expected, actual)
-	}
-	test("1 minute", 1)
-	test("2 minutes", 2)
-	test("1 hour", 60)
-	test("1 hour, 1 minute", 61)
-	test("1 hour, 2 minutes", 62)
-	test("2 hours", 120)
-}
-
-func TestTimeSince(t *testing.T) {
-	assert.Equal(t, "now", timeSince(BaseDate, BaseDate, "en"))
-
-	// test that each diff in `diffs` yields the expected string
-	test := func(expected string, diffs ...time.Duration) {
-		for _, diff := range diffs {
-			actual := timeSince(BaseDate, BaseDate.Add(diff), "en")
-			assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual)
-			actual = timeSince(BaseDate.Add(diff), BaseDate, "en")
-			assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual)
-		}
-	}
-	test("1 second", time.Second, time.Second+50*time.Millisecond)
-	test("2 seconds", 2*time.Second, 2*time.Second+50*time.Millisecond)
-	test("1 minute", time.Minute, time.Minute+30*time.Second)
-	test("2 minutes", 2*time.Minute, 2*time.Minute+30*time.Second)
-	test("1 hour", time.Hour, time.Hour+30*time.Minute)
-	test("2 hours", 2*time.Hour, 2*time.Hour+30*time.Minute)
-	test("1 day", DayDur, DayDur+12*time.Hour)
-	test("2 days", 2*DayDur, 2*DayDur+12*time.Hour)
-	test("1 week", WeekDur, WeekDur+3*DayDur)
-	test("2 weeks", 2*WeekDur, 2*WeekDur+3*DayDur)
-	test("1 month", MonthDur, MonthDur+15*DayDur)
-	test("2 months", 2*MonthDur, 2*MonthDur+15*DayDur)
-	test("1 year", YearDur, YearDur+6*MonthDur)
-	test("2 years", 2*YearDur, 2*YearDur+6*MonthDur)
-}
-
-func TestTimeSincePro(t *testing.T) {
-	assert.Equal(t, "now", timeSincePro(BaseDate, BaseDate, "en"))
-
-	// test that a difference of `diff` yields the expected string
-	test := func(expected string, diff time.Duration) {
-		actual := timeSincePro(BaseDate, BaseDate.Add(diff), "en")
-		assert.Equal(t, expected, actual)
-		assert.Equal(t, "future", timeSincePro(BaseDate.Add(diff), BaseDate, "en"))
-	}
-	test("1 second", time.Second)
-	test("2 seconds", 2*time.Second)
-	test("1 minute", time.Minute)
-	test("1 minute, 1 second", time.Minute+time.Second)
-	test("1 minute, 59 seconds", time.Minute+59*time.Second)
-	test("2 minutes", 2*time.Minute)
-	test("1 hour", time.Hour)
-	test("1 hour, 1 second", time.Hour+time.Second)
-	test("1 hour, 59 minutes, 59 seconds", time.Hour+59*time.Minute+59*time.Second)
-	test("2 hours", 2*time.Hour)
-	test("1 day", DayDur)
-	test("1 day, 23 hours, 59 minutes, 59 seconds",
-		DayDur+23*time.Hour+59*time.Minute+59*time.Second)
-	test("2 days", 2*DayDur)
-	test("1 week", WeekDur)
-	test("2 weeks", 2*WeekDur)
-	test("1 month", MonthDur)
-	test("3 months", 3*MonthDur)
-	test("1 year", YearDur)
-	test("2 years, 3 months, 1 week, 2 days, 4 hours, 12 minutes, 17 seconds",
-		2*YearDur+3*MonthDur+WeekDur+2*DayDur+4*time.Hour+
-			12*time.Minute+17*time.Second)
-}
-
-func TestHtmlTimeSince(t *testing.T) {
-	setting.TimeFormat = time.UnixDate
-	// test that `diff` yields a result containing `expected`
-	test := func(expected string, diff time.Duration) {
-		actual := htmlTimeSince(BaseDate, BaseDate.Add(diff), "en")
-		assert.Contains(t, actual, `title="Sat Jan  1 00:00:00 UTC 2000"`)
-		assert.Contains(t, actual, expected)
-	}
-	test("1 second", time.Second)
-	test("3 minutes", 3*time.Minute+5*time.Second)
-	test("1 day", DayDur+18*time.Hour)
-	test("1 week", WeekDur+6*DayDur)
-	test("3 months", 3*MonthDur+3*WeekDur)
-	test("2 years", 2*YearDur)
-	test("3 years", 3*YearDur+11*MonthDur+4*WeekDur)
-}
-
 func TestFileSize(t *testing.T) {
 	var size int64 = 512
 	assert.Equal(t, "512B", FileSize(size))
diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea.go
index 08244e5ab2..ddfc2ca271 100644
--- a/modules/migrations/gitea.go
+++ b/modules/migrations/gitea.go
@@ -21,7 +21,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/migrations/base"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	gouuid "github.com/satori/go.uuid"
 )
@@ -106,12 +106,12 @@ func (g *GiteaLocalUploader) CreateTopics(topics ...string) error {
 func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) error {
 	var mss = make([]*models.Milestone, 0, len(milestones))
 	for _, milestone := range milestones {
-		var deadline util.TimeStamp
+		var deadline timeutil.TimeStamp
 		if milestone.Deadline != nil {
-			deadline = util.TimeStamp(milestone.Deadline.Unix())
+			deadline = timeutil.TimeStamp(milestone.Deadline.Unix())
 		}
 		if deadline == 0 {
-			deadline = util.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.UILocation).Unix())
+			deadline = timeutil.TimeStamp(time.Date(9999, 1, 1, 0, 0, 0, 0, setting.DefaultUILocation).Unix())
 		}
 		var ms = models.Milestone{
 			RepoID:       g.repo.ID,
@@ -121,7 +121,7 @@ func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) err
 			DeadlineUnix: deadline,
 		}
 		if ms.IsClosed && milestone.Closed != nil {
-			ms.ClosedDateUnix = util.TimeStamp(milestone.Closed.Unix())
+			ms.ClosedDateUnix = timeutil.TimeStamp(milestone.Closed.Unix())
 		}
 		mss = append(mss, &ms)
 	}
@@ -175,7 +175,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
 			IsDraft:      release.Draft,
 			IsPrerelease: release.Prerelease,
 			IsTag:        false,
-			CreatedUnix:  util.TimeStamp(release.Created.Unix()),
+			CreatedUnix:  timeutil.TimeStamp(release.Created.Unix()),
 		}
 
 		// calc NumCommits
@@ -194,7 +194,7 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
 				Name:          asset.Name,
 				DownloadCount: int64(*asset.DownloadCount),
 				Size:          int64(*asset.Size),
-				CreatedUnix:   util.TimeStamp(asset.Created.Unix()),
+				CreatedUnix:   timeutil.TimeStamp(asset.Created.Unix()),
 			}
 
 			// download attachment
@@ -265,10 +265,10 @@ func (g *GiteaLocalUploader) CreateIssues(issues ...*base.Issue) error {
 			IsLocked:         issue.IsLocked,
 			MilestoneID:      milestoneID,
 			Labels:           labels,
-			CreatedUnix:      util.TimeStamp(issue.Created.Unix()),
+			CreatedUnix:      timeutil.TimeStamp(issue.Created.Unix()),
 		}
 		if issue.Closed != nil {
-			is.ClosedUnix = util.TimeStamp(issue.Closed.Unix())
+			is.ClosedUnix = timeutil.TimeStamp(issue.Closed.Unix())
 		}
 		// TODO: add reactions
 		iss = append(iss, &is)
@@ -307,7 +307,7 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
 			OriginalAuthor:   comment.PosterName,
 			OriginalAuthorID: comment.PosterID,
 			Content:          comment.Content,
-			CreatedUnix:      util.TimeStamp(comment.Created.Unix()),
+			CreatedUnix:      timeutil.TimeStamp(comment.Created.Unix()),
 		})
 
 		// TODO: Reactions
@@ -453,15 +453,15 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*models.PullR
 			IsClosed:         pr.State == "closed",
 			IsLocked:         pr.IsLocked,
 			Labels:           labels,
-			CreatedUnix:      util.TimeStamp(pr.Created.Unix()),
+			CreatedUnix:      timeutil.TimeStamp(pr.Created.Unix()),
 		},
 	}
 
 	if pullRequest.Issue.IsClosed && pr.Closed != nil {
-		pullRequest.Issue.ClosedUnix = util.TimeStamp(pr.Closed.Unix())
+		pullRequest.Issue.ClosedUnix = timeutil.TimeStamp(pr.Closed.Unix())
 	}
 	if pullRequest.HasMerged && pr.MergedTime != nil {
-		pullRequest.MergedUnix = util.TimeStamp(pr.MergedTime.Unix())
+		pullRequest.MergedUnix = timeutil.TimeStamp(pr.MergedTime.Unix())
 		pullRequest.MergedCommitID = pr.MergeCommitSHA
 		pullRequest.MergerID = g.doer.ID
 	}
diff --git a/modules/pull/merge.go b/modules/pull/merge.go
index cf2fb7fc4f..ef154128c7 100644
--- a/modules/pull/merge.go
+++ b/modules/pull/merge.go
@@ -21,7 +21,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // Merge merges pull request to base repository.
@@ -258,7 +258,7 @@ func Merge(pr *models.PullRequest, doer *models.User, baseGitRepo *git.Repositor
 		return fmt.Errorf("GetBranchCommit: %v", err)
 	}
 
-	pr.MergedUnix = util.TimeStampNow()
+	pr.MergedUnix = timeutil.TimeStampNow()
 	pr.Merger = doer
 	pr.MergerID = doer.ID
 
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 97bdc03cc9..97db7eaf9e 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -278,6 +278,8 @@ var (
 
 	// Time settings
 	TimeFormat string
+	// UILocation is the location on the UI, so that we can display the time on UI.
+	DefaultUILocation = time.Local
 
 	CSRFCookieName     = "_csrf"
 	CSRFCookieHTTPOnly = true
@@ -356,10 +358,6 @@ var (
 	HasRobotsTxt      bool
 	InternalToken     string // internal access token
 	IterateBufferSize int
-
-	// UILocation is the location on the UI, so that we can display the time on UI.
-	// Currently only show the default time.Local, it could be added to app.ini after UI is ready
-	UILocation = time.Local
 )
 
 // DateLang transforms standard language locale name to corresponding value in datetime plugin.
@@ -792,32 +790,47 @@ func NewContext() {
 	AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
 	AttachmentEnabled = sec.Key("ENABLED").MustBool(true)
 
-	TimeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("RFC1123")
-	TimeFormat = map[string]string{
-		"ANSIC":       time.ANSIC,
-		"UnixDate":    time.UnixDate,
-		"RubyDate":    time.RubyDate,
-		"RFC822":      time.RFC822,
-		"RFC822Z":     time.RFC822Z,
-		"RFC850":      time.RFC850,
-		"RFC1123":     time.RFC1123,
-		"RFC1123Z":    time.RFC1123Z,
-		"RFC3339":     time.RFC3339,
-		"RFC3339Nano": time.RFC3339Nano,
-		"Kitchen":     time.Kitchen,
-		"Stamp":       time.Stamp,
-		"StampMilli":  time.StampMilli,
-		"StampMicro":  time.StampMicro,
-		"StampNano":   time.StampNano,
-	}[TimeFormatKey]
-	// When the TimeFormatKey does not exist in the previous map e.g.'2006-01-02 15:04:05'
-	if len(TimeFormat) == 0 {
-		TimeFormat = TimeFormatKey
-		TestTimeFormat, _ := time.Parse(TimeFormat, TimeFormat)
-		if TestTimeFormat.Format(time.RFC3339) != "2006-01-02T15:04:05Z" {
-			log.Fatal("Can't create time properly, please check your time format has 2006, 01, 02, 15, 04 and 05")
+	timeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("")
+	if timeFormatKey != "" {
+		TimeFormat = map[string]string{
+			"ANSIC":       time.ANSIC,
+			"UnixDate":    time.UnixDate,
+			"RubyDate":    time.RubyDate,
+			"RFC822":      time.RFC822,
+			"RFC822Z":     time.RFC822Z,
+			"RFC850":      time.RFC850,
+			"RFC1123":     time.RFC1123,
+			"RFC1123Z":    time.RFC1123Z,
+			"RFC3339":     time.RFC3339,
+			"RFC3339Nano": time.RFC3339Nano,
+			"Kitchen":     time.Kitchen,
+			"Stamp":       time.Stamp,
+			"StampMilli":  time.StampMilli,
+			"StampMicro":  time.StampMicro,
+			"StampNano":   time.StampNano,
+		}[timeFormatKey]
+		// When the TimeFormatKey does not exist in the previous map e.g.'2006-01-02 15:04:05'
+		if len(TimeFormat) == 0 {
+			TimeFormat = timeFormatKey
+			TestTimeFormat, _ := time.Parse(TimeFormat, TimeFormat)
+			if TestTimeFormat.Format(time.RFC3339) != "2006-01-02T15:04:05Z" {
+				log.Fatal("Can't create time properly, please check your time format has 2006, 01, 02, 15, 04 and 05")
+			}
+			log.Trace("Custom TimeFormat: %s", TimeFormat)
 		}
-		log.Trace("Custom TimeFormat: %s", TimeFormat)
+	}
+
+	zone := Cfg.Section("time").Key("DEFAULT_UI_LOCATION").String()
+	if zone != "" {
+		DefaultUILocation, err = time.LoadLocation(zone)
+		if err != nil {
+			log.Fatal("Load time zone failed: %v", err)
+		} else {
+			log.Info("Default UI Location is %v", zone)
+		}
+	}
+	if DefaultUILocation == nil {
+		DefaultUILocation = time.Local
 	}
 
 	RunUser = Cfg.Section("").Key("RUN_USER").MustString(user.CurrentUsername())
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index 17df375a80..e0285808f8 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -20,13 +20,13 @@ import (
 	"strings"
 	"time"
 
-	"code.gitea.io/gitea/modules/util"
-
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
+	"code.gitea.io/gitea/modules/util"
 
 	"gopkg.in/editorconfig/editorconfig-core-go.v1"
 )
@@ -74,9 +74,9 @@ func NewFuncMap() []template.FuncMap {
 		"Safe":          Safe,
 		"SafeJS":        SafeJS,
 		"Str2html":      Str2html,
-		"TimeSince":     base.TimeSince,
-		"TimeSinceUnix": base.TimeSinceUnix,
-		"RawTimeSince":  base.RawTimeSince,
+		"TimeSince":     timeutil.TimeSince,
+		"TimeSinceUnix": timeutil.TimeSinceUnix,
+		"RawTimeSince":  timeutil.RawTimeSince,
 		"FileSize":      base.FileSize,
 		"Subtract":      base.Subtract,
 		"EntryIcon":     base.EntryIcon,
diff --git a/modules/timeutil/language.go b/modules/timeutil/language.go
new file mode 100644
index 0000000000..121b50f277
--- /dev/null
+++ b/modules/timeutil/language.go
@@ -0,0 +1,36 @@
+// Copyright 2019 The Gitea 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 timeutil
+
+import (
+	"time"
+
+	"code.gitea.io/gitea/modules/setting"
+)
+
+var (
+	langTimeFormats = map[string]string{
+		"zh-CN": "2006年01月02日 15时04分05秒",
+		"en-US": time.RFC1123,
+		"lv-LV": "02.01.2006. 15:04:05",
+	}
+)
+
+// GetLangTimeFormat represents the default time format for the language
+func GetLangTimeFormat(lang string) string {
+	return langTimeFormats[lang]
+}
+
+// GetTimeFormat represents the
+func GetTimeFormat(lang string) string {
+	if setting.TimeFormat == "" {
+		format := GetLangTimeFormat(lang)
+		if format == "" {
+			format = time.RFC1123
+		}
+		return format
+	}
+	return setting.TimeFormat
+}
diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go
new file mode 100644
index 0000000000..9ba111e54e
--- /dev/null
+++ b/modules/timeutil/since.go
@@ -0,0 +1,164 @@
+// Copyright 2019 The Gitea 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 timeutil
+
+import (
+	"fmt"
+	"html/template"
+	"strings"
+	"time"
+
+	"code.gitea.io/gitea/modules/setting"
+
+	"github.com/Unknwon/i18n"
+)
+
+// Seconds-based time units
+const (
+	Minute = 60
+	Hour   = 60 * Minute
+	Day    = 24 * Hour
+	Week   = 7 * Day
+	Month  = 30 * Day
+	Year   = 12 * Month
+)
+
+func computeTimeDiff(diff int64, lang string) (int64, string) {
+	diffStr := ""
+	switch {
+	case diff <= 0:
+		diff = 0
+		diffStr = i18n.Tr(lang, "tool.now")
+	case diff < 2:
+		diff = 0
+		diffStr = i18n.Tr(lang, "tool.1s")
+	case diff < 1*Minute:
+		diffStr = i18n.Tr(lang, "tool.seconds", diff)
+		diff = 0
+
+	case diff < 2*Minute:
+		diff -= 1 * Minute
+		diffStr = i18n.Tr(lang, "tool.1m")
+	case diff < 1*Hour:
+		diffStr = i18n.Tr(lang, "tool.minutes", diff/Minute)
+		diff -= diff / Minute * Minute
+
+	case diff < 2*Hour:
+		diff -= 1 * Hour
+		diffStr = i18n.Tr(lang, "tool.1h")
+	case diff < 1*Day:
+		diffStr = i18n.Tr(lang, "tool.hours", diff/Hour)
+		diff -= diff / Hour * Hour
+
+	case diff < 2*Day:
+		diff -= 1 * Day
+		diffStr = i18n.Tr(lang, "tool.1d")
+	case diff < 1*Week:
+		diffStr = i18n.Tr(lang, "tool.days", diff/Day)
+		diff -= diff / Day * Day
+
+	case diff < 2*Week:
+		diff -= 1 * Week
+		diffStr = i18n.Tr(lang, "tool.1w")
+	case diff < 1*Month:
+		diffStr = i18n.Tr(lang, "tool.weeks", diff/Week)
+		diff -= diff / Week * Week
+
+	case diff < 2*Month:
+		diff -= 1 * Month
+		diffStr = i18n.Tr(lang, "tool.1mon")
+	case diff < 1*Year:
+		diffStr = i18n.Tr(lang, "tool.months", diff/Month)
+		diff -= diff / Month * Month
+
+	case diff < 2*Year:
+		diff -= 1 * Year
+		diffStr = i18n.Tr(lang, "tool.1y")
+	default:
+		diffStr = i18n.Tr(lang, "tool.years", diff/Year)
+		diff -= (diff / Year) * Year
+	}
+	return diff, diffStr
+}
+
+// MinutesToFriendly returns a user friendly string with number of minutes
+// converted to hours and minutes.
+func MinutesToFriendly(minutes int, lang string) string {
+	duration := time.Duration(minutes) * time.Minute
+	return TimeSincePro(time.Now().Add(-duration), lang)
+}
+
+// TimeSincePro calculates the time interval and generate full user-friendly string.
+func TimeSincePro(then time.Time, lang string) string {
+	return timeSincePro(then, time.Now(), lang)
+}
+
+func timeSincePro(then, now time.Time, lang string) string {
+	diff := now.Unix() - then.Unix()
+
+	if then.After(now) {
+		return i18n.Tr(lang, "tool.future")
+	}
+	if diff == 0 {
+		return i18n.Tr(lang, "tool.now")
+	}
+
+	var timeStr, diffStr string
+	for {
+		if diff == 0 {
+			break
+		}
+
+		diff, diffStr = computeTimeDiff(diff, lang)
+		timeStr += ", " + diffStr
+	}
+	return strings.TrimPrefix(timeStr, ", ")
+}
+
+func timeSince(then, now time.Time, lang string) string {
+	return timeSinceUnix(then.Unix(), now.Unix(), lang)
+}
+
+func timeSinceUnix(then, now int64, lang string) string {
+	lbl := "tool.ago"
+	diff := now - then
+	if then > now {
+		lbl = "tool.from_now"
+		diff = then - now
+	}
+	if diff <= 0 {
+		return i18n.Tr(lang, "tool.now")
+	}
+
+	_, diffStr := computeTimeDiff(diff, lang)
+	return i18n.Tr(lang, lbl, diffStr)
+}
+
+// RawTimeSince retrieves i18n key of time since t
+func RawTimeSince(t time.Time, lang string) string {
+	return timeSince(t, time.Now(), lang)
+}
+
+// TimeSince calculates the time interval and generate user-friendly string.
+func TimeSince(then time.Time, lang string) template.HTML {
+	return htmlTimeSince(then, time.Now(), lang)
+}
+
+func htmlTimeSince(then, now time.Time, lang string) template.HTML {
+	return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`,
+		then.In(setting.DefaultUILocation).Format(GetTimeFormat(lang)),
+		timeSince(then, now, lang)))
+}
+
+// TimeSinceUnix calculates the time interval and generate user-friendly string.
+func TimeSinceUnix(then TimeStamp, lang string) template.HTML {
+	return htmlTimeSinceUnix(then, TimeStamp(time.Now().Unix()), lang)
+}
+
+func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML {
+	return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`,
+		then.FormatInLocation(GetTimeFormat(lang), setting.DefaultUILocation),
+		timeSinceUnix(int64(then), int64(now), lang)))
+}
diff --git a/modules/timeutil/since_test.go b/modules/timeutil/since_test.go
new file mode 100644
index 0000000000..c016e4ac65
--- /dev/null
+++ b/modules/timeutil/since_test.go
@@ -0,0 +1,163 @@
+// Copyright 2019 The Gitea 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 timeutil
+
+import (
+	"os"
+	"testing"
+	"time"
+
+	"code.gitea.io/gitea/modules/setting"
+
+	"github.com/Unknwon/i18n"
+	macaroni18n "github.com/go-macaron/i18n"
+	"github.com/stretchr/testify/assert"
+)
+
+var BaseDate time.Time
+
+// time durations
+const (
+	DayDur   = 24 * time.Hour
+	WeekDur  = 7 * DayDur
+	MonthDur = 30 * DayDur
+	YearDur  = 12 * MonthDur
+)
+
+func TestMain(m *testing.M) {
+	// setup
+	macaroni18n.I18n(macaroni18n.Options{
+		Directory:   "../../options/locale/",
+		DefaultLang: "en-US",
+		Langs:       []string{"en-US"},
+		Names:       []string{"english"},
+	})
+	BaseDate = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
+
+	// run the tests
+	retVal := m.Run()
+
+	os.Exit(retVal)
+}
+
+func TestTimeSince(t *testing.T) {
+	assert.Equal(t, "now", timeSince(BaseDate, BaseDate, "en"))
+
+	// test that each diff in `diffs` yields the expected string
+	test := func(expected string, diffs ...time.Duration) {
+		for _, diff := range diffs {
+			actual := timeSince(BaseDate, BaseDate.Add(diff), "en")
+			assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual)
+			actual = timeSince(BaseDate.Add(diff), BaseDate, "en")
+			assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual)
+		}
+	}
+	test("1 second", time.Second, time.Second+50*time.Millisecond)
+	test("2 seconds", 2*time.Second, 2*time.Second+50*time.Millisecond)
+	test("1 minute", time.Minute, time.Minute+30*time.Second)
+	test("2 minutes", 2*time.Minute, 2*time.Minute+30*time.Second)
+	test("1 hour", time.Hour, time.Hour+30*time.Minute)
+	test("2 hours", 2*time.Hour, 2*time.Hour+30*time.Minute)
+	test("1 day", DayDur, DayDur+12*time.Hour)
+	test("2 days", 2*DayDur, 2*DayDur+12*time.Hour)
+	test("1 week", WeekDur, WeekDur+3*DayDur)
+	test("2 weeks", 2*WeekDur, 2*WeekDur+3*DayDur)
+	test("1 month", MonthDur, MonthDur+15*DayDur)
+	test("2 months", 2*MonthDur, 2*MonthDur+15*DayDur)
+	test("1 year", YearDur, YearDur+6*MonthDur)
+	test("2 years", 2*YearDur, 2*YearDur+6*MonthDur)
+}
+
+func TestTimeSincePro(t *testing.T) {
+	assert.Equal(t, "now", timeSincePro(BaseDate, BaseDate, "en"))
+
+	// test that a difference of `diff` yields the expected string
+	test := func(expected string, diff time.Duration) {
+		actual := timeSincePro(BaseDate, BaseDate.Add(diff), "en")
+		assert.Equal(t, expected, actual)
+		assert.Equal(t, "future", timeSincePro(BaseDate.Add(diff), BaseDate, "en"))
+	}
+	test("1 second", time.Second)
+	test("2 seconds", 2*time.Second)
+	test("1 minute", time.Minute)
+	test("1 minute, 1 second", time.Minute+time.Second)
+	test("1 minute, 59 seconds", time.Minute+59*time.Second)
+	test("2 minutes", 2*time.Minute)
+	test("1 hour", time.Hour)
+	test("1 hour, 1 second", time.Hour+time.Second)
+	test("1 hour, 59 minutes, 59 seconds", time.Hour+59*time.Minute+59*time.Second)
+	test("2 hours", 2*time.Hour)
+	test("1 day", DayDur)
+	test("1 day, 23 hours, 59 minutes, 59 seconds",
+		DayDur+23*time.Hour+59*time.Minute+59*time.Second)
+	test("2 days", 2*DayDur)
+	test("1 week", WeekDur)
+	test("2 weeks", 2*WeekDur)
+	test("1 month", MonthDur)
+	test("3 months", 3*MonthDur)
+	test("1 year", YearDur)
+	test("2 years, 3 months, 1 week, 2 days, 4 hours, 12 minutes, 17 seconds",
+		2*YearDur+3*MonthDur+WeekDur+2*DayDur+4*time.Hour+
+			12*time.Minute+17*time.Second)
+}
+
+func TestHtmlTimeSince(t *testing.T) {
+	setting.TimeFormat = time.UnixDate
+	setting.DefaultUILocation = time.UTC
+	// test that `diff` yields a result containing `expected`
+	test := func(expected string, diff time.Duration) {
+		actual := htmlTimeSince(BaseDate, BaseDate.Add(diff), "en")
+		assert.Contains(t, actual, `title="Sat Jan  1 00:00:00 UTC 2000"`)
+		assert.Contains(t, actual, expected)
+	}
+	test("1 second", time.Second)
+	test("3 minutes", 3*time.Minute+5*time.Second)
+	test("1 day", DayDur+18*time.Hour)
+	test("1 week", WeekDur+6*DayDur)
+	test("3 months", 3*MonthDur+3*WeekDur)
+	test("2 years", 2*YearDur)
+	test("3 years", 3*YearDur+11*MonthDur+4*WeekDur)
+}
+
+func TestComputeTimeDiff(t *testing.T) {
+	// test that for each offset in offsets,
+	// computeTimeDiff(base + offset) == (offset, str)
+	test := func(base int64, str string, offsets ...int64) {
+		for _, offset := range offsets {
+			diff, diffStr := computeTimeDiff(base+offset, "en")
+			assert.Equal(t, offset, diff)
+			assert.Equal(t, str, diffStr)
+		}
+	}
+	test(0, "now", 0)
+	test(1, "1 second", 0)
+	test(2, "2 seconds", 0)
+	test(Minute, "1 minute", 0, 1, 30, Minute-1)
+	test(2*Minute, "2 minutes", 0, Minute-1)
+	test(Hour, "1 hour", 0, 1, Hour-1)
+	test(5*Hour, "5 hours", 0, Hour-1)
+	test(Day, "1 day", 0, 1, Day-1)
+	test(5*Day, "5 days", 0, Day-1)
+	test(Week, "1 week", 0, 1, Week-1)
+	test(3*Week, "3 weeks", 0, 4*Day+25000)
+	test(Month, "1 month", 0, 1, Month-1)
+	test(10*Month, "10 months", 0, Month-1)
+	test(Year, "1 year", 0, Year-1)
+	test(3*Year, "3 years", 0, Year-1)
+}
+
+func TestMinutesToFriendly(t *testing.T) {
+	// test that a number of minutes yields the expected string
+	test := func(expected string, minutes int) {
+		actual := MinutesToFriendly(minutes, "en")
+		assert.Equal(t, expected, actual)
+	}
+	test("1 minute", 1)
+	test("2 minutes", 2)
+	test("1 hour", 60)
+	test("1 hour, 1 minute", 61)
+	test("1 hour, 2 minutes", 62)
+	test("2 hours", 120)
+}
diff --git a/modules/util/time_stamp.go b/modules/timeutil/timestamp.go
similarity index 60%
rename from modules/util/time_stamp.go
rename to modules/timeutil/timestamp.go
index 56f41882f5..f70da9db74 100644
--- a/modules/util/time_stamp.go
+++ b/modules/timeutil/timestamp.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
-package util
+package timeutil
 
 import (
 	"time"
@@ -35,19 +35,34 @@ func (ts TimeStamp) Year() int {
 
 // AsTime convert timestamp as time.Time in Local locale
 func (ts TimeStamp) AsTime() (tm time.Time) {
-	tm = time.Unix(int64(ts), 0).In(setting.UILocation)
+	return ts.AsTimeInLocation(setting.DefaultUILocation)
+}
+
+// AsTimeInLocation convert timestamp as time.Time in Local locale
+func (ts TimeStamp) AsTimeInLocation(loc *time.Location) (tm time.Time) {
+	tm = time.Unix(int64(ts), 0).In(loc)
 	return
 }
 
 // AsTimePtr convert timestamp as *time.Time in Local locale
 func (ts TimeStamp) AsTimePtr() *time.Time {
-	tm := time.Unix(int64(ts), 0).In(setting.UILocation)
+	return ts.AsTimePtrInLocation(setting.DefaultUILocation)
+}
+
+// AsTimePtrInLocation convert timestamp as *time.Time in customize location
+func (ts TimeStamp) AsTimePtrInLocation(loc *time.Location) *time.Time {
+	tm := time.Unix(int64(ts), 0).In(loc)
 	return &tm
 }
 
-// Format formats timestamp as
+// Format formats timestamp as given format
 func (ts TimeStamp) Format(f string) string {
-	return ts.AsTime().Format(f)
+	return ts.FormatInLocation(f, setting.DefaultUILocation)
+}
+
+// FormatInLocation formats timestamp as given format with spiecific location
+func (ts TimeStamp) FormatInLocation(f string, loc *time.Location) string {
+	return ts.AsTimeInLocation(loc).Format(f)
 }
 
 // FormatLong formats as RFC1123Z
@@ -62,5 +77,5 @@ func (ts TimeStamp) FormatShort() string {
 
 // IsZero is zero time
 func (ts TimeStamp) IsZero() bool {
-	return ts.AsTime().IsZero()
+	return ts.AsTimeInLocation(time.Local).IsZero()
 }
diff --git a/public/js/index.js b/public/js/index.js
index 90412756f1..15f8d02bbd 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -1959,10 +1959,6 @@ $(document).ready(function () {
 
     // Show exact time
     $('.time-since').each(function () {
-        const time = new Date($(this).attr('title'))
-        if (!isNaN(time)){
-            $(this).attr('title', time.toLocaleString())
-        }
         $(this).addClass('poping up').attr('data-content', $(this).attr('title')).attr('data-variation', 'inverted tiny').attr('title', '');
     });
 
diff --git a/routers/admin/admin.go b/routers/admin/admin.go
index 370f816525..8339c1eaec 100644
--- a/routers/admin/admin.go
+++ b/routers/admin/admin.go
@@ -24,6 +24,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/process"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 const (
@@ -78,7 +79,7 @@ var sysStatus struct {
 }
 
 func updateSystemStatus() {
-	sysStatus.Uptime = base.TimeSincePro(startTime, "en")
+	sysStatus.Uptime = timeutil.TimeSincePro(startTime, "en")
 
 	m := new(runtime.MemStats)
 	runtime.ReadMemStats(m)
diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go
index daaa3d5985..8595be335b 100644
--- a/routers/api/v1/repo/issue.go
+++ b/routers/api/v1/repo/issue.go
@@ -16,6 +16,7 @@ import (
 	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
 	"code.gitea.io/gitea/modules/notification"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 
 	api "code.gitea.io/gitea/modules/structs"
@@ -183,9 +184,9 @@ func CreateIssue(ctx *context.APIContext, form api.CreateIssueOption) {
 	//   "201":
 	//     "$ref": "#/responses/Issue"
 
-	var deadlineUnix util.TimeStamp
+	var deadlineUnix timeutil.TimeStamp
 	if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) {
-		deadlineUnix = util.TimeStamp(form.Deadline.Unix())
+		deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
 	}
 
 	issue := &models.Issue{
@@ -310,9 +311,9 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
 	}
 
 	// Update the deadline
-	var deadlineUnix util.TimeStamp
+	var deadlineUnix timeutil.TimeStamp
 	if form.Deadline != nil && !form.Deadline.IsZero() && ctx.Repo.CanWrite(models.UnitTypeIssues) {
-		deadlineUnix = util.TimeStamp(form.Deadline.Unix())
+		deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
 	}
 
 	if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
@@ -430,12 +431,12 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
 		return
 	}
 
-	var deadlineUnix util.TimeStamp
+	var deadlineUnix timeutil.TimeStamp
 	var deadline time.Time
 	if form.Deadline != nil && !form.Deadline.IsZero() {
 		deadline = time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
 			23, 59, 59, 0, form.Deadline.Location())
-		deadlineUnix = util.TimeStamp(deadline.Unix())
+		deadlineUnix = timeutil.TimeStamp(deadline.Unix())
 	}
 
 	if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
diff --git a/routers/api/v1/repo/milestone.go b/routers/api/v1/repo/milestone.go
index 14030a2186..9bc73852ca 100644
--- a/routers/api/v1/repo/milestone.go
+++ b/routers/api/v1/repo/milestone.go
@@ -9,9 +9,8 @@ import (
 
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/context"
-	"code.gitea.io/gitea/modules/util"
-
 	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // ListMilestones list milestones for a repository
@@ -127,7 +126,7 @@ func CreateMilestone(ctx *context.APIContext, form api.CreateMilestoneOption) {
 		RepoID:       ctx.Repo.Repository.ID,
 		Name:         form.Title,
 		Content:      form.Description,
-		DeadlineUnix: util.TimeStamp(form.Deadline.Unix()),
+		DeadlineUnix: timeutil.TimeStamp(form.Deadline.Unix()),
 	}
 
 	if err := models.NewMilestone(milestone); err != nil {
@@ -187,7 +186,7 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) {
 		milestone.Content = *form.Description
 	}
 	if form.Deadline != nil && !form.Deadline.IsZero() {
-		milestone.DeadlineUnix = util.TimeStamp(form.Deadline.Unix())
+		milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
 	}
 
 	if err := models.UpdateMilestone(milestone); err != nil {
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index d99c9a00c9..8168c6b010 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -17,7 +17,7 @@ import (
 	"code.gitea.io/gitea/modules/notification"
 	"code.gitea.io/gitea/modules/pull"
 	api "code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // ListPullRequests returns a list of all PRs
@@ -247,9 +247,9 @@ func CreatePullRequest(ctx *context.APIContext, form api.CreatePullRequestOption
 		return
 	}
 
-	var deadlineUnix util.TimeStamp
+	var deadlineUnix timeutil.TimeStamp
 	if form.Deadline != nil {
-		deadlineUnix = util.TimeStamp(form.Deadline.Unix())
+		deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
 	}
 
 	maxIndex, err := models.GetMaxIndexOfIssue(repo.ID)
@@ -375,9 +375,9 @@ func EditPullRequest(ctx *context.APIContext, form api.EditPullRequestOption) {
 	}
 
 	// Update Deadline
-	var deadlineUnix util.TimeStamp
+	var deadlineUnix timeutil.TimeStamp
 	if form.Deadline != nil && !form.Deadline.IsZero() {
-		deadlineUnix = util.TimeStamp(form.Deadline.Unix())
+		deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
 	}
 
 	if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
diff --git a/routers/dev/template.go b/routers/dev/template.go
index ad0d2c1cd3..92599a7423 100644
--- a/routers/dev/template.go
+++ b/routers/dev/template.go
@@ -9,6 +9,7 @@ import (
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // TemplatePreview render for previewing the indicated template
@@ -18,8 +19,8 @@ func TemplatePreview(ctx *context.Context) {
 	ctx.Data["AppVer"] = setting.AppVer
 	ctx.Data["AppUrl"] = setting.AppURL
 	ctx.Data["Code"] = "2014031910370000009fff6782aadb2162b4a997acb69d4400888e0b9274657374"
-	ctx.Data["ActiveCodeLives"] = base.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
-	ctx.Data["ResetPwdCodeLives"] = base.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language())
+	ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
+	ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language())
 	ctx.Data["CurDbValue"] = ""
 
 	ctx.HTML(200, base.TplName(ctx.Params("*")))
diff --git a/routers/private/key.go b/routers/private/key.go
index f7212ec892..8c1dbd40f1 100644
--- a/routers/private/key.go
+++ b/routers/private/key.go
@@ -7,7 +7,7 @@ package private
 
 import (
 	"code.gitea.io/gitea/models"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	macaron "gopkg.in/macaron.v1"
 )
@@ -34,7 +34,7 @@ func UpdatePublicKeyInRepo(ctx *macaron.Context) {
 		})
 		return
 	}
-	deployKey.UpdatedUnix = util.TimeStampNow()
+	deployKey.UpdatedUnix = timeutil.TimeStampNow()
 	if err = models.UpdateDeployKeyCols(deployKey, "updated_unix"); err != nil {
 		ctx.JSON(500, map[string]interface{}{
 			"err": err.Error(),
diff --git a/routers/repo/blame.go b/routers/repo/blame.go
index 07952a4cae..5578942a9f 100644
--- a/routers/repo/blame.go
+++ b/routers/repo/blame.go
@@ -14,7 +14,6 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models"
-
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/git"
@@ -22,7 +21,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 const (
@@ -212,7 +211,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
 			if index == 0 {
 				// User avatar image
 				avatar := ""
-				commitSince := base.TimeSinceUnix(util.TimeStamp(commit.Author.When.Unix()), ctx.Data["Lang"].(string))
+				commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Data["Lang"].(string))
 				if commit.User != nil {
 					authorName := commit.Author.Name
 					if len(commit.User.FullName) > 0 {
diff --git a/routers/repo/http.go b/routers/repo/http.go
index c0bf830969..09dd820585 100644
--- a/routers/repo/http.go
+++ b/routers/repo/http.go
@@ -25,7 +25,7 @@ import (
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 // HTTP implmentation git smart HTTP protocol
@@ -203,7 +203,7 @@ func HTTP(ctx *context.Context) {
 						return
 					}
 				}
-				token.UpdatedUnix = util.TimeStampNow()
+				token.UpdatedUnix = timeutil.TimeStampNow()
 				if err = models.UpdateAccessToken(token); err != nil {
 					ctx.ServerError("UpdateAccessToken", err)
 				}
diff --git a/routers/repo/milestone.go b/routers/repo/milestone.go
index 5b8a5faa33..b4056cc6d1 100644
--- a/routers/repo/milestone.go
+++ b/routers/repo/milestone.go
@@ -13,6 +13,7 @@ import (
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 )
 
@@ -120,7 +121,7 @@ func NewMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
 		RepoID:       ctx.Repo.Repository.ID,
 		Name:         form.Title,
 		Content:      form.Content,
-		DeadlineUnix: util.TimeStamp(deadline.Unix()),
+		DeadlineUnix: timeutil.TimeStamp(deadline.Unix()),
 	}); err != nil {
 		ctx.ServerError("NewMilestone", err)
 		return
@@ -190,7 +191,7 @@ func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
 	}
 	m.Name = form.Title
 	m.Content = form.Content
-	m.DeadlineUnix = util.TimeStamp(deadline.Unix())
+	m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix())
 	if err = models.UpdateMilestone(m); err != nil {
 		ctx.ServerError("UpdateMilestone", err)
 		return
@@ -223,7 +224,7 @@ func ChangeMilestonStatus(ctx *context.Context) {
 		ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=open")
 	case "close":
 		if !m.IsClosed {
-			m.ClosedDateUnix = util.TimeStampNow()
+			m.ClosedDateUnix = timeutil.TimeStampNow()
 			if err = models.ChangeMilestoneStatus(m, true); err != nil {
 				ctx.ServerError("ChangeMilestoneStatus", err)
 				return
diff --git a/routers/repo/setting.go b/routers/repo/setting.go
index 757295069e..c9ecb63020 100644
--- a/routers/repo/setting.go
+++ b/routers/repo/setting.go
@@ -14,9 +14,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/Unknwon/com"
-	"mvdan.cc/xurls/v2"
-
 	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/auth"
 	"code.gitea.io/gitea/modules/base"
@@ -24,9 +21,12 @@ import (
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/validation"
 	"code.gitea.io/gitea/routers/utils"
+
+	"github.com/Unknwon/com"
+	"mvdan.cc/xurls/v2"
 )
 
 const (
@@ -144,7 +144,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
 			ctx.Repo.Mirror.EnablePrune = form.EnablePrune
 			ctx.Repo.Mirror.Interval = interval
 			if interval != 0 {
-				ctx.Repo.Mirror.NextUpdateUnix = util.TimeStampNow().AddDuration(interval)
+				ctx.Repo.Mirror.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(interval)
 			} else {
 				ctx.Repo.Mirror.NextUpdateUnix = 0
 			}
diff --git a/routers/repo/wiki.go b/routers/repo/wiki.go
index 0d965f2183..54cb4b83dd 100644
--- a/routers/repo/wiki.go
+++ b/routers/repo/wiki.go
@@ -19,6 +19,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/markdown"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 )
 
@@ -58,7 +59,7 @@ func MustEnableWiki(ctx *context.Context) {
 type PageMeta struct {
 	Name        string
 	SubURL      string
-	UpdatedUnix util.TimeStamp
+	UpdatedUnix timeutil.TimeStamp
 }
 
 // findEntryForFile finds the tree entry for a target filepath.
@@ -413,7 +414,7 @@ func WikiPages(ctx *context.Context) {
 		pages = append(pages, PageMeta{
 			Name:        wikiName,
 			SubURL:      models.WikiNameToSubURL(wikiName),
-			UpdatedUnix: util.TimeStamp(c.Author.When.Unix()),
+			UpdatedUnix: timeutil.TimeStamp(c.Author.When.Unix()),
 		})
 	}
 	ctx.Data["Pages"] = pages
diff --git a/routers/user/auth.go b/routers/user/auth.go
index f78171a1b7..6850bbe11b 100644
--- a/routers/user/auth.go
+++ b/routers/user/auth.go
@@ -19,6 +19,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/recaptcha"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/go-macaron/captcha"
@@ -950,7 +951,7 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
 		models.SendActivateAccountMail(ctx.Context, u)
 		ctx.Data["IsSendRegisterMail"] = true
 		ctx.Data["Email"] = u.Email
-		ctx.Data["ActiveCodeLives"] = base.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
+		ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
 		ctx.HTML(200, TplActivate)
 
 		if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
@@ -1096,7 +1097,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
 		models.SendActivateAccountMail(ctx.Context, u)
 		ctx.Data["IsSendRegisterMail"] = true
 		ctx.Data["Email"] = u.Email
-		ctx.Data["ActiveCodeLives"] = base.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
+		ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
 		ctx.HTML(200, TplActivate)
 
 		if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
@@ -1123,7 +1124,7 @@ func Activate(ctx *context.Context) {
 			if ctx.Cache.IsExist("MailResendLimit_" + ctx.User.LowerName) {
 				ctx.Data["ResendLimited"] = true
 			} else {
-				ctx.Data["ActiveCodeLives"] = base.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
+				ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
 				models.SendActivateAccountMail(ctx.Context, ctx.User)
 
 				if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
@@ -1224,7 +1225,7 @@ func ForgotPasswdPost(ctx *context.Context) {
 	u, err := models.GetUserByEmail(email)
 	if err != nil {
 		if models.IsErrUserNotExist(err) {
-			ctx.Data["ResetPwdCodeLives"] = base.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language())
+			ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language())
 			ctx.Data["IsResetSent"] = true
 			ctx.HTML(200, tplForgotPassword)
 			return
@@ -1251,7 +1252,7 @@ func ForgotPasswdPost(ctx *context.Context) {
 		log.Error("Set cache(MailResendLimit) fail: %v", err)
 	}
 
-	ctx.Data["ResetPwdCodeLives"] = base.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language())
+	ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language())
 	ctx.Data["IsResetSent"] = true
 	ctx.HTML(200, tplForgotPassword)
 }
diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go
index d6baf0d92b..48dbf02ffb 100644
--- a/routers/user/auth_openid.go
+++ b/routers/user/auth_openid.go
@@ -17,6 +17,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/recaptcha"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/go-macaron/captcha"
 )
@@ -446,7 +447,7 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si
 		models.SendActivateAccountMail(ctx.Context, u)
 		ctx.Data["IsSendRegisterMail"] = true
 		ctx.Data["Email"] = u.Email
-		ctx.Data["ActiveCodeLives"] = base.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
+		ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())
 		ctx.HTML(200, TplActivate)
 
 		if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
diff --git a/routers/user/oauth.go b/routers/user/oauth.go
index 924bac8ef6..19add5e647 100644
--- a/routers/user/oauth.go
+++ b/routers/user/oauth.go
@@ -7,7 +7,6 @@ package user
 import (
 	"encoding/base64"
 	"fmt"
-	"github.com/go-macaron/binding"
 	"net/url"
 	"strings"
 
@@ -17,9 +16,10 @@ import (
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/timeutil"
 
 	"github.com/dgrijalva/jwt-go"
+	"github.com/go-macaron/binding"
 )
 
 const (
@@ -118,7 +118,7 @@ func newAccessTokenResponse(grant *models.OAuth2Grant) (*AccessTokenResponse, *A
 		}
 	}
 	// generate access token to access the API
-	expirationDate := util.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime)
+	expirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.AccessTokenExpirationTime)
 	accessToken := &models.OAuth2Token{
 		GrantID: grant.ID,
 		Type:    models.TypeAccessToken,
@@ -135,7 +135,7 @@ func newAccessTokenResponse(grant *models.OAuth2Grant) (*AccessTokenResponse, *A
 	}
 
 	// generate refresh token to request an access token after it expired later
-	refreshExpirationDate := util.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime().Unix()
+	refreshExpirationDate := timeutil.TimeStampNow().Add(setting.OAuth2.RefreshTokenExpirationTime * 60 * 60).AsTime().Unix()
 	refreshToken := &models.OAuth2Token{
 		GrantID: grant.ID,
 		Counter: grant.Counter,
diff --git a/routers/user/setting/account.go b/routers/user/setting/account.go
index 3fd6a8db5e..c6b5c0c2dc 100644
--- a/routers/user/setting/account.go
+++ b/routers/user/setting/account.go
@@ -12,6 +12,7 @@ import (
 	"code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/timeutil"
 )
 
 const (
@@ -112,7 +113,7 @@ func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
 		if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
 			log.Error("Set cache(MailResendLimit) fail: %v", err)
 		}
-		ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, base.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())))
+		ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language())))
 	} else {
 		ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
 	}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index 4ae7f5a49e..2d5f8c5c9c 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -9770,7 +9770,7 @@
       "description": "TimeStamp defines a timestamp",
       "type": "integer",
       "format": "int64",
-      "x-go-package": "code.gitea.io/gitea/modules/util"
+      "x-go-package": "code.gitea.io/gitea/modules/timeutil"
     },
     "TrackedTime": {
       "description": "TrackedTime worked time for an issue / pr",