diff --git a/protocols/utils.proto b/protocols/utils.proto index e735d731..4a96debe 100644 --- a/protocols/utils.proto +++ b/protocols/utils.proto @@ -4,12 +4,15 @@ option go_package = "github.com/movsb/taoblog/proto"; message FormatTimeRequest { repeated int32 unix = 1; + // 设备时区。 + string device = 2; } message FormatTimeResponse { message Formatted { string friendly = 1; - string rfc3339 = 2; + string server = 3; + string device = 4; } repeated Formatted formatted = 1; } diff --git a/service/utils.go b/service/utils.go index 74d39b8f..6638022d 100644 --- a/service/utils.go +++ b/service/utils.go @@ -7,14 +7,17 @@ import ( "sync/atomic" "time" + _ "time/tzdata" + "github.com/movsb/taoblog/modules/auth" "github.com/movsb/taoblog/modules/dialers" "github.com/movsb/taoblog/modules/notify" "github.com/movsb/taoblog/protocols/go/proto" + "github.com/phuslu/lru" "github.com/xeonx/timeago" ) -var fixedZone = time.FixedZone(``, 8*60*60) +var fixedZone = time.Now().Local().Location() type Utils struct { proto.UnimplementedUtilsServer @@ -22,11 +25,14 @@ type Utils struct { instantNotifier notify.InstantNotifier RemoteDialer atomic.Pointer[dialers.RemoteDialerManager] + + timeLocations *lru.TTLCache[string, *time.Location] } func NewUtils(instantNotifier notify.InstantNotifier) *Utils { u := &Utils{ instantNotifier: instantNotifier, + timeLocations: lru.NewTTLCache[string, *time.Location](16), } u.RemoteDialer.Store(nil) return u @@ -34,11 +40,25 @@ func NewUtils(instantNotifier notify.InstantNotifier) *Utils { func (u *Utils) FormatTime(ctx context.Context, in *proto.FormatTimeRequest) (*proto.FormatTimeResponse, error) { formatted := make([]*proto.FormatTimeResponse_Formatted, len(in.Unix)) - for i, u := range in.Unix { + for i, ts := range in.Unix { r := proto.FormatTimeResponse_Formatted{} - t := time.Unix(int64(u), 0).In(fixedZone) + t := time.Unix(int64(ts), 0) r.Friendly = timeago.Chinese.Format(t) - r.Rfc3339 = t.Format(time.RFC3339) + r.Server = t.In(fixedZone).Format(time.RFC3339) + + if in.Device != `` { + loc, err, _ := u.timeLocations.GetOrLoad(ctx, in.Device, func(ctx context.Context, s string) (*time.Location, time.Duration, error) { + loc, err := time.LoadLocation(s) + // log.Println(`加载时区:`, s, loc, err) + return loc, time.Hour, err + }) + if err != nil { + log.Println(err) + } else { + // log.Println(`时区:`, loc) + r.Device = t.In(loc).Format(time.RFC3339) + } + } formatted[i] = &r } return &proto.FormatTimeResponse{ diff --git a/theme/blog/statics/scripts/comment.js b/theme/blog/statics/scripts/comment.js index 07ed046a..e15508b8 100644 --- a/theme/blog/statics/scripts/comment.js +++ b/theme/blog/statics/scripts/comment.js @@ -54,50 +54,6 @@ document.write(function(){/* */}.toString().slice(14,-3)); -class TimeWithZone { - constructor(timestamp, zone) { - const now = new Date(); - if (typeof timestamp != 'number') { - timestamp = now.getTime() / 1000; - zone = TimeWithZone.getTimezone(); - } else if (typeof zone != 'string' || zone == '') { - zone = TimeWithZone.getTimezone(); - } - this._timestamp = timestamp; - this._zone = zone; - } - - get time() { return this._timestamp; } - get zone() { return this._zone; } - - format() { - const options = { - timeZone: this._zone, - timeZoneName: 'longOffset', - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - hourCycle: 'h24', - } - return new Intl.DateTimeFormat(navigator.language, options).format(new Date(this._timestamp)); - } - - toJSON() { - return new Date(this._timestamp).toJSON(); - } - - static getTimezone() { - try { - return Intl.DateTimeFormat().resolvedOptions().timeZone; - } catch { - return ''; - } - } -} - class CommentAPI { constructor(postID) { @@ -734,12 +690,9 @@ class Comment { let info = ''; if (loggedin) { info = `编号:${cmt.id} -作者:${cmt.author} 邮箱:${cmt.email} -网址:${cmt.url} 地址:${cmt.ip} 位置:${cmt.geo_location} -日期:${date.format()} `; } @@ -767,7 +720,7 @@ class Comment {
${h2t(cmt.author)} ${urlContent} - +
${cmt.source_type === 'markdown' ? `
${cmt.content}
` diff --git a/theme/blog/statics/scripts/footer.js b/theme/blog/statics/scripts/footer.js index d1425c07..1f0e2256 100644 --- a/theme/blog/statics/scripts/footer.js +++ b/theme/blog/statics/scripts/footer.js @@ -242,11 +242,13 @@ function all() { async function format(stamps) { let path = '/v3/utils/time/format'; let formatted = undefined; + const timezone = TimeWithZone.getTimezone(); try { let rsp = await fetch(path, { method: 'POST', body: JSON.stringify({ unix: stamps, + device: timezone, }), }); if (!rsp.ok) { @@ -265,7 +267,14 @@ let update = async function() { let formatted = await format(stamps); if (!formatted) { return; } times.forEach((t, i) => { - t.innerText = formatted[i].friendly; + const f = formatted[i]; + t.innerText = f.friendly; + let title = f.server; + if (f.device && f.device != f.server) { + title = `${title}\n${f.device}`; + } + t.title = title; + // console.log(title); }); let current = Math.floor(new Date().getTime()/1000); let diff = current - latest; diff --git a/theme/blog/statics/scripts/header.js b/theme/blog/statics/scripts/header.js index cdff267f..5234f6fe 100644 --- a/theme/blog/statics/scripts/header.js +++ b/theme/blog/statics/scripts/header.js @@ -99,3 +99,32 @@ if (TaoBlog.userID > 0) { document.body.classList.add('signed-in'); }); } + +class TimeWithZone { + constructor(timestamp, zone) { + const now = new Date(); + if (typeof timestamp != 'number') { + timestamp = now.getTime() / 1000; + zone = TimeWithZone.getTimezone(); + } else if (typeof zone != 'string' || zone == '') { + zone = TimeWithZone.getTimezone(); + } + this._timestamp = timestamp; + this._zone = zone; + } + + get time() { return this._timestamp; } + get zone() { return this._zone; } + + toJSON() { + return new Date(this._timestamp).toJSON(); + } + + static getTimezone() { + try { + return Intl.DateTimeFormat().resolvedOptions().timeZone; + } catch { + return ''; + } + } +}