Module datetime

This commit is contained in:
Benjamin VAUDOUR 2023-10-31 11:18:00 +01:00
parent 79b5b534f3
commit 0700fced7b
9 changed files with 1277 additions and 0 deletions

78
datetime/compare.go Normal file
View File

@ -0,0 +1,78 @@
package datetime
import (
"time"
"golang.org/x/tools/go/analysis/passes/bools"
)
// IsZero retourne vrai si la date représente le 01/01/0001 à 00:00 UTC.
func IsZero(t time.Time) bool {
return t.IsZero()
}
// Compare retourne :
// - 1 si t1 est dans le futur de t2,
// - 0 si t1 et t2 sont égaux,
// - -1 sinon.
func Compare(t1, t2 time.Time) int {
u1, u2 := t1.Unix(), t2.Unix()
switch {
case u1 == u2:
return 0
case u1 > u2:
return 1
default:
return -1
}
}
// Eq retourne vrai si t1 = t2.
func Eq(t1, t2 time.Time) bool { return Compare(t1, t2) == 0 }
// Ne retourne vrai si t1 ≠ t2.
func Ne(t1, t2 time.Time) bool { return Compare(t1, t2) != 0 }
// Gt retourne vrai si t1 > t2.
func Gt(t1, t2 time.Time) bool { return Compare(t1, t2) > 0 }
// Ge retourne vrai si t1 ≥ t2.
func Ge(t1, t2 time.Time) bool { return Compare(t1, t2) >= 0 }
// Lt retourne vrai si t1 < t2.
func Lt(t1, t2 time.Time) bool { return Compare(t1, t2) < 0 }
// Le retourne vrai si t1 ≤ t2.
func Le(t1, t2 time.Time) bool { return Compare(t1, t2) <= 0 }
// Min retourne la date la plus située dans le passé.
func Min(t time.Time, times ...time.Time) time.Time {
for _, tt := range times {
if Lt(tt, t) {
t = tt
}
}
return t
}
// Max retourne la date la plus située dans le futur.
func Max(t time.Time, times ...time.Time) time.Time {
for _, tt := range times {
if Gt(tt, t) {
t = tt
}
}
return t
}
// IsNow retourne vrai si la date est actuelle.
func IsNow(t time.Time) bool { return Eq(t, time.Now()) }
// IsPast retourne vrai si la date est située dans le passé.
func IsPast(t time.Time) bool { return Lt(t, time.Now()) }
// IsFuture retourne vrai si la date est située dans le future.
func IsFuture(t time.Time) bool { return Gt(t, time.Now()) }

257
datetime/const.go Normal file
View File

