mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 06:21:11 +00:00 
			
		
		
		
	- Massive replacement of changing `code.gitea.io/gitea` to `forgejo.org`. - Resolves forgejo/discussions#258 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7337 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Reviewed-by: Michael Kriese <michael.kriese@gmx.de> Reviewed-by: Beowulf <beowulf@beocode.eu> Reviewed-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
		
			
				
	
	
		
			151 lines
		
	
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			151 lines
		
	
	
	
		
			4.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2024 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package templates
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"html"
 | |
| 	"html/template"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"forgejo.org/modules/setting"
 | |
| 	"forgejo.org/modules/timeutil"
 | |
| 	"forgejo.org/modules/translation"
 | |
| )
 | |
| 
 | |
| type DateUtils struct{}
 | |
| 
 | |
| func NewDateUtils() *DateUtils {
 | |
| 	return (*DateUtils)(nil) // the util is stateless, and we do not need to create an instance
 | |
| }
 | |
| 
 | |
| // AbsoluteShort renders in "Jan 01, 2006" format
 | |
| func (du *DateUtils) AbsoluteShort(time any) template.HTML {
 | |
| 	return dateTimeFormat("short", time)
 | |
| }
 | |
| 
 | |
| // AbsoluteLong renders in "January 01, 2006" format
 | |
| func (du *DateUtils) AbsoluteLong(time any) template.HTML {
 | |
| 	return dateTimeFormat("long", time)
 | |
| }
 | |
| 
 | |
| // FullTime renders in "Jan 01, 2006 20:33:44" format
 | |
| func (du *DateUtils) FullTime(time any) template.HTML {
 | |
| 	return dateTimeFormat("full", time)
 | |
| }
 | |
| 
 | |
| func (du *DateUtils) TimeSince(time any) template.HTML {
 | |
| 	return TimeSince(time)
 | |
| }
 | |
| 
 | |
| // ParseLegacy parses the datetime in legacy format, eg: "2016-01-02" in server's timezone.
 | |
| // It shouldn't be used in new code. New code should use Time or TimeStamp as much as possible.
 | |
| func (du *DateUtils) ParseLegacy(datetime string) time.Time {
 | |
| 	return parseLegacy(datetime)
 | |
| }
 | |
| 
 | |
| func parseLegacy(datetime string) time.Time {
 | |
| 	t, err := time.Parse(time.RFC3339, datetime)
 | |
| 	if err != nil {
 | |
| 		t, _ = time.ParseInLocation(time.DateOnly, datetime, setting.DefaultUILocation)
 | |
| 	}
 | |
| 	return t
 | |
| }
 | |
| 
 | |
| func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
 | |
| 	if !setting.IsProd || setting.IsInTesting {
 | |
| 		panic("dateTimeLegacy is for backward compatibility only, do not use it in new code")
 | |
| 	}
 | |
| 	if s, ok := datetime.(string); ok {
 | |
| 		datetime = parseLegacy(s)
 | |
| 	}
 | |
| 	return dateTimeFormat(format, datetime)
 | |
| }
 | |
| 
 | |
| func timeSinceLegacy(time any, _ translation.Locale) template.HTML {
 | |
| 	if !setting.IsProd || setting.IsInTesting {
 | |
| 		panic("timeSinceLegacy is for backward compatibility only, do not use it in new code")
 | |
| 	}
 | |
| 	return TimeSince(time)
 | |
| }
 | |
| 
 | |
| func anyToTime(any any) (t time.Time, isZero bool) {
 | |
| 	switch v := any.(type) {
 | |
| 	case nil:
 | |
| 		// it is zero
 | |
| 	case *time.Time:
 | |
| 		if v != nil {
 | |
| 			t = *v
 | |
| 		}
 | |
| 	case time.Time:
 | |
| 		t = v
 | |
| 	case timeutil.TimeStamp:
 | |
| 		t = v.AsTime()
 | |
| 	case timeutil.TimeStampNano:
 | |
| 		t = v.AsTime()
 | |
| 	case int:
 | |
| 		t = timeutil.TimeStamp(v).AsTime()
 | |
| 	case int64:
 | |
| 		t = timeutil.TimeStamp(v).AsTime()
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("Unsupported time type %T", any))
 | |
| 	}
 | |
| 	return t, t.IsZero() || t.Unix() == 0
 | |
| }
 | |
| 
 | |
| func dateTimeFormat(format string, datetime any) template.HTML {
 | |
| 	t, isZero := anyToTime(datetime)
 | |
| 	if isZero {
 | |
| 		return "-"
 | |
| 	}
 | |
| 	var textEscaped string
 | |
| 	datetimeEscaped := html.EscapeString(t.Format(time.RFC3339))
 | |
| 	if format == "full" {
 | |
| 		textEscaped = html.EscapeString(t.Format("2006-01-02 15:04:05 -07:00"))
 | |
| 	} else {
 | |
| 		textEscaped = html.EscapeString(t.Format("2006-01-02"))
 | |
| 	}
 | |
| 
 | |
| 	attrs := []string{`weekday=""`, `year="numeric"`}
 | |
| 	switch format {
 | |
| 	case "short", "long": // date only
 | |
| 		attrs = append(attrs, `month="`+format+`"`, `day="numeric"`)
 | |
| 		return template.HTML(fmt.Sprintf(`<absolute-date %s date="%s">%s</absolute-date>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
 | |
| 	case "full": // full date including time
 | |
| 		attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`)
 | |
| 		return template.HTML(fmt.Sprintf(`<relative-time %s datetime="%s">%s</relative-time>`, strings.Join(attrs, " "), datetimeEscaped, textEscaped))
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("Unsupported format %s", format))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func timeSinceTo(then any, now time.Time) template.HTML {
 | |
| 	thenTime, isZero := anyToTime(then)
 | |
| 	if isZero {
 | |
| 		return "-"
 | |
| 	}
 | |
| 
 | |
| 	friendlyText := thenTime.Format("2006-01-02 15:04:05 -07:00")
 | |
| 
 | |
| 	// document: https://github.com/github/relative-time-element
 | |
| 	attrs := `tense="past"`
 | |
| 	isFuture := now.Before(thenTime)
 | |
| 	if isFuture {
 | |
| 		attrs = `tense="future"`
 | |
| 	}
 | |
| 
 | |
| 	// declare data-tooltip-content attribute to switch from "title" tooltip to "tippy" tooltip
 | |
| 	htm := fmt.Sprintf(`<relative-time prefix="" %s datetime="%s" data-tooltip-content data-tooltip-interactive="true">%s</relative-time>`,
 | |
| 		attrs, thenTime.Format(time.RFC3339), friendlyText)
 | |
| 	return template.HTML(htm)
 | |
| }
 | |
| 
 | |
| // TimeSince renders relative time HTML given a time
 | |
| func TimeSince(then any) template.HTML {
 | |
| 	if setting.UI.PreferredTimestampTense == "absolute" {
 | |
| 		return dateTimeFormat("full", then)
 | |
| 	}
 | |
| 	return timeSinceTo(then, time.Now())
 | |
| }
 |