@ -0,0 +1,257 @@
package datetime
import (
"time"
)
var (
// Fuseau horaire par défaut
DefaultTZ = time.Local
// Jours de la semaine (format court)
shortDays = []string{
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
}
// Jours de la semaine (format long)
longDays = []string{
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
}
// Mois (format court)
shortMonths = []string{
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
}
// Mois (format long)
longMonths = []string{
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
}
formatters = map[rune]func(time.Time) string{
// Jour
'j': format_j, // Jour du mois sans les 0 initiaux (1-31)
'd': format_d, // Jour du mois, sur deux chiffres (avec un 0 initial) (01-31)
'N': format_N, // Représentation numérique ISO 8601 du jour de la semaine (1-7)
'w': format_w, // Jour de la semaine au format numérique (0-6)
'z': format_z, // Jour de lannée (0-365)
'D': format_D, // Jour de la semaine, en 3 lettres (Mon-Sun)
'l': format_l, // Jour de la semaine, textuel, version longue (Monday-Sunday)
// Semaine
'W': format_W, // N° de semaine dans lannée ISO 8601, les semaines commencent le lundi (0-53)
// Mois
'n': format_n, // Mois sans les 0 initiaux (1-12)
'm': format_m, // Mois au format numérique, avec 0 initiaux (01-12)
't': format_t, // Nombre de jours dans le mois (28-31)
'M': format_M, // Mois, en trois lettres (Jan-Dec)
'F': format_F, // Mois, textuel, version longue (January-December)
// Année
'y': format_y, // Année sur 2 chiffres (Exemples : 99 ou 03)
'Y': format_Y, // Année sur au moins 4 chiffres, avec - pour les années av. J.-C. (Exemples : -0055, 0787, 1999, 2003, 10191)
'L': format_L, // Est ce que lannée est bissextile (1 si bissextile, 0 sinon)
// Heure
'a': format_a, // Ante meridiem et Post meridiem en minuscules (am ou pm)
'A': format_A, // Ante meridiem et Post meridiem en majuscules (AM ou PM)
'B': format_B, // Heure Internet Swatch (000-999)
'g': format_g, // Heure, au format 12h, sans les 0 initiaux (1-12)
'G': format_G, // Heure, au format 24h, sans les 0 initiaux (0-23)
'h': format_h, // Heure, au format 12h, avec les 0 initiaux (01-12)
'H': format_H, // Heure, au format 24h, avec les 0 initiaux (00-23)
'i': format_i, // Minutes avec les 0 initiaux (00-59)
's': format_s, // Secondes avec les 0 initiaux (00-59)
'v': format_v, // Nillisecondes avec les 0 initiaux (000-999)
'u': format_u, // Microsecondes avec les 0 initiaux (000000-999999)
// Fuseau horaire
'T': format_T, // Abréviation du fuseau horaire, si connu; sinon décalage depuis GMT (Exemples : EST, MDT, +05)
'e': format_e, // Lidentifiant du fuseau horaire (Exemples : UTC, GMT, Atlantic/Azores)
'I': format_I, // Lheure dété est activée ou pas (1 si oui, 0 sinon)
'O': format_O, // Différence dheures avec lheure de Greenwich (GMT), sans deux-points entre les heures et les minutes (Exemple : +0200)
'P': format_P, // Différence dheures avec lheure de Greenwich (GMT), avec deux-points entre les heures et les minutes (Exemple : +02:00)
'p': format_p, // Identique à P, mais retourne Z au lieu de +00:00
'Z': format_Z, // Décalage horaire en secondes. Le décalage des zones à louest de la zone UTC est négatif, et à lest, il est positif.(-43200 à 50400)
// Date et heure complète
'c': format_c, // Date au format ISO 8601 (2004-02-12T15:19:21+00:00)
'r': format_r, // Date au format RFC 5322 (Thu, 21 Dec 2000 16:01:070200)
'U': format_U, // Secondes depuis lépoque Unix (1er Janvier 1970, 0h00 00s GMT)
}
parsers = map[rune]string{
// Jours
'j': "2", // Jour du mois sans les 0 initiaux (1-31)
'd': "02", // Jour du mois, sur deux chiffres (avec un 0 initial) (01-31)
'D': "Mon", // Jour de la semaine, en 3 lettres (Mon-Sun)
'l': "Monday", // Jour de la semaine, textuel, version longue (Monday-Sunday)
// Mois
'n': "1", // Mois sans les 0 initiaux (1-12)
'm': "01", // Mois au format numérique, avec 0 initiaux (01-12)
'M': "Jan", // Mois, en trois lettres (Jan-Dec)
'F': "January", // Mois, textuel, version longue (January-December)
// Année
'y': "06", // Année sur 2 chiffres (Exemples : 99 ou 03)
'Y': "2006", // Année sur au moins 4 chiffres, avec - pour les années av. J.-C. (Exemples : -0055, 0787, 1999, 2003, 10191)
// Heure
'a': "pm", // Ante meridiem et Post meridiem en minuscules (am ou pm)
'A': "PM", // Ante meridiem et Post meridiem en majuscules (AM ou PM)
'g': "3", // Heure, au format 12h, sans les 0 initiaux (1-12)
'h': "03", // Heure, au format 12h, avec les 0 initiaux (01-12)
'H': "15", // Heure, au format 24h, avec les 0 initiaux (00-23)
'i': "04", // Minutes avec les 0 initiaux (00-59)
's': "05", // Secondes avec les 0 initiaux (00-59)
// Fuseau horaire
'T': "MST", // Abréviation du fuseau horaire, si connu; sinon décalage depuis GMT (Exemples : EST, MDT, +05)
'O': "-0700", // Différence dheures avec lheure de Greenwich (GMT), sans deux-points entre les heures et les minutes (Exemple : +0200)
'P': "-07:00", // Différence dheures avec lheure de Greenwich (GMT), avec deux-points entre les heures et les minutes (Exemple : +02:00)
// Date et heure complète
'c': "2006-01-02T15:04:05-07:00", // Date au format ISO 8601 (2004-02-12T15:19:21+00:00)
'r': "Thu, 21 Dec 2000 16:01:07 +0200", // Date au format RFC 5322 (Thu, 21 Dec 2000 16:01:070200)
}
layouts = []string{
DayDateTimeLayout,
DateTimeLayout, DateTimeNanoLayout, ShortDateTimeLayout, ShortDateTimeNanoLayout,
DateLayout, DateNanoLayout, ShortDateLayout, ShortDateNanoLayout,
ISO8601Layout, ISO8601NanoLayout,
RFC822Layout, RFC822ZLayout, RFC850Layout, RFC1123Layout, RFC1123ZLayout, RFC3339Layout, RFC3339NanoLayout, RFC1036Layout, RFC7231Layout,
KitchenLayout,
CookieLayout,
ANSICLayout,
UnixDateLayout,
RubyDateLayout,
"2006",
"2006-1", "2006-1-2", "2006-1-2 15", "2006-1-2 15:4", "2006-1-2 15:4:5", "2006-1-2 15:4:5.999999999",
"2006.1", "2006.1.2", "2006.1.2 15", "2006.1.2 15:4", "2006.1.2 15:4:5", "2006.1.2 15:4:5.999999999",
"2006/1", "2006/1/2", "2006/1/2 15", "2006/1/2 15:4", "2006/1/2 15:4:5", "2006/1/2 15:4:5.999999999",
"2006-01-02 15:04:05PM MST", "2006-01-02 15:04:05.999999999PM MST", "2006-1-2 15:4:5PM MST", "2006-1-2 15:4:5.999999999PM MST",
"2006-01-02 15:04:05 PM MST", "2006-01-02 15:04:05.999999999 PM MST", "2006-1-2 15:4:5 PM MST", "2006-1-2 15:4:5.999999999 PM MST",
"1/2/2006", "1/2/2006 15", "1/2/2006 15:4", "1/2/2006 15:4:5", "1/2/2006 15:4:5.999999999",
"2006-1-2 15:4:5 -0700 MST", "2006-1-2 15:4:5.999999999 -0700 MST",
"2006-1-2T15:4:5Z07", "2006-1-2T15:4:5.999999999Z07",
"2006-1-2T15:4:5Z07:00", "2006-1-2T15:4:5.999999999Z07:00",
"2006-1-2T15:4:5-07:00", "2006-1-2T15:4:5.999999999-07:00",
"20060102150405-07:00", "20060102150405.999999999-07:00",
"20060102150405Z07", "20060102150405.999999999Z07",
"20060102150405Z07:00", "20060102150405.999999999Z07:00",
}
)
const (
// Unités de durée
Nanosecond = time.Nanosecond
Microsecond = time.Microsecond
Millisecond = time.Millisecond
Second = time.Second
Minute = time.Minute
Hour = time.Hour
Day = time.Hour * 24
Week = Day * 7
MonthsPerYear = 12
DaysPerWeek = 7
HoursPerDay = 24
HoursPerWeek = HoursPerDay * DaysPerWeek
MinutesPerHour = 60
MinutesPerDay = MinutesPerHour * HoursPerDay
MinutesPerWeek = MinutesPerHour * HoursPerWeek
SecondsPerMinute = 60
SecondsPerHour = SecondsPerMinute * MinutesPerHour
SecondsPerDay = SecondsPerMinute * MinutesPerDay
SecondsPerWeek = SecondsPerMinute * MinutesPerWeek
// Erreurs
errInvalidTZ = "Invalid timezone %q"
errInvalidValue = "Cannot parse string %q as datetime, please make sure the value is valid"
// Quelques layouts
ANSICLayout = time.ANSIC
UnixDateLayout = time.UnixDate
RubyDateLayout = time.RubyDate
RFC822Layout = time.RFC822
RFC822ZLayout = time.RFC822Z
RFC850Layout = time.RFC850
RFC1123Layout = time.RFC1123
RFC1123ZLayout = time.RFC1123Z
RssLayout = time.RFC1123Z
KitchenLayout = time.Kitchen
RFC2822Layout = time.RFC1123Z
CookieLayout = "Monday, 02-Jan-2006 15:04:05 MST"
RFC3339Layout = "2006-01-02T15:04:05Z07:00"
RFC3339MilliLayout = "2006-01-02T15:04:05.999Z07:00"
RFC3339MicroLayout = "2006-01-02T15:04:05.999999Z07:00"
RFC3339NanoLayout = "2006-01-02T15:04:05.999999999Z07:00"
ISO8601Layout = "2006-01-02T15:04:05-07:00"
ISO8601MilliLayout = "2006-01-02T15:04:05.999-07:00"
ISO8601MicroLayout = "2006-01-02T15:04:05.999999-07:00"
ISO8601NanoLayout = "2006-01-02T15:04:05.999999999-07:00"
RFC1036Layout = "Mon, 02 Jan 06 15:04:05 -0700"
RFC7231Layout = "Mon, 02 Jan 2006 15:04:05 MST"
DayDateTimeLayout = "Mon, Jan 2, 2006 3:04 PM"
DateTimeLayout = "2006-01-02 15:04:05"
DateTimeMilliLayout = "2006-01-02 15:04:05.999"
DateTimeMicroLayout = "2006-01-02 15:04:05.999999"
DateTimeNanoLayout = "2006-01-02 15:04:05.999999999"
ShortDateTimeLayout = "20060102150405"
ShortDateTimeMilliLayout = "20060102150405.999"
ShortDateTimeMicroLayout = "20060102150405.999999"
ShortDateTimeNanoLayout = "20060102150405.999999999"
DateLayout = "2006-01-02"
DateMilliLayout = "2006-01-02.999"
DateMicroLayout = "2006-01-02.999999"
DateNanoLayout = "2006-01-02.999999999"
ShortDateLayout = "20060102"
ShortDateMilliLayout = "20060102.999"
ShortDateMicroLayout = "20060102.999999"
ShortDateNanoLayout = "20060102.999999999"
TimeLayout = "15:04:05"
TimeMilliLayout = "15:04:05.999"
TimeMicroLayout = "15:04:05.999999"
TimeNanoLayout = "15:04:05.999999999"
ShortTimeLayout = "150405"
ShortTimeMilliLayout = "150405.999"
ShortTimeMicroLayout = "150405.999999"
ShortTimeNanoLayout = "150405.999999999"
)

101
datetime/create.go Normal file
View File

@ -0,0 +1,101 @@
package datetime
import (
"fmt"
"time"
. "gitea.zaclys.com/bvaudour/gob/option"
)
// Zero retourne la date correspondant au 01/01/0001 00:00:00 UTC.
// Si tz est fourni, la date est dans le fuseau horaire demandé,
// sinon, dans le fuseau horaire par défaut.
func Zero(tz ...string) Result[time.Time] {
var t time.Time
return toTZ(t, tz...)
}
// Now retourne la date actuelle.
func Now(tz ...string) Result[time.Time] {
t := time.Now()
return toTZ(t, tz...)
}
// Tomorrow retourne la date dans 24h.
func Tomorrow(tz ...string) Result[time.Time] {
now := Now(tz...)
if t, ok := now.Ok(); ok {
return Ok(AddDays(t, 1))
}
return now
}
// Yesterday retourne la date il y a 24h.
func Yesterday(tz ...string) Result[time.Time] {
now := Now(tz...)
if t, ok := now.Ok(); ok {
return Ok(AddDays(t, -1))
}
return now
}
// FromTimestamp convertit un timestamp (exprimé en secondes) en date.
func FromTimestamp(timestamp int64, tz ...string) Result[time.Time] {
t := time.Unix(timestamp, 0)
return toTZ(t, tz...)
}
// FromTimestampMilli convertit un timestamp (exprimé en ms) en date.
func FromTimestampMilli(timestamp int64, tz ...string) Result[time.Time] {
t := time.Unix(timestamp/1e3, (timestamp%1e3)*1e6)
return toTZ(t, tz...)
}
// FromTimestampMicro convertit un timestamp (exprimé en μs) en date.
func FromTimestampMicro(timestamp int64, tz ...string) Result[time.Time] {
t := time.Unix(timestamp/1e6, (timestamp%1e6)*1e3)
return toTZ(t, tz...)
}
// FromTimestampNano convertit un timestamp (exprimé en ns) en date.
func FromTimestampNano(timestamp int64, tz ...string) Result[time.Time] {
t := time.Unix(timestamp/1e9, timestamp%1e9)
return toTZ(t, tz...)
}
// FromDateTime retourne la date à partir des données numériques complètes.
func FromDateTime(year, month, day, hour, minute, second int, tz ...string) Result[time.Time] {
location, ok := getTZ(tz...).Get()
if !ok {
return Err[time.Time](fmt.Errorf(errInvalidTZ, tz))
}
t := time.Date(year, time.Month(month-1), day, hour, minute, second, 0, location)
return Ok(t)
}
// FromDate retourne le début du jour à partir des données de dates.
func FromDate(year, month, day int, tz ...string) Result[time.Time] {
return FromDateTime(year, month, day, 0, 0, 0)
}
// FromTime retourne la date daujourdhui à lheure indiquée.
func FromTime(hour, minute, second int, tz ...string) Result[time.Time] {
now := Now(tz...)
if !now.IsOk() {
return now
}
n, _ := now.Ok()
year, month, day := n.Date()
return FromDateTime(year, int(month+1), day, hour, minute, second, tz...)
}

72
datetime/duration.go Normal file
View File

@ -0,0 +1,72 @@
package datetime
import (
"math"
"time"
)
// DiffInYears retourne (t1 - t2) exprimé en années.
func DiffInYears(t1, t2 time.Time) int64 {
y1, m1, d1 := t1.Date()
y2, m2, d2 := t2.Date()
dy, dm, dd := y1-y2, m1-m2, d1-d2
if dm < 0 || (dm == 0 && dd < 0) {
dy--
}
if dy < 0 && (dd != 0 || dm != 0) {
dy++
}
return int64(dy)
}
// DiffInMonths retourne (t1 - t2) exprimé en mois.
func DiffInMonths(t1, t2 time.Time) int64 {
y1, m1, d1 := t1.Date()
y2, m2, d2 := t2.Date()
dy, dm, dd := y1-y2, m1-m2, d1-d2
if dd < 0 {
dm--
}
if dy == 0 && dm == 0 {
return int64(dm)
}
if dy == 0 && dm != 0 && dd != 0 {
dh := abs(DiffInHours(t1, t2))
if int(dh) < DaysInMonth(t1)*HoursPerDay {
return int64(0)
}
return int64(dm)
}
return int64(dy*MonthsPerYear + int(dm))
}
// DiffInWeeks retourne (t1 - t2) exprimé en semaines.
func DiffInWeeks(t1, t2 time.Time) int64 {
return int64(math.Floor(float64(DiffInSeconds(t1, t2)) / float64(SecondsPerWeek)))
}
// DiffInDays retourne (t1 - t2) exprimé en jours.
func DiffInDays(t1, t2 time.Time) int64 {
return int64(math.Floor(float64(DiffInSeconds(t1, t2)) / float64(SecondsPerDay)))
}
// DiffInHours retourne (t1 - t2) exprimé en heures.
func DiffInHours(t1, t2 time.Time) int64 {
return DiffInSeconds(t1, t2) / SecondsPerHour
}
// DiffInMinutes retourne (t1 - t2) exprimé en minutes.
func DiffInMinutes(t1, t2 time.Time) int64 {
return DiffInSeconds(t1, t2) / SecondsPerMinute
}
// DiffInSeconds retourne (t1 - t2) exprimé en secondes.
func DiffInSeconds(t1, t2 time.Time) int64 {
return t1.Unix() - t2.Unix()
}

206
datetime/format.go Normal file
View File

@ -0,0 +1,206 @@
package datetime
import (
"fmt"
"strings"
"time"
)
func abs[N ~int | ~int64](e N) N {
if e < 0 {
return -e
}
return e
}
func isYearBissextil(year int) bool { return year%4 == 0 && !(year%100 == 0 && year%400 != 0) }
func daysInMonth(year int, month time.Month) int {
switch month {
case time.February:
if isYearBissextil(year) {
return 29
}
return 28
case time.April, time.June, time.September, time.November:
return 30
default:
return 31
}
}
func internetHour(t time.Time) int {
t, _ = ToTimezone(t, "CET").Ok()
h, m, s := t.Clock()
n := t.Nanosecond()
d := time.Duration(n)*Nanosecond + time.Duration(s)*Second + time.Duration(m)*Minute + time.Duration(h)*Hour
i := d * 1000 / Day
return int(i)
}
func gmtDiff(t time.Time) time.Duration {
d0 := t.Day()
h0, m0, _ := t.Clock()
g, _ := ToTimezone(t, "GMT").Ok()
d1 := g.Day()
h1, m1, _ := t.Clock()
return time.Duration(d0-d1)*Day + time.Duration(h0-h1)*Hour + time.Duration(m0-m1)*Minute
}
// Jour
func format_j(t time.Time) string { return fmt.Sprintf("%d", t.Day()) }
func format_d(t time.Time) string { return fmt.Sprintf("%02d", t.Day()) }
func format_N(t time.Time) string { return fmt.Sprintf("%d", t.Weekday()+1) }
func format_w(t time.Time) string { return fmt.Sprintf("%d", t.Weekday()) }
func format_D(t time.Time) string { return shortDays[t.Weekday()] }
func format_l(t time.Time) string { return longDays[t.Weekday()] }
func format_z(t time.Time) string { return fmt.Sprintf("%d", t.YearDay()) }
// Semaine
func format_W(t time.Time) string {
_, w := t.ISOWeek()
return fmt.Sprintf("%d", w)
}
// Mois
func format_n(t time.Time) string { return fmt.Sprintf("%d", t.Month()+1) }
func format_m(t time.Time) string { return fmt.Sprintf("%02d", t.Month()+1) }
func format_M(t time.Time) string { return shortMonths[t.Month()] }
func format_F(t time.Time) string { return longMonths[t.Month()] }
func format_t(t time.Time) string { return fmt.Sprintf("%d", daysInMonth(t.Year(), t.Month())) }
// Année
func format_y(t time.Time) string { return fmt.Sprintf("%02d", t.Year()%100) }
func format_Y(t time.Time) string { return fmt.Sprintf("%d", t.Year()) }
func format_L(t time.Time) string {
if isYearBissextil(t.Year()) {
return "1"
}
return "0"
}
// Heure
func format_a(t time.Time) string {
if t.Hour() < 12 {
return "am"
}
return "pm"
}
func format_A(t time.Time) string {
if t.Hour() < 12 {
return "AM"
}
return "PM"
}
func format_B(t time.Time) string { return fmt.Sprintf("%03d", internetHour(t)) }
func format_g(t time.Time) string {
h := t.Hour() % 12
if h == 0 {
h = 12
}
return fmt.Sprintf("%d", h)
}
func format_G(t time.Time) string { return fmt.Sprintf("%d", t.Hour()) }
func format_h(t time.Time) string {
h := t.Hour() % 12
if h == 0 {
h = 12
}
return fmt.Sprintf("%02d", h)
}
func format_H(t time.Time) string { return fmt.Sprintf("%02d", t.Hour()) }
func format_i(t time.Time) string { return fmt.Sprintf("%02d", t.Minute()) }
func format_s(t time.Time) string { return fmt.Sprintf("%02d", t.Second()) }
func format_v(t time.Time) string { return fmt.Sprintf("%03d", t.Nanosecond()/1000000) }
func format_u(t time.Time) string { return fmt.Sprintf("%06d", t.Nanosecond()/1000) }
// Fuseau horaire
func format_T(t time.Time) string {
name := t.Location().String()
if !strings.Contains(name, "/") {
return name
}
diff := gmtDiff(t)
h, m := diff/Hour, (diff%Hour)/Minute
s := "+"
if h < 0 {
s = "-"
}
if m == 0 {
return fmt.Sprintf("%s%02d", s, abs(h))
}
return fmt.Sprintf("%s%02d:%02d", s, abs(h), abs(m))
}
func format_e(t time.Time) string { return fmt.Sprintf("%s", t.Location()) }
func format_I(t time.Time) string {
if t.IsDST() {
return "1"
}
return "0"
}
func format_O(t time.Time) string {
diff := gmtDiff(t)
h, m := diff/Hour, (diff%Hour)/Minute
s := "+"
if h < 0 {
s = "-"
}
return fmt.Sprintf("%s%02d%02d", s, abs(h), abs(m))
}
func format_P(t time.Time) string {
diff := gmtDiff(t)
h, m := diff/Hour, (diff%Hour)/Minute
s := "+"
if h < 0 {
s = "-"
}
return fmt.Sprintf("%s%02d:%02d", s, abs(h), abs(m))
}
func format_p(t time.Time) string {
diff := gmtDiff(t)
if diff == 0 {
return "Z"
}
return format_P(t)
}
func format_Z(t time.Time) string {
diff := gmtDiff(t)
return fmt.Sprintf("%d", diff/Second)
}
// Date et heure complète
func format_c(t time.Time) string {
// Équivalent à "Y-m-d\TH:i:sP"
return fmt.Sprintf("%s-%s-%sT%s:%s:%s%s", format_Y(t), format_m(t), format_d(t), format_H(t), format_i(t), format_s(t), format_P(t))
}
func format_r(t time.Time) string {
// Équivalent à "D, j M Y H:i:sO"
return fmt.Sprintf("%s, %s %s %s %s:%s:%s%s", format_D(t), format_j(t), format_M(t), format_Y(t), format_H(t), format_i(t), format_s(t), format_P(t))
}
func format_U(t time.Time) string { return fmt.Sprintf("%d", t.Unix()) }
// Format retourne la représentation de la date dans le format indiqué.
// Pour connaître toutes les options possibles pour le format, veullez consulter
// https://www.php.net/manual/fr/datetime.format.php
// (ou le fichier const.go).
func Format(t time.Time, format string) string {
var buffer strings.Builder
runes := []rune(format)
for i := 0; i < len(runes); i++ {
if f, ok := formatters[runes[i]]; ok {
buffer.WriteString(f(t))
} else {
switch runes[i] {
case '\\': // raw output, no parse
buffer.WriteRune(runes[i+1])
i++
default:
buffer.WriteRune(runes[i])
}
}
}
return buffer.String()
}

80
datetime/informations.go Normal file
View File

@ -0,0 +1,80 @@
package datetime
import (
"time"
)
// DaysInMonth retourne le nombre de jours dans le mois.
func DaysInMonth(t time.Time) int { return daysInMonth(t.Year(), t.Month()) }
// DaysInYear retourne le nombe de jours dans lannée.
func DaysInYear(t time.Time) int {
if IsBissextil(t) {
return 366
}
return 365
}
// IsBissextil retourne vrai si la date est située dans une année bissextile.
func IsBissextil(t time.Time) bool { return isYearBissextil(t.Year()) }
// IsWeekend retourne vrai si la date est située dans le weekend.
func IsWeekend(t time.Time) bool {
d := t.Weekday()
return d != time.Saturday && d != time.Sunday
}
// IsBeginOfMonth retourne vrai si la date est dans le premier jour du mois.
func IsBeginOfMonth(t time.Time) bool { return t.Day() == 1 }
// IsEndOfMonth retourne vrai si la date est dans le dernier jour du mois.
func IsEndOfMonth(t time.Time) bool { return t.Day() == DaysInMonth(t) }
// IsBeginOfYear retourne vrai si la date est dans le premier jour de lannée.
func IsBeginOfYear(t time.Time) bool { return IsBeginOfMonth(t) && t.Month() == time.January }
// IsEndOfYear retourne vrai si la date est dans le dernier jour de lannée.
func IsEndOfYear(t time.Time) bool { return IsEndOfMonth(t) && t.Month() == time.December }
// IsToday retourne vrai si la date est située aujourdhui.
func IsToday(t time.Time) bool {
y, m, d := t.Date()
y0, m0, d0 := time.Now().In(t.Location()).Date()
return y == y0 && m == m0 && d == d0
}
// IsTomorrow retourne vrai si la date est située demain.
func IsTomorrow(t time.Time) bool {
n := time.Now().In(t.Location())
y, m, d := t.Date()
y0, m0, d0 := n.Date()
if d > 1 {
return y == y0 && m == m0 && d == d0+1
}
if m > time.January {
return y == y0 && m == m0+1 && IsEndOfMonth(n)
}
return y == y0+1 && IsEndOfYear(n)
}
// IsYesterday retourne vrai si la date est située hier.
func IsYesterday(t time.Time) bool {
n := time.Now().In(t.Location())
y, m, d := t.Date()
y0, m0, d0 := n.Date()
if d < DaysInMonth(t) {
return y == y0 && m == m0 && d == d0-1
}
if m < time.December {
return y == y0 && m == m0-1 && IsBeginOfMonth(n)
}
return y == y0-1 && IsBeginOfYear(n)
}

266
datetime/operations.go Normal file
View File

@ -0,0 +1,266 @@
package datetime
import (
"fmt"
"time"
. "gitea.zaclys.com/bvaudour/gob/option"
)
func addUnit[I int | int64](t time.Time, duration I, unit time.Duration) time.Time {
return t.Add(time.Duration(duration) * unit)
}
// AddNanoseconds ajoute d ns à la date.
func AddNanoseconds[I int | int64](t time.Time, d I) time.Time {
return addUnit(t, d, Nanosecond)
}
// AddMicroseconds ajoute d μs à la date.
func AddMicroseconds[I int | int64](t time.Time, d I) time.Time {
return addUnit(t, d, Microsecond)
}
// AddMilliseconds ajoute d ms à la date.
func AddMilliseconds[I int | int64](t time.Time, d I) time.Time {
return addUnit(t, d, Millisecond)
}
// AddSeconds ajoute d s à la date.
func AddSeconds[I int | int64](t time.Time, d I) time.Time {
return addUnit(t, d, Second)
}
// AddMinutes ajoute d min à la date.
func AddMinutes[I int | int64](t time.Time, d I) time.Time {
return addUnit(t, d, Minute)
}
// AddHours ajoute d heures à la date.
func AddHours[I int | int64](t time.Time, d I) time.Time {
return addUnit(t, d, Hour)
}
// AddDays ajoute d jours à la date.
func AddDays[I int | int64](t time.Time, d I) time.Time {
return addUnit(t, d, Day)
}
// AddWeeks ajoute d semaines à la date.
func AddWeeks[I int | int64](t time.Time, d I) time.Time {
return addUnit(t, d, Week)
}
// AddMonths ajoute d mois à la date.
func AddMonths[I int | int64](t time.Time, d I) time.Time {
return t.AddDate(0, int(d), 0)
}
// AddYears ajoute d années à la date.
func AddYears[I int | int64](t time.Time, d I) time.Time {
return t.AddDate(int(d), 0, 0)
}
// AddDecades ajoute d×10 années à la date.
func AddDecades[I int | int64](t time.Time, d I) time.Time {
return AddYears(t, 10*d)
}
// AddCenturies ajoute d siècles à la date.
func AddCenturies[I int | int64](t time.Time, d I) time.Time {
return AddYears(t, 100*d)
}
func getTZ(tz ...string) (result Option[*time.Location]) {
if len(tz) > 0 {
location, err := time.LoadLocation(tz[0])
if err == nil {
return Some(location)
}
return
}
return Some(DefaultTZ)
}
func toTZ(t time.Time, tz ...string) Result[time.Time] {
if l, ok := getTZ(tz...).Get(); ok {
return Ok(t.In(l))
}
if len(tz) == 0 {
return Ok(t.In(DefaultTZ))
}
return Err[time.Time](fmt.Errorf(errInvalidTZ, tz))
}
// ToTimezone retourne la date dans le fuseau horaire indiqué.
func ToTimezone(t time.Time, tz string) Result[time.Time] {
return toTZ(t, tz)
}
// Local retourne la date dans le fuseau horaire local.
func Local(t time.Time) time.Time {
return t.In(time.Local)
}
// UTC retourne la date dans le fuseau UTC.
func UTC(t time.Time) time.Time {
return t.In(time.UTC)
}
// Date modifie la date avec lannée, le mois et le jour fournis.
func Date(t time.Time, year, month, day int) time.Time {
h, m, s := t.Clock()
e := t.Nanosecond()
l := t.Location()
return time.Date(year, time.Month(month-1), day, h, m, s, e, l)
}
// Clock modifie lheure de la date.
func Clock(t time.Time, hour, minute, second int, nano ...int) time.Time {
y, m, d := t.Date()
l := t.Location()
e := 0
if len(nano) > 0 {
e = nano[0]
}
return time.Date(y, m, d, hour, minute, second, e, l)
}
// SetNano modifie les nanosecondes de la date.
func SetNano(t time.Time, nano int) time.Time {
h, m, s := t.Clock()
return Clock(t, h, m, s, nano)
}
// SetSecond modifie la seconde de la date (et éventuellement la ns).
func SetSecond(t time.Time, second int, nano ...int) time.Time {
h, m := t.Hour(), t.Minute()
e := t.Nanosecond()
if len(nano) > 0 {
e = nano[0]
}
return Clock(t, h, m, second, e)
}
// SetMinute modifie la minute de la date (et éventuellement la s et la ns).
func SetMinute(t time.Time, minute int, other ...int) time.Time {
h, s, e := t.Hour(), t.Second(), t.Nanosecond()
if len(other) > 0 {
s = other[0]
if len(other) > 1 {
e = other[1]
}
}
return Clock(t, h, minute, s, e)
}
// SetHour modifie lheure de la date (et éventuellement la min, s & ns).
func SetHour(t time.Time, hour int, other ...int) time.Time {
m, s, e := t.Minute(), t.Second(), t.Nanosecond()
if len(other) > 0 {
m = other[0]
if len(other) > 1 {
s = other[1]
if len(other) > 2 {
e = other[2]
}
}
}
return Clock(t, hour, m, s, e)
}
// SetDay modifie le jour de la date.
func SetDay(t time.Time, day int) time.Time {
y, m := t.Year(), t.Month()
return Date(t, y, int(m+1), day)
}
// SetMonth modifie le mois de la date (et éventuellement le jour).
func SetMonth(t time.Time, month int, day ...int) time.Time {
y, d := t.Year(), t.Day()
if len(day) > 0 {
d = day[0]
}
return Date(t, y, month, d)
}
// SetYear modifie lannée de la date (et éventuellement le mois et le jour).
func SetYear(t time.Time, year int, other ...int) time.Time {
m, d := int(t.Month()+1), t.Day()
if len(other) > 0 {
m = other[0]
if len(other) > 1 {
d = other[1]
}
}
return Date(t, year, m, d)
}
// BeginOfSecond retourne la date au début de la seconde en cours.
func BeginOfSecond(t time.Time) time.Time { return SetNano(t, 0) }
// EndOfSecond retourne la date à la fin de la seconde en cours.
func EndOfSecond(t time.Time) time.Time { return SetNano(t, 999999999) }
// BeginOfMinute retourne la date au début de la minute en cours.
func BeginOfMinute(t time.Time) time.Time { return SetSecond(t, 0, 0) }
// EndOfMinute retourne la date à la fin de la minute en cours.
func EndOfMinute(t time.Time) time.Time { return SetSecond(t, 59, 999999999) }
// BeginOfHour retourne la date au début de lheure en cours.
func BeginOfHour(t time.Time) time.Time { return SetMinute(t, 0, 0, 0) }
// EndOfHour retourne la date à la fin de lheure en cours.
func EndOfHour(t time.Time) time.Time { return SetMinute(t, 59, 59, 999999999) }
// BeginOfDay retourne la date au début du jour en cours.
func BeginOfDay(t time.Time) time.Time { return SetHour(t, 0, 0, 0, 0) }
// EndOfDay retourne la date à la fin du jour en cours.
func EndOfDay(t time.Time) time.Time { return SetHour(t, 23, 59, 59, 999999999) }
// BeginOfWeek retourne la date au début de la semaine en cours.
func BeginOfWeek(t time.Time) time.Time {
d := t.Weekday()
if d == time.Sunday {
d += 7
}
return BeginOfDay(t.AddDate(0, 0, 1-int(d)))
}
// EndOfWeek retourne la date à la fin de la semaine en cours.
func EndOfWeek(t time.Time) time.Time {
d := t.Weekday()
if d == time.Sunday {
d += 7
}
return EndOfDay(t.AddDate(0, 0, 7-int(d)))
}
// BeginOfMonth retourne la date au début du mois en cours.
func BeginOfMonth(t time.Time) time.Time { return BeginOfDay(SetDay(t, 1)) }
// EndOfMonth retourne la date à la fin du mois en cours.
func EndOfMonth(t time.Time) time.Time { return EndOfDay(SetDay(t, DaysInMonth(t))) }
// BeginOfYear retourne la date au début de lannée en cours.
func BeginOfYear(t time.Time) time.Time { return BeginOfDay(SetMonth(t, 1, 1)) }
// EndOfYear retourne la date à la fin de lannée en cours.
func EndOfYear(t time.Time) time.Time { return EndOfDay(SetMonth(t, 12, 31)) }

95
datetime/parse.go Normal file
View File

@ -0,0 +1,95 @@
package datetime
import (
"fmt"
"strings"
"time"
. "gitea.zaclys.com/bvaudour/gob/option"
)
func format2layout(f string) string {
var buffer strings.Builder
runes := []rune(f)
for i := 0; i < len(runes); i++ {
if layout, ok := parsers[runes[i]]; ok {
buffer.WriteString(layout)
} else {
switch runes[i] {
case '\\': // raw output, no parse
buffer.WriteRune(runes[i+1])
i++
continue
default:
buffer.WriteRune(runes[i])
}
}
}
return buffer.String()
}
func parseInLocation(value, layout string, tz ...string) Result[time.Time] {
location := DefaultTZ
if len(tz) > 0 {
var err error
if location, err = time.LoadLocation(tz[0]); err != nil {
return Err[time.Time](fmt.Errorf(errInvalidTZ, tz[0]))
}
}
t, err := time.ParseInLocation(layout, value, location)
if err == nil {
return Ok(t)
}
return Err[time.Time](fmt.Errorf(errInvalidValue, value))
}
// Guess tente de parser la chaîne de caractères en date en essayant de deviner le format.
// Si le fuseau horaire nest pas précisé dans la chaîne, le fuseau utilisé est tz (si fourni) ou le fuseau horaire par défaut.
func Guess(value string, tz ...string) Result[time.Time] {
if value == "" || value == "0" || value == "0000-00-00 00:00:00" || value == "0000-00-00" || value == "00:00:00" {
return Zero(tz...)
}
switch value {
case "now":
return Now(tz...)
case "yesterday":
return Yesterday(tz...)
case "tomorrow":
return Tomorrow(tz...)
}
if len(tz) > 0 {
if _, err := time.LoadLocation(tz[0]); err != nil {
return Err[time.Time](fmt.Errorf(errInvalidTZ, tz[0]))
}
}
for _, layout := range layouts {
t := parseInLocation(layout, value, tz...)
if t.IsOk() {
return t
}
}
return Err[time.Time](fmt.Errorf(errInvalidValue, value))
}
// ParseFromLayout parse la chaîne de caractères en date à partir du layout (façon Go) fourni.
// Si le fuseau horaire nest pas précisé dans la chaîne, le fuseau utilisé est tz (si fourni) ou le fuseau horaire par défaut.
func ParseFromLayout(value, layout string, tz ...string) Result[time.Time] {
if value == "" || value == "0" || value == "0000-00-00 00:00:00" || value == "0000-00-00" || value == "00:00:00" {
return Zero(tz...)
}
return parseInLocation(value, layout, tz...)
}
// Parse parse la chaîne de caractères en date à partir du format (dans le style PHP) fourni.
// Si le fuseau horaire nest pas précisé dans la chaîne, le fuseau utilisé est tz (si fourni) ou le fuseau horaire par défaut.
func Parse(value, format string, tz ...string) Result[time.Time] {
return ParseFromLayout(value, format2layout(format), tz...)
}

122
datetime/range.go Normal file
View File

@ -0,0 +1,122 @@
package datetime
import (
"time"
. "gitea.zaclys.com/bvaudour/gob/option"
)
// Range représente une période entre deux dates.
type Range struct {
begin time.Time
end time.Time
}
// NewRange initialise une période.
// Si t1 > t2, la date début de la période sera t2, et inversement si t1 < t2.
func NewRange(t1, t2 time.Time) Range {
if Gt(t1, t2) {
t1, t2 = t2, t1
}
return Range{
begin: t1,
end: t2,
}
}
// Begin retourne la date de début de la période.
func (r Range) Begin() time.Time { return r.begin }
// End retourne la date de fin de la période.
func (r Range) End() time.Time { return r.end }
// BeforeDate retourne vrai si la date est située avant la période.
func (r Range) BeforeDate(t time.Time) bool { return Lt(r.end, t) }
// AfterDate retourne vrai si la date est située après la période.
func (r Range) AfterDate(t time.Time) bool { return Gt(r.begin, t) }
// ContainsDate retourne vrai si t ∈ [begin; end].
func (r Range) ContainsDate(t time.Time) bool { return Le(r.begin, t) && Ge(r.end, t) }
// ContainsDateStrictBegin retourne vrai si t ∈ ]begin; end].
func (r Range) ContainsDateStrictBegin(t time.Time) bool { return Lt(r.begin, t) && Ge(r.end, t) }
// ContainsDateStrictEnd retourne vrai si t ∈ [begin; end[.
func (r Range) ContainsDateStrictEnd(t time.Time) bool { return Le(r.begin, t) && Gt(r.end, t) }
// ContainsDateStrict retourne vrai si t ∈ ]begin; end[.
func (r Range) ContainsDateStrict(t time.Time) bool { return Lt(r.begin, t) && Gt(r.end, t) }
// IsFuture retourne vrai si la période est située dans le futur.
func (r Range) IsFuture() bool { return r.AfterDate(time.Now()) }
// IsPast retourne vrai si la période est située dans le passé.
func (r Range) IsPast() bool { return r.BeforeDate(time.Now()) }
// IsNow retourne vrai si la période est en cours.
func (r Range) IsNow() bool { return r.ContainsDate(time.Now()) }
// Before retourne vrai si r est avant r2 sans la recouvrir.
func (r Range) Before(r2 Range) bool { return Le(r.end, r2.begin) }
// After retourne vrai si r est après r2 sans la recouvrir.
func (r Range) After(r2 Range) bool { return Ge(r.begin, r2.end) }
// Contains retourne vrai si r inclut complètement r2.
func (r Range) Contains(r2 Range) bool { return Le(r.begin, r2.begin) && Ge(r.end, r2.end) }
// In retourne vrai si r est complètement inclus dans r2.
func (r Range) In(r2 Range) bool { return r2.Contains(r) }
// Excludes retourne vrai si les périodes ne se chevauchent pas.
func (r Range) Excludes(r2 Range) bool { return Le(r.end, r2.begin) || Ge(r.begin, r2.end) }
// Overlaps retourne vrai si les périodes se chevauchent.
func (r Range) Overlaps(r2 Range) bool { return Lt(r.begin, r2.end) && Gt(r.end, r2.begin) }
// Intersection retourne la période commune aux deux périodes, si elle existe.
func (r Range) Intersection(r2 Range) (result Option[Range]) {
if r.Overlaps(r2) {
result = Some(NewRange(Max(r.begin, r2.begin), Min(r.end, r2.end)))
}
return
}
// Joins retourne la plus grande période contiguë entre deux période, si elle existe.
func (r Range) Joins(r2 Range) (result Option[Range]) {
if Le(r.begin, r2.end) && Ge(r.end, r2.begin) {
result = Some(NewRange(Min(r.begin, r2.begin), Max(r.end, r2.end)))
}
return
}
// Diff retourne lensemble des périodes non communes aux deux périodes.
func (r Range) Diff(r2 Range) (result []Range) {
if Ne(r.begin, r2.begin) {
begin := Min(r.begin, r2.begin)
var end time.Time
if begin == r.begin {
end = Min(r.end, r2.begin)
} else {
end = Min(r2.end, r.begin)
}
result = append(result, NewRange(begin, end))
}
if Ne(r.end, r2.end) {
end := Max(r.end, r2.end)
var begin time.Time
if end == r.end {
begin = Max(r.begin, r2.end)
} else {
begin = Max(r2.begin, r.end)
}
result = append(result, NewRange(begin, end))
}
return
}