From 41e37d83980245d3882c300ae7c7406f0f065ba4 Mon Sep 17 00:00:00 2001 From: Benjamin VAUDOUR Date: Fri, 3 Nov 2023 20:33:26 +0100 Subject: [PATCH] =?UTF-8?q?Module=20datetime=20r=C3=A9=C3=A9crit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- datetime/clock.go | 414 +++++++++++++++++++++++++++++++ datetime/compare.go | 78 ------ datetime/const.go | 375 +++++++++++++++++----------- datetime/create.go | 101 -------- datetime/date.go | 374 ++++++++++++++++++++++++++++ datetime/datetime.go | 522 +++++++++++++++++++++++++++++++++++++++ datetime/duration.go | 99 +++----- datetime/format.go | 104 +++----- datetime/informations.go | 80 ------ datetime/operations.go | 266 -------------------- datetime/parse.go | 83 +++---- datetime/range.go | 178 +++++++------ datetime/util.go | 133 ++++++++++ go.mod | 4 +- 14 files changed, 1894 insertions(+), 917 deletions(-) create mode 100644 datetime/clock.go delete mode 100644 datetime/compare.go delete mode 100644 datetime/create.go create mode 100644 datetime/date.go create mode 100644 datetime/datetime.go delete mode 100644 datetime/informations.go delete mode 100644 datetime/operations.go create mode 100644 datetime/util.go diff --git a/datetime/clock.go b/datetime/clock.go new file mode 100644 index 0000000..c4a0d4f --- /dev/null +++ b/datetime/clock.go @@ -0,0 +1,414 @@ +package datetime + +import ( + "time" +) + +type clock uint + +// Precision représente la précision d’une horloge. +type Precision uint + +func newC0(h, i uint, e ...uint) clock { + h, i, s, v, p := formatC(h, i, e...) + return newC(ms(h, i, s, v), p) +} +func newC(n uint, p Precision) clock { + switch p { + case PrecisionMillisecond: + case PrecisionSecond: + n -= n % MillisecondPerSecond + case PrecisionMinute: + n -= n % MillisecondPerMinute + default: + return clockNil + } + if n > maxClock { + return clockNil + } + return clock(n< NoPrecision && c.dv() <= maxClock } + +func (c clock) dv() uint { return uint(c >> bitsPrecision) } +func (c clock) ds() uint { return c.dv() / MillisecondPerSecond } +func (c clock) di() uint { return c.dv() / MillisecondPerMinute } + +func (c clock) v() uint { return c.dv() % MillisecondPerSecond } +func (c clock) s() uint { return c.ds() % SecondPerMinute } +func (c clock) i() uint { return c.di() % MinutePerHour } +func (c clock) h() uint { return c.dv() / MillisecondPerHour } +func (c clock) t(l *time.Location) (t time.Time) { + if !c.valid() { + return + } + return fromT(c.h(), c.i(), c.s(), c.v(), l) +} +func (c clock) dst(l *time.Location) bool { return c.t(l).IsDST() } +func (c clock) ts(l *time.Location) int64 { return c.t(l).Unix() } +func (c clock) tv(l *time.Location) int64 { return c.t(l).UnixMilli() } + +func (c clock) _setV(e, v uint, p Precision) (v2 uint, p2 Precision) { + if e >= MillisecondPerSecond || p == NoPrecision { + return + } + return v + e - c.v(), PrecisionMillisecond +} +func (c clock) _setS(e, v uint, p Precision) (v2 uint, p2 Precision) { + if e >= SecondPerMinute || p == NoPrecision { + return + } + return v + e*MillisecondPerSecond - c.s()*MillisecondPerSecond, min(p, PrecisionSecond) +} +func (c clock) _setI(e, v uint, p Precision) (v2 uint, p2 Precision) { + if e >= MinutePerHour || p == NoPrecision { + return + } + return v + e*MillisecondPerMinute - c.i()*MillisecondPerMinute, min(p, PrecisionMinute) +} +func (c clock) _setH(e, v uint, p Precision) (v2 uint, p2 Precision) { + if e >= HourPerDay || p == NoPrecision { + return + } + return v + e*MillisecondPerHour - c.h()*MillisecondPerHour, min(p, PrecisionMinute) +} + +func (c clock) setV(v uint) clock { + v, p := c._setV(v, c.dv(), c.p()) + return newC(v, p) +} +func (c clock) setS(s uint, e ...uint) clock { + v, p := c._setS(s, c.dv(), c.p()) + if len(e) > 0 { + v, p = c._setV(e[0], v, p) + } + return newC(v, p) +} +func (c clock) setI(i uint, e ...uint) clock { + v, p := c._setI(i, c.dv(), c.p()) + if len(e) > 0 { + i, p = c._setS(e[0], v, p) + if len(e) > 1 { + i, p = c._setV(e[1], v, p) + } + } + return newC(v, p) +} +func (c clock) setH(h uint, e ...uint) clock { + v, p := c._setH(h, c.dv(), c.p()) + if len(e) > 0 { + v, p = c._setI(e[0], v, p) + if len(e) > 1 { + v, p = c._setS(e[1], v, p) + if len(e) > 2 { + v, p = c._setV(e[2], v, p) + } + } + } + return newC(v, p) +} +func (c clock) setP(p Precision) clock { return newC(c.dv(), p) } + +func (c clock) add(h, i int, e ...int) clock { + if !c.valid() { + return c + } + r, v, p := h*MillisecondPerHour+i*MillisecondPerMinute, c.dv(), c.p() + if len(e) > 0 { + r, p = r+e[0]*MillisecondPerSecond, min(p, PrecisionSecond) + if len(e) > 1 { + r, p = r+e[1], PrecisionMillisecond + } + } + switch { + case r >= 0: + v += uint(r) + case int(v)+r >= 0: + v -= uint(abs(r)) + default: + return clockNil + } + + return newC(v, p) +} +func (c clock) addH(h int) clock { return c.add(h, 0) } +func (c clock) addI(i int) clock { return c.add(0, i) } +func (c clock) addS(s int) clock { return c.add(0, 0, s) } +func (c clock) addV(n int) clock { return c.add(0, 0, 0, n) } + +func (c clock) addDD(d Duration) clock { + e, u := d.Duration() + switch u { + case Millisecond: + return c.add(0, 0, 0, e) + case Second: + return c.add(0, 0, e) + case Minute: + return c.add(0, e) + case Hour: + return c.add(e, 0) + default: + return c + } +} + +func (c clock) bD() clock { return newC(minClock, c.p()) } +func (c clock) eD() clock { return newC(maxClock, c.p()) } +func (c clock) bH() clock { return c.setI(0, beginPrecisionS[c.p()]...) } +func (c clock) eH() clock { return c.setI(59, endPrecisionS[c.p()]...) } +func (c clock) bI() clock { return c.setS(0, beginPrecisionM[c.p()]...) } +func (c clock) eI() clock { return c.setS(59, endPrecisionM[c.p()]...) } +func (c clock) bS() clock { return c.setV(0) } +func (c clock) eS() clock { return c.setV(999) } + +func (c1 clock) cmp(c2 clock) int { + if !c1.valid() { + if !c2.valid() { + return 0 + } + return -1 + } + if !c2.valid() { + return 1 + } + p := max(c1.p(), c2.p()) + c1, c2 = c1.setP(p), c2.setP(p) + return cmp(c1.dv(), c2.dv()) +} + +func (c1 clock) subV(c2 clock) int { return int(c1.dv()) - int(c2.dv()) } +func (c1 clock) subS(c2 clock) int { return c1.subV(c2) / MillisecondPerSecond } +func (c1 clock) subI(c2 clock) int { return c1.subV(c2) / MillisecondPerMinute } +func (c1 Clock) subH(c2 clock) int { return c1.subV(c2) / MillisecondPerHour } + +func (c clock) f(f string, l *time.Location) string { + if !c.valid() { + return "-" + } + return formatT(c.t(l), f) +} +func (c clock) str(l *time.Location) string { return c.f(formatP[c.p()], l) } + +// Clock représente une indication de temps dans la journée. +type Clock struct { + clock + location *time.Location +} + +// ClockNil retourne une horloge nulle. +func ClockNil() (c Clock) { return } + +func newClock(c clock, l *time.Location) Clock { + if c.valid() { + return Clock{ + clock: c, + location: l, + } + } + + return ClockNil() +} +func setClock(v uint, p Precision, l *time.Location) Clock { + return newClock(newC(v, p), l) +} + +// NewClock retourne une horloge dans le fuseau horaire par défaut. +// La précision est calculée automatiquement suivant que la seconde et la milliseconde sont indiquées. +func NewClock(h uint, i uint, args ...uint) Clock { return newClock(newC0(h, i, args...), DefaultTZ) } + +// NewClockTZ agit comme NewClock mais dans le fuseau horaire tz. +func NewClockTZ(tz string, h uint, i uint, args ...uint) Clock { + c := NewClock(h, i, args...) + if c.valid() { + c.location = timezone(tz) + } + + return c +} + +// NewClockFromTime retourne l’heure à partir d’une date Go. +func NewClockFromTime(t time.Time, p ...Precision) Clock { + var ( + pp = PrecisionMillisecond + h, i, s = t.Clock() + v = t.Nanosecond() / int(time.Millisecond) + l = t.Location() + ) + + if len(p) > 0 { + pp = p[0] + } + + return setClock(ms(uint(h), uint(i), uint(s), uint(v)), pp, l) +} + +// ClockNow retourne l’heure actuelle. Si tz est renseignée, +// l’heure est placée dans le fuseau horaire indiqué. +func ClockNow(p Precision, tz ...string) Clock { + return NewClockFromTime(now(tz...), p) +} + +func (c Clock) Now() Clock { return ClockNow(c.p(), c.location.String()) } + +// ClockGuess retourne l’heure à partir de e en essayant de deviner le format. +func ClockGuess(p Precision, e string, tz ...string) Clock { + if t, ok := guess(e, tz...).Get(); ok { + return NewClockFromTime(t, p) + } + + return ClockNil() +} + +// ClockParse retourne l’heure à partir de e en spécifiant le format f. +func ClockParse(p Precision, e, f string, tz ...string) Clock { + if t, ok := parse(e, f).Get(); ok { + return NewClockFromTime(t, p) + } + + return ClockNil() +} + +// Precision retourne la précision de l’horloge. +func (c Clock) Precision() Precision { return c.p() } + +// IsNil retourne vrai si l’horloge est nulle. +func (c Clock) IsNil() bool { return !c.valid() } + +// DayMilli retourne le nombre de millisecondes écoulées dans le jour. +func (c Clock) DayMilli() uint { return c.dv() } + +// DaySecond retourne le nombre de secondes écoulées dans le jour. +func (c Clock) DaySecond() uint { return c.ds() } + +// DayMinute retourne le nombre de minutes écoulées dans le jour. +func (c Clock) DayMinute() uint { return c.di() } + +// Milli retourne la milliseconde dans la seconde. +func (c Clock) Milli() uint { return c.v() } + +// Second retourne la seconde de la minute. +func (c Clock) Second() uint { return c.s() } + +// Minute retourne la minute de l’heure. +func (c Clock) Minute() uint { return c.i() } + +// Hour retourne l’heure du jour. +func (c Clock) Hour() uint { return c.h() } + +// Clock retourne l’heure, la minute, la seconde et la milliseconde. +func (c Clock) Clock() (h, i, s, v uint) { return c.h(), c.i(), c.s(), c.v() } + +// SetMilli modifie la milliseconde. +func (c Clock) SetMilli(v uint) Clock { return newClock(c.setV(v), c.location) } + +// SetSecond modifie la seconde. +func (c Clock) SetSecond(s uint, v ...uint) Clock { return newClock(c.setS(s, v...), c.location) } + +// SetMinute modifie la minute. +func (c Clock) SetMinute(i uint, args ...uint) Clock { return newClock(c.setI(i, args...), c.location) } + +// SetHour modifie l’heure. +func (c Clock) SetHour(h uint, args ...uint) Clock { return newClock(c.setH(h, args...), c.location) } + +// SetPrecision modifie la précision. +func (c Clock) SetPrecision(p Precision) Clock { return newClock(c.setP(p), c.location) } + +// Add ajoute un nombre d’heures et de minutes, et, facultativement, +// de secondes et de millisecondes. +func (c Clock) Add(h, i int, args ...int) Clock { return newClock(c.add(h, i, args...), c.location) } + +// AddHour ajoute une durée en heures. +func (c Clock) AddHour(h int) Clock { return newClock(c.addH(h), c.location) } + +// AddMinute ajoute une durée en minutes. +func (c Clock) AddMinute(i int) Clock { return newClock(c.addI(i), c.location) } + +// AddSecond ajoute une durée en secondes. +func (c Clock) AddSecond(s int) Clock { return newClock(c.addS(s), c.location) } + +// AddMilli ajoute une durée en ms. +func (c Clock) AddMilli(v int) Clock { return newClock(c.addV(v), c.location) } + +// AddDuration ajoute une durée. +func (c Clock) AddDuration(dd Duration) Clock { return newClock(c.addDD(dd), c.location) } + +// BeginOfDay retourne le début de la journée, en gardant la précision. +func (c Clock) BeginOfDay() Clock { return newClock(c.bD(), c.location) } + +// EndOfDay retourne la fin de la journée, en gardant la précision. +func (c Clock) EndOfDay() Clock { return newClock(c.eD(), c.location) } + +// BeginOfHour retourne le début de l’heure en cours. +func (c Clock) BeginOfHour() Clock { return newClock(c.bH(), c.location) } + +// EndOfHour retourne la fin de l’heure en cours. +func (c Clock) EndOfHour() Clock { return newClock(c.eH(), c.location) } + +// BeginOfMinute retourne le début de la minute en cours. +func (c Clock) BeginOfMinute() Clock { return newClock(c.bI(), c.location) } + +// EndOfMinute retourne la fin de la minute en cours. +func (c Clock) EndOfMinute() Clock { return newClock(c.eI(), c.location) } + +// BeginOfSecond retourne le début de la seconde en cours. +func (c Clock) BeginOfSecond() Clock { return newClock(c.bS(), c.location) } + +// EndOfSecond retourne la fin de la seconde en cours. +func (c Clock) EndOfSecond() Clock { return newClock(c.eS(), c.location) } + +// ToTime convertit l’heure en date de type time.Time. +func (c Clock) ToTime() time.Time { return c.t(c.location) } + +// In retourne l’heure dans le fuseau horaire indiqué. +func (c Clock) In(l *time.Location) Clock { + if c.IsNil() { + return c + } + + return NewClockFromTime(c.ToTime().In(l)) +} + +// ToTimezone retourne l’heure dans le fuseau horaire indiqué. +func (c Clock) ToTimezone(tz string) Clock { return c.In(timezone(tz)) } + +// Location retourne le fuseau horaire. +func (c Clock) Location() *time.Location { return c.location } + +// IsDST retourne vrai si le fuseau horaire est à l’heure d’été. +func (c Clock) IsDST() bool { return c.dst(c.location) } + +// Timestamp retourne le timestamp en secondes. +func (c Clock) Timestamp() int64 { return c.ts(c.location) } + +// TimestampMilli retourne le timestamp en secondes. +func (c Clock) TimestampMilli() int64 { return c.tv(c.location) } + +// Compare retourne : +// - 1 si c1 est dans le futur de c2 +// - -1 si c1 est dans le passé de c2 +// - 0 si c1 = c2. +// La comparaison s’effectue après normalisation (ie. même fuseau horaire et même précision). +func (c1 Clock) Compare(c2 Clock) int { return c1.cmp(c2.In(c1.location).clock) } + +func (c1 Clock) Eq(c2 Clock) bool { return c1.Compare(c2) == 0 } +func (c1 Clock) Ne(c2 Clock) bool { return c1.Compare(c2) != 0 } +func (c1 Clock) Gt(c2 Clock) bool { return c1.Compare(c2) > 0 } +func (c1 Clock) Ge(c2 Clock) bool { return c1.Compare(c2) >= 0 } +func (c1 Clock) Lt(c2 Clock) bool { return c1.Compare(c2) < 0 } +func (c1 Clock) Le(c2 Clock) bool { return c1.Compare(c2) <= 0 } + +func (c Clock) IsNow() bool { return c.Eq(c.Now()) } +func (c Clock) IsPast() bool { return c.Lt(c.Now()) } +func (c Clock) IsFuture() bool { return c.Gt(c.Now()) } + +func (c1 Clock) DiffInMillis(c2 Clock) int { return c1.subV(c2.clock) } +func (c1 Clock) DiffInSeconds(c2 Clock) int { return c1.subS(c2.clock) } +func (c1 Clock) DiffInMinutes(c2 Clock) int { return c1.subI(c2.clock) } +func (c1 Clock) DiffInHours(c2 Clock) int { return c1.subH(c2.clock) } + +// Format retourne une représentation de l’heure au format spécifié. +func (c Clock) Format(format string) string { return c.f(format, c.location) } + +// String retourne une représentation au format H:iT, H:i:sT ou H:i:s.vT suivant la précision. +func (c Clock) String() string { return c.str(c.location) } diff --git a/datetime/compare.go b/datetime/compare.go deleted file mode 100644 index 1656898..0000000 --- a/datetime/compare.go +++ /dev/null @@ -1,78 +0,0 @@ -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()) } diff --git a/datetime/const.go b/datetime/const.go index aeac6ae..9e270d4 100644 --- a/datetime/const.go +++ b/datetime/const.go @@ -4,9 +4,229 @@ import ( "time" ) +const ( + // Conversions d’unités. + NanosecondPerMicrosecond = 1000 + MicrosecondPerMillisecond = 1000 + MillisecondPerSecond = 1000 + SecondPerMinute = 60 + MinutePerHour = 60 + HourPerDay = 24 + DayPerWeek = 7 + MonthPerYear = 12 + BeatPerDay = 1000 + + HourPerWeek = HourPerDay * DayPerWeek + MinutePerDay = MinutePerHour * HourPerDay + MinutePerWeek = MinutePerHour * HourPerWeek + SecondPerHour = SecondPerMinute * MinutePerHour + SecondPerDay = SecondPerMinute * MinutePerDay + SecondPerWeek = SecondPerMinute * MinutePerWeek + MillisecondPerMinute = MillisecondPerSecond * SecondPerMinute + MillisecondPerHour = MillisecondPerSecond * SecondPerHour + MillisecondPerDay = MillisecondPerSecond * SecondPerDay + MillisecondPerWeek = MillisecondPerSecond * SecondPerWeek + MicrosecondPerSecond = MicrosecondPerMillisecond * MillisecondPerSecond + NanosecondPerMillisecond = NanosecondPerMicrosecond * MicrosecondPerMillisecond + NanosecondPerSecond = NanosecondPerMicrosecond * MicrosecondPerSecond + + // Unités supportées + NoUnit Unit = iota + Millisecond + Second + Minute + Hour + Day + Week + Month + Year + + // Durée nulle + DurationNil = Duration(0) + + bitsUnit = Duration(4) + bitsValue = 64 - bitsUnit + + maskUnit = 1< 0 { + m = e[0] + if len(e) > 1 { + d = e[1] + } else if ml := monthLen(y, m); d > ml { + d = ml + } + } + return newD(y, m, d) +} +func (t date) setM(m uint, e ...uint) date { + if !t.valid() { + return t + } + y, d := t.y(), t.d() + if len(e) > 0 { + d = e[0] + } else if ml := monthLen(y, m); d > ml { + d = ml + } + return newD(y, m, d) +} +func (t date) setD(d uint) date { + if !t.valid() { + return t + } + t = replD(t, d, "d") + if !t.valid() { + return dateNil + } + return t +} + +func (t date) add(y, m, d int) date { + if !t.valid() { + return t + } + y, m, d = t.y()+y, int(t.m())+m, int(t.d())+d + if m > int(December) { + mm := m - 1 + y, m = y+mm/MonthPerYear, (mm%MonthPerYear)+1 + } else if m < int(January) { + mm := m - 12 + y, m = y+mm/MonthPerYear, (mm%MonthPerYear)+12 + } + for !validD(y, uint(m), uint(d)) { + ml := int(monthLen(y, uint(m))) + if d > ml { + d -= ml + m++ + if m > int(December) { + m -= MonthPerYear + y++ + } + } else if d < 1 { + m-- + if m < int(January) { + m += MonthPerYear + y-- + } + d += int(monthLen(y, uint(m))) + } + } + return newD(y, uint(m), uint(d)) +} +func (t date) addY(y int) date { return t.add(y, 0, 0) } +func (t date) addM(m int) date { return t.add(0, m, 0) } +func (t date) addW(w int) date { return t.addD(w * DayPerWeek) } +func (t date) addD(d int) date { return t.add(0, 0, d) } +func (t date) addDD(dd Duration) date { + ad := func(ud int) func(int) date { + return func(e int) date { return t.addD(e / ud) } + } + f := map[Unit]func(int) date{ + Year: t.addY, + Month: t.addM, + Week: t.addW, + Day: t.addD, + Hour: ad(HourPerDay), + Minute: ad(MinutePerDay), + Second: ad(SecondPerDay), + Millisecond: ad(MillisecondPerDay), + } + e, u := dd.Value(), dd.Unit() + if cb, ok := f[u]; ok { + return cb(e) + } + return t +} + +func (t date) bY() date { return t.setM(January, 1) } +func (t date) eY() date { return t.setM(December, 31) } +func (t date) bM() date { return t.setD(1) } +func (t date) eM() date { return t.setD(monthLen(t.y(), t.m())) } +func (t date) bW(l *time.Location) date { return t.addD(diffBow(t.wd(l))) } +func (t date) eW(l *time.Location) date { return t.addD(diffEow(t.wd(l))) } + +func (t1 date) cmp(t2 date) int { + if !t1.valid() { + if !t2.valid() { + return 0 + } + return -1 + } + if !t2.valid() { + return 1 + } + if c := cmp(t1.y(), t2.y()); c != 0 { + return c + } + if c := cmp(t1.m(), t2.m()); c != 0 { + return c + } + return cmp(t1.d(), t2.d()) +} + +func (t1 date) diff(t2 date) (dy, dm, dd int) { + return t1.y() - t2.y(), int(t1.m()) - int(t2.m()), int(t1.d()) - int(t2.d()) +} +func (t1 date) subD(t2 date, l1, l2 *time.Location) int { + return int(t1.ts(l1)-t2.ts(l2)) / SecondPerDay +} +func (t1 date) subW(t2 date, l1, l2 *time.Location) int { return t1.subD(t2, l1, l2) / DayPerWeek } +func (t1 date) subM(t2 date) int { + dy, dm, dd := t1.diff(t2) + if dd < 0 { + dm-- + } + return dy*MonthPerYear + dm +} +func (t1 date) subY(t2 date) int { return t1.subM(t2) / MonthPerYear } + +func (t date) f(f string, l *time.Location) string { + if !t.valid() { + return "-" + } + return formatT(t.t(l), f) +} +func (t date) str(l *time.Location) string { return t.f("Y-m-d", l) } + +// Date représente une date sans l’heure de la journée. +type Date struct { + date + location *time.Location +} + +// DateNil retourne une date nulle. +func DateNil() (t Date) { return } + +func newDate(t date, l *time.Location) Date { + if t.valid() { + return Date{ + date: t, + location: l, + } + } + + return DateNil() +} + +// NewDate retourne la date dans le fuseau horaire demandé. +func NewDate(y int, m, d uint, tz ...string) Date { + return newDate(newD(y, m, d), timezone(tz...)) +} + +// NewDateFromTime retourne la date à partir d’une date Go. +func NewDateFromTime(t time.Time) Date { + y, m, d := t.Date() + l := t.Location() + + return newDate(newD(y, uint(m+1), uint(d)), l) +} + +// DateNow retourne la date actuelle. Si tz est renseignée, +// la date est placée dans le fuseau horaire indiqué. +func DateNow(tz ...string) Date { return NewDateFromTime(now(tz...)) } + +func (t Date) Now() Date { return DateNow(t.location.String()) } + +// DateGuess retourne la date à partir de e en essayant de deviner le format. +func DateGuess(e string, tz ...string) Date { + if t, ok := guess(e, tz...).Get(); ok { + return NewDateFromTime(t) + } + + return DateNil() +} + +// DateParse retourne la date à partir de v en spécifiant le format f. +func DateParse(e, f string, tz ...string) Date { + if t, ok := parse(e, f).Get(); ok { + return NewDateFromTime(t) + } + + return DateNil() +} + +// IsNil retourne vrai si la date est nulle. +func (t Date) IsNil() bool { return !t.valid() } + +// Year retourne l’année. +func (t Date) Year() int { return t.y() } + +// Month retourne le mois (de 1 à 12). +func (t Date) Month() uint { return t.m() } + +// Day retourne le jour du mois (de 1 à 31). +func (t Date) Day() uint { return t.d() } + +// Date retourne l’année, le mois et le jour du mois. +func (t Date) Date() (y int, m, d uint) { return t.y(), t.m(), t.d() } + +// YearDay retourne le jour dans l’année (de 1 à 366). +func (t Date) YearDay() uint { return t.yd() } + +// WeekDay retourne le jour de la semaine (de 0 à 6 en commençant par dimanche). +func (t Date) WeekDay() uint { return t.wd(t.location) } + +// IsBissextil retourn vrai si la date est dans une année bissextile. +func (t Date) IsBissextil() bool { return t.b() } + +// DaysInYear retourne le nombre de jours dans l’année. +func (t Date) DaysInYear() uint { return t.yl() } + +// DaysInMonth retourne le nombre de jours dans le mois. +func (t Date) DaysInMonth() uint { return t.ml() } + +// SetYear crée une nouvelle date en changeant l’année (et facultativement le mois et le jour). +func (t Date) SetYear(y int, md ...uint) Date { return newDate(t.setY(y, md...), t.location) } + +// SetMonth crée une nouvelle date en changeant le mois (et facultativement le jour). +func (t Date) SetMonth(m uint, d ...uint) Date { return newDate(t.setM(m, d...), t.location) } + +// SetDay crée une nouvelle date en changeant le jour. +func (t Date) SetDay(d uint) Date { return newDate(t.setD(d), t.location) } + +// Add incrémente l’année, le mois et le jour. +func (t Date) Add(y, m, d int) Date { return newDate(t.add(y, m, d), t.location) } + +// AddYear incrémente l’année. +func (t Date) AddYear(y int) Date { return newDate(t.addY(y), t.location) } + +// AddMonth incrémente le mois. +func (t Date) AddMonth(m int) Date { return newDate(t.addM(m), t.location) } + +// AddWeek incrémente la date de week semaines. +func (t Date) AddWeek(w int) Date { return newDate(t.addW(w), t.location) } + +// AddDay incrémente le jour. +func (t Date) AddDay(d int) Date { return newDate(t.addD(d), t.location) } + +// AddDuration décale la date d’une durée donnée. +func (t Date) AddDuration(dd Duration) Date { return newDate(t.addDD(dd), t.location) } + +// BeginOfYear retourne la date du premier jour de l’année. +func (t Date) BeginOfYear() Date { return newDate(t.bY(), t.location) } + +// EndOfYear retourne la date du dernier jour de l’année. +func (t Date) EndOfYear() Date { return newDate(t.eY(), t.location) } + +// BeginOfMonth retourne la date du premier jour du mois. +func (t Date) BeginOfMonth() Date { return newDate(t.bM(), t.location) } + +// EndOfMonth retourne la date du dernier jour du mois. +func (t Date) EndOfMonth() Date { return newDate(t.eM(), t.location) } + +// BeginOfWeek retourne la date du premier jour de la semaine (ie. lundi). +func (t Date) BeginOfWeek() Date { return newDate(t.bW(t.location), t.location) } + +// EndOfWeek retourne la date du dernier jour de la semaine. +func (t Date) EndOfWeek() Date { return newDate(t.eW(t.location), t.location) } + +// ToTime convertit la date en date de type time.Time. +func (t Date) ToTime() time.Time { return t.t(t.location) } + +// In retourne la date dans le fuseau horaire indiqué. +func (t Date) In(l *time.Location) Date { + if !t.valid() { + return t + } + + return NewDateFromTime(t.t(t.location).In(l)) +} + +// ToTimezone retourne la date dans le fuseau horaire indiqué. +func (t Date) ToTimezone(tz string) Date { return t.In(timezone(tz)) } + +// Location retourne le fuseau horaire. +func (t Date) Location() *time.Location { return t.location } + +// IsDST retourne vrai si le fuseau horaire est à l’heure d’été. +func (t Date) IsDST() bool { return t.dst(t.location) } + +// Timestamp retourne le timestamp en secondes. +func (t Date) Timestamp() int64 { return t.ts(t.location) } + +// TimestampMilli retourne le timestamp en secondes. +func (t Date) TimestampMilli() int64 { return t.tv(t.location) } + +// Compare compare 2 dates. +func (t1 Date) Compare(t2 Date) int { return t1.cmp(t2.In(t1.location).date) } + +func (t1 Date) Eq(t2 Date) bool { return t1.Compare(t2) == 0 } +func (t1 Date) Ne(t2 Date) bool { return t1.Compare(t2) != 0 } +func (t1 Date) Gt(t2 Date) bool { return t1.Compare(t2) > 0 } +func (t1 Date) Ge(t2 Date) bool { return t1.Compare(t2) >= 0 } +func (t1 Date) Lt(t2 Date) bool { return t1.Compare(t2) < 0 } +func (t1 Date) Le(t2 Date) bool { return t1.Compare(t2) <= 0 } + +func (t Date) IsNow() bool { return t.Eq(t.Now()) } +func (t Date) IsPast() bool { return t.Lt(t.Now()) } +func (t Date) IsFuture() bool { return t.Gt(t.Now()) } + +func (t1 Date) diff(t2 Date) (dy, dm, dd int) { + return t1.y() - t2.y(), int(t1.m()) - int(t2.m()), int(t1.d()) - int(t2.d()) +} + +func (t1 Date) DiffInDays(t2 Date) int { return t1.subD(t2.date, t1.location, t2.location) } +func (t1 Date) DiffInWeeks(t2 Date) int { return t1.subW(t2.date, t1.location, t2.location) } +func (t1 Date) DiffInMonths(t2 Date) int { return t1.subM(t2.date) } +func (t1 Date) DiffInYears(t2 Date) int { return t1.subY(t2.date) } + +// Format retourne une représentation de la date au format spécifié. +func (t Date) Format(format string) string { return t.f(format, t.location) } + +// String retourne la date au format Y-m-d. +func (t Date) String() string { return t.str(t.location) } diff --git a/datetime/datetime.go b/datetime/datetime.go new file mode 100644 index 0000000..46ae3ae --- /dev/null +++ b/datetime/datetime.go @@ -0,0 +1,522 @@ +package datetime + +import ( + "time" +) + +type datetime struct { + date + clock clock +} + +func newDT(d date, c clock) datetime { + if d.valid() && c.valid() { + return datetime{d, c} + } + return datetime{dateNil, clockNil} +} + +func (dt datetime) h() uint { return dt.clock.h() } +func (dt datetime) i() uint { return dt.clock.i() } +func (dt datetime) s() uint { return dt.clock.s() } +func (dt datetime) v() uint { return dt.clock.v() } +func (dt datetime) p() Precision { return dt.clock.p() } +func (dt datetime) dv() uint { return dt.clock.dv() } +func (dt datetime) ds() uint { return dt.clock.ds() } +func (dt datetime) di() uint { return dt.clock.di() } + +func (dt datetime) t(l *time.Location) time.Time { + return fromDT(dt.y(), dt.m(), dt.d(), dt.h(), dt.i(), dt.s(), dt.v(), l) +} +func (dt datetime) dst(l *time.Location) bool { return dt.t(l).IsDST() } +func (dt datetime) ts(l *time.Location) int64 { return dt.t(l).Unix() } +func (dt datetime) tv(l *time.Location) int64 { return dt.t(l).UnixMilli() } + +func (dt datetime) valid() bool { return dt.date.valid() && dt.clock.valid() } + +func (dt datetime) setY(y int, e ...uint) datetime { + ed, ec := spl(e, 2) + d, c := dt.date.setY(y, ed...), dt.clock + if len(ec) > 0 { + c = c.setH(ec[0], ec[1:]...) + } + return newDT(d, c) +} +func (dt datetime) setM(m uint, e ...uint) datetime { + ed, ec := spl(e, 1) + d, c := dt.date.setM(m, ed...), dt.clock + if len(ec) > 0 { + c = c.setH(ec[0], ec[1:]...) + } + return newDT(d, c) +} +func (dt datetime) setD(d uint, e ...uint) datetime { + t, c := dt.date.setD(d), dt.clock + if len(e) > 0 { + c = c.setH(e[0], e[1:]...) + } + return newDT(t, c) +} +func (dt datetime) setH(h uint, e ...uint) datetime { return newDT(dt.date, dt.clock.setH(h, e...)) } +func (dt datetime) setI(i uint, e ...uint) datetime { return newDT(dt.date, dt.clock.setI(i, e...)) } +func (dt datetime) setS(s uint, e ...uint) datetime { return newDT(dt.date, dt.clock.setS(s, e...)) } +func (dt datetime) setV(v uint) datetime { return newDT(dt.date, dt.clock.setV(v)) } +func (dt datetime) setP(p Precision) datetime { return newDT(dt.date, dt.clock.setP(p)) } + +func (dt datetime) add(y, m, d, h, i int, e ...int) datetime { + if !dt.valid() { + return dt + } + p := dt.p() + v := int(dt.dv()) + h*MillisecondPerHour + i*MillisecondPerMinute + if len(e) > 0 { + p, v = min(p, PrecisionSecond), v+e[0]*MillisecondPerSecond + if len(e) > 1 { + p, v = PrecisionMillisecond, v+e[1] + } + } + dc := v / MillisecondPerDay + d, v = d+dc, v-dc*MillisecondPerDay + return newDT(dt.date.add(y, m, d), newC(uint(v), p)) +} +func (dt datetime) addT(y, m, d int) datetime { return newDT(dt.date.add(y, m, d), dt.clock) } +func (dt datetime) addC(h, i int, e ...int) datetime { return dt.add(0, 0, 0, h, i, e...) } +func (dt datetime) addY(y int) datetime { return dt.addT(y, 0, 0) } +func (dt datetime) addM(m int) datetime { return dt.addT(0, m, 0) } +func (dt datetime) addW(w int) datetime { return dt.addD(w * DayPerWeek) } +func (dt datetime) addD(d int) datetime { return dt.addT(0, 0, d) } +func (dt datetime) addH(h int) datetime { return dt.addC(h, 0) } +func (dt datetime) addI(i int) datetime { return dt.addC(0, i) } +func (dt datetime) addS(s int) datetime { return dt.addC(0, 0, s) } +func (dt datetime) addV(n int) datetime { return dt.addC(0, 0, 0, n) } +func (dt datetime) addDD(dd Duration) datetime { + f := map[Unit]func(int) datetime{ + Year: dt.addY, + Month: dt.addM, + Week: dt.addW, + Day: dt.addD, + Hour: dt.addH, + Minute: dt.addI, + Second: dt.addS, + Millisecond: dt.addV, + } + e, u := dd.Value(), dd.Unit() + if cb, ok := f[u]; ok { + return cb(e) + } + return dt +} + +func (dt datetime) bY() datetime { return dt.setM(January, 1).bD() } +func (dt datetime) eY() datetime { return dt.setM(December, 31).eD() } +func (dt datetime) bM() datetime { return dt.setD(1).bD() } +func (dt datetime) eM() datetime { return dt.setD(monthLen(dt.y(), dt.m())).eD() } +func (dt datetime) bW(l *time.Location) datetime { return newDT(dt.date.bW(l), dt.clock).bD() } +func (dt datetime) eW(l *time.Location) datetime { return newDT(dt.date.eW(l), dt.clock).eD() } +func (dt datetime) bD() datetime { return newDT(dt.date, dt.clock.bD()) } +func (dt datetime) eD() datetime { return newDT(dt.date, dt.clock.eD()) } +func (dt datetime) bH() datetime { return newDT(dt.date, dt.clock.bH()) } +func (dt datetime) eH() datetime { return newDT(dt.date, dt.clock.eH()) } +func (dt datetime) bI() datetime { return newDT(dt.date, dt.clock.bI()) } +func (dt datetime) eI() datetime { return newDT(dt.date, dt.clock.eI()) } +func (dt datetime) bS() datetime { return newDT(dt.date, dt.clock.bS()) } +func (dt datetime) eS() datetime { return newDT(dt.date, dt.clock.eS()) } + +func (dt1 datetime) cmp(dt2 datetime) int { + if !dt1.valid() { + if !dt2.valid() { + return 0 + } + return -1 + } + if !dt2.valid() { + return 1 + } + if c := dt1.date.cmp(dt2.date); c != 0 { + return c + } + return dt1.clock.cmp(dt2.clock) +} + +func (dt1 datetime) subV(dt2 datetime, l1, l2 *time.Location) int { + return int(dt1.tv(l1) - dt2.tv(l2)) +} +func (dt1 datetime) subS(dt2 datetime, l1, l2 *time.Location) int { + return dt1.subV(dt2, l1, l2) / MillisecondPerSecond +} +func (dt1 datetime) subI(dt2 datetime, l1, l2 *time.Location) int { + return dt1.subV(dt2, l1, l2) / MillisecondPerMinute +} +func (dt1 datetime) subH(dt2 datetime, l1, l2 *time.Location) int { + return dt1.subV(dt2, l1, l2) / MillisecondPerHour +} +func (dt1 datetime) subD(dt2 datetime, l1, l2 *time.Location) int { + return dt1.subV(dt2, l1, l2) / MillisecondPerMinute +} +func (dt1 datetime) subW(dt2 datetime, l1, l2 *time.Location) int { + return dt1.subV(dt2, l1, l2) / MillisecondPerWeek +} +func (dt1 datetime) subM(dt2 datetime) int { + d := dt1.date.subM(dt2.date) + if d < 0 { + dd, dv := int(dt1.d())-int(dt2.d()), int(dt1.dv())-int(dt2.dv()) + if dd > 0 || (dd == 0 && dv > 0) { + d++ + } + } + return d +} +func (dt1 datetime) subY(dt2 datetime) int { return dt1.subM(dt2) / MonthPerYear } + +func (dt datetime) f(f string, l *time.Location) string { + if !dt.valid() { + return "-" + } + return formatT(dt.t(l), f) +} +func (dt datetime) str(l *time.Location) string { return dt.f("Y-m-d", l) } + +// DateTime représente une indication de temps. +type DateTime struct { + datetime + location *time.Location +} + +// DateTimeNil retourne un temps nul. +func DateTimeNil() (dt DateTime) { return } + +func initDT0(d date, c clock, l *time.Location) DateTime { return initDT(newDT(d, c), l) } +func initDT(dt datetime, l *time.Location) DateTime { + if !dt.valid() { + return DateTimeNil() + } + return DateTime{dt, l} +} + +// NewDateTime retourne un temps dans le fuseau horaire par défaut. +// La précision est calculée automatiquement suivant que la seconde et la milliseconde sont indiquées. +func NewDateTime(y int, m, d, h, i uint, args ...uint) DateTime { + return initDT0(newD(y, m, d), newC0(h, i, args...), DefaultTZ) +} + +// NewDateTimeTZ agit comme NewDateTime mais dans le fuseau horaire tz. +func NewDateTimeTZ(tz string, y int, m, d, h, i uint, args ...uint) DateTime { + dt := NewDateTime(y, m, d, h, m, args...) + if dt.valid() { + dt.location = timezone(tz) + } + + return dt +} + +// NewDateTimeFromTime retourne le temps à partir d’une date Go. +func NewDateTimeFromTime(t time.Time, p ...Precision) DateTime { + var ( + pp = PrecisionMillisecond + y, m, d = t.Date() + h, i, s = t.Clock() + v = t.Nanosecond() / NanosecondPerMillisecond + l = t.Location() + ) + + if len(p) > 0 { + pp = p[0] + } + + return initDT0(newD(y, uint(m+1), uint(d)), newC(ms(uint(h), uint(i), uint(s), uint(v)), pp), l) +} + +// DateTimeNow retourne la date actuelle. Si tz est renseignée, +// la date est placée dans le fuseau horaire indiqué. +func DateTimeNow(p Precision, tz ...string) DateTime { + return NewDateTimeFromTime(now(tz...), p) +} + +func (dt DateTime) Now() DateTime { return DateTimeNow(dt.p(), dt.location.String()) } + +// DateTimeGuess retourne la date à partir de e en essayant de deviner le format. +func DateTimeGuess(p Precision, e string, tz ...string) DateTime { + if t, ok := guess(e, tz...).Get(); ok { + return NewDateTimeFromTime(t, p) + } + + return DateTimeNil() +} + +// DateTimeParse retourne la date à partir de e en spécifiant le format f. +func DateTimeParse(p Precision, e, f string, tz ...string) DateTime { + if t, ok := parse(e, f, tz...).Get(); ok { + return NewDateTimeFromTime(t, p) + } + + return DateTimeNil() +} + +// Precision retourne la précision de l’heure. +func (dt DateTime) Precision() Precision { return dt.p() } + +// IsNil retourne vrai si la date est nulle. +func (dt DateTime) IsNil() bool { return !dt.valid() } + +// Year retourne l’année. +func (dt DateTime) Year() int { return dt.y() } + +// Month retourne le mois. +func (dt DateTime) Month() uint { return dt.m() } + +// Day retourne le . +func (dt DateTime) Day() uint { return dt.d() } + +// Hour retourne l’heure. +func (dt DateTime) Hour() uint { return dt.h() } + +// Minute retourne la minute. +func (dt DateTime) Minute() uint { return dt.i() } + +// Second retourne la seconde. +func (dt DateTime) Second() uint { return dt.s() } + +// Milli retourne la milliseconde. +func (dt DateTime) Milli() uint { return dt.v() } + +// Date retourne la partie date. +func (dt DateTime) Date() (y int, m, d uint) { return dt.y(), dt.m(), dt.d() } + +// Clock retourne la partie horaire. +func (dt DateTime) Clock() (h, i, s, v uint) { return dt.h(), dt.i(), dt.s(), dt.m() } + +// YearDay retourne le jour dans l’année (de 1 à 366). +func (dt DateTime) YeardDay() uint { return dt.yd() } + +// WeekDay retourne le jour dans la semaine (de 0 à 6 en commençant par dimanche). +func (dt DateTime) WeekDay() uint { return dt.wd(dt.location) } + +// IsBissextil retourne vrai si la date est dans une année bissextile. +func (dt DateTime) IsBissextile() bool { return dt.b() } + +// DaysInYear retourne le nombre de jours dans l’année. +func (dt DateTime) DaysInYear() uint { return dt.yl() } + +// DaysInMonth retourne le nombre de jours dans le mois. +func (dt DateTime) DaysInMonth() uint { return dt.ml() } + +// DayMinute retourne le nombre de minutes écoulées dans la journée. +func (dt DateTime) DayMinute() uint { return dt.di() } + +// DaySecond retourne le nombre de secondes écoulées dans la journée. +func (dt DateTime) DaySecond() uint { return dt.ds() } + +// DayMilli retourne le nombre de millisecondes écoulées dans la journée. +func (dt DateTime) DayMilli() uint { return dt.dv() } + +// ToDate convertit le temps en ne conservant que la partie date. +func (dt DateTime) ToDate() Date { return newDate(dt.date, dt.location) } + +// ToClock convertit le temps en ne conservant que la partie heure. +func (dt DateTime) ToClock() Clock { return newClock(dt.clock, dt.location) } + +// ToDateClock sépare la partie date et la partie heure. +func (dt DateTime) ToDateClock() (Date, Clock) { return dt.ToDate(), dt.ToClock() } + +// SetYear modifie l’année. +func (dt DateTime) SetYear(y int, args ...uint) DateTime { + return initDT(dt.setY(y, args...), dt.location) +} + +// SetMonth modifie le mois. +func (dt DateTime) SetMonth(m uint, args ...uint) DateTime { + return initDT(dt.setM(m, args...), dt.location) +} + +// SetDay modifie le jour. +func (dt DateTime) SetDay(d uint, args ...uint) DateTime { + return initDT(dt.setD(d, args...), dt.location) +} + +// SetHour modifie l’heure. +func (dt DateTime) SetHour(h uint, args ...uint) DateTime { + return initDT(dt.setH(h, args...), dt.location) +} + +// SetMinute modifie la minute. +func (dt DateTime) SetMinute(i uint, args ...uint) DateTime { + return initDT(dt.setI(i, args...), dt.location) +} + +// SetSecond modifie la seconde. +func (dt DateTime) SetSecond(s uint, args ...uint) DateTime { + return initDT(dt.setS(s, args...), dt.location) +} + +// SetMilli modifie la milliseconde. +func (dt DateTime) SetMilli(v uint) DateTime { return initDT(dt.setV(v), dt.location) } + +// SetPrecision modifie la précision. +func (dt DateTime) SetPrecision(p Precision) DateTime { return initDT(dt.setP(p), dt.location) } + +// Add incrémente l’année, le mois, le jour, l’heure, la minute, +// et facultativement la seconde et la milliseconde. +func (dt DateTime) Add(y, m, d, h, i int, args ...int) DateTime { + return initDT(dt.add(y, m, d, h, i, args...), dt.location) +} + +// AddDate incrémente l’année, le mois et le jour. +func (dt DateTime) AddDate(y, m, d int) DateTime { return initDT(dt.addT(y, m, d), dt.location) } + +// AddClock incrémente l’heure, la minute et facultativement la seconde et la milliseconde. +func (dt DateTime) AddClock(h, i int, args ...int) DateTime { + return initDT(dt.addC(h, i, args...), dt.location) +} + +// AddYear incrémente l’année. +func (dt DateTime) AddYear(y int) DateTime { return initDT(dt.addY(y), dt.location) } + +// AddMonth incrémente le mois. +func (dt DateTime) AddMonth(m int) DateTime { return initDT(dt.addM(m), dt.location) } + +// AddWeek incrémente la semaine. +func (dt DateTime) AddWeek(w int) DateTime { return initDT(dt.addW(w), dt.location) } + +// AddDay incrémente le jour. +func (dt DateTime) AddDay(d int) DateTime { return initDT(dt.addD(d), dt.location) } + +// AddHour incrémente l’heure. +func (dt DateTime) AddHour(h int) DateTime { return initDT(dt.addH(h), dt.location) } + +// AddMinute incrémente la minute. +func (dt DateTime) AddMinute(i int) DateTime { return initDT(dt.addI(i), dt.location) } + +// AddSecond incrémente la seconde. +func (dt DateTime) AddSecond(s int) DateTime { return initDT(dt.addS(s), dt.location) } + +// AddMilli incrémente la milliseconde. +func (dt DateTime) AddMilli(v int) DateTime { return initDT(dt.addV(v), dt.location) } + +// AddDuration incrémente une durée. +func (dt DateTime) AddDuration(dd Duration) DateTime { return initDT(dt.addDD(dd), dt.location) } + +// BeginOfYear retourne la date en début d’année. +func (dt DateTime) BeginOfYear() DateTime { return initDT(dt.bY(), dt.location) } + +// EndOfYear retourne la date en fin d’année. +func (dt DateTime) EndOfYear() DateTime { return initDT(dt.eY(), dt.location) } + +// BeginOfMonth retourne la date en début de mois. +func (dt DateTime) BeginOfMonth() DateTime { return initDT(dt.bM(), dt.location) } + +// EndOfMonth retourne la date en fin de mois. +func (dt DateTime) EndOfMonth() DateTime { return initDT(dt.eM(), dt.location) } + +// BeginOfWeek retourne la date en début de semaine. +func (dt DateTime) BeginOfWeek() DateTime { return initDT(dt.bW(dt.location), dt.location) } + +// EndOfWeek retourne la date en fin de semaine. +func (dt DateTime) EndOfWeek() DateTime { return initDT(dt.eW(dt.location), dt.location) } + +// BeginOfDay retourne la date en début de jour. +func (dt DateTime) BeginOfDay() DateTime { return initDT(dt.bD(), dt.location) } + +// EndOfDay retourne la date en fin de jour. +func (dt DateTime) EndOfDay() DateTime { return initDT(dt.eD(), dt.location) } + +// BeginOfHour retourne la date en début d’heure. +func (dt DateTime) BeginOfHour() DateTime { return initDT(dt.bH(), dt.location) } + +// EndOfHour retourne la date en fin d’heure. +func (dt DateTime) EndOfHour() DateTime { return initDT(dt.eH(), dt.location) } + +// BeginOfMinute retourne la date en début de minute. +func (dt DateTime) BeginOfMinute() DateTime { return initDT(dt.bI(), dt.location) } + +// EndOfMinute retourne la date en fin de minute. +func (dt DateTime) EndOfMinute() DateTime { return initDT(dt.eI(), dt.location) } + +// BeginOfSecond retourne la date en début de seconde. +func (dt DateTime) BeginOfSecond() DateTime { return initDT(dt.bS(), dt.location) } + +// EndOfSecond retourne la date en fin de seconde. +func (dt DateTime) EndOfSecond() DateTime { return initDT(dt.eS(), dt.location) } + +// ToTime retourne la date dans le fuseau horaire indiqué. +func (dt DateTime) ToTime() time.Time { return dt.t(dt.location) } + +// In retourne la date dans le fuseau horaire indiqué. +func (dt DateTime) In(l *time.Location) DateTime { + if dt.IsNil() { + return DateTimeNil() + } + + return NewDateTimeFromTime(dt.ToTime().In(l), dt.p()) +} + +// ToTimezone retourne la date dans le fuseau horaire indiqué. +func (dt DateTime) ToTimezone(tz string) DateTime { return dt.In(timezone(tz)) } + +// Location retourne le fuseau horaire. +func (dt DateTime) Location() *time.Location { return dt.location } + +// IsDST retourne vrai si le fuseau horaire est à l’heure d’été. +func (dt DateTime) IsDST() bool { return dt.dst(dt.location) } + +// Timestamp retourne le timestamp en secondes. +func (dt DateTime) TimeStamp() int64 { return dt.ts(dt.location) } + +// TimestampMilli retourne le timestamp en millisecondes. +func (dt DateTime) TimeStampMilli() int64 { return dt.tv(dt.location) } + +// Compare compare 2 dates. +func (dt1 DateTime) Compare(dt2 DateTime) int { return dt1.cmp(dt2.datetime) } + +func (dt1 DateTime) Eq(dt2 DateTime) bool { return dt1.Compare(dt2) == 0 } +func (dt1 DateTime) Ne(dt2 DateTime) bool { return dt1.Compare(dt2) != 0 } +func (dt1 DateTime) Gt(dt2 DateTime) bool { return dt1.Compare(dt2) > 0 } +func (dt1 DateTime) Ge(dt2 DateTime) bool { return dt1.Compare(dt2) >= 0 } +func (dt1 DateTime) Lt(dt2 DateTime) bool { return dt1.Compare(dt2) < 0 } +func (dt1 DateTime) Le(dt2 DateTime) bool { return dt1.Compare(dt2) <= 0 } + +func (dt DateTime) IsNow() bool { return dt.Eq(dt.Now()) } +func (dt DateTime) IsPast() bool { return dt.Lt(dt.Now()) } +func (dt DateTime) IsFuture() bool { return dt.Gt(dt.Now()) } + +// DiffInMills retourne dt1-dt2 en millisecondes. +func (dt1 DateTime) DiffInMillis(dt2 DateTime) int { + return dt1.subV(dt2.datetime, dt1.location, dt2.location) +} + +// DiffInSeconds retourne dt1 - dt2 en secondes. +func (dt1 DateTime) DiffInSeconds(dt2 DateTime) int { + return dt1.subS(dt2.datetime, dt1.location, dt2.location) +} + +// DiffInMinutes retourne dt1 - dt2 en minutes. +func (dt1 DateTime) DiffInMinutes(dt2 DateTime) int { + return dt1.subI(dt2.datetime, dt1.location, dt2.location) +} + +// DiffInHours retourne dt1 - dt2 en heures. +func (dt1 DateTime) DiffInHours(dt2 DateTime) int { + return dt1.subH(dt2.datetime, dt1.location, dt2.location) +} + +// DiffInDays retourne dt1 - dt2 en jours. +func (dt1 DateTime) DiffInDays(dt2 DateTime) int { + return dt1.subD(dt2.datetime, dt1.location, dt2.location) +} + +// DiffInWeeks retourne dt1 - dt2 en semaines. +func (dt1 DateTime) DiffInWeeks(dt2 DateTime) int { + return dt1.subW(dt2.datetime, dt1.location, dt2.location) +} + +// DiffInMonths retourne dt1 - dt2 en mois. +func (dt1 DateTime) DiffInMonths(dt2 DateTime) int { return dt1.subM(dt2.datetime) } + +// DiffInYears retourn dt1 - dt2 en années. +func (dt1 DateTime) DiffInYears(dt2 DateTime) int { return dt1.subY(dt2.datetime) } + +// Format retourne une représentation de la date au format spécifié. +func (dt DateTime) Format(f string) string { return dt.f(f, dt.location) } + +// String retourne une représentation de la date au format 'Y-m-d H:iT', +// 'Y-m-d H:i:sT' ou 'Y-m-d H:i:s.vT' suivant la précision. +func (dt DateTime) String() string { return dt.str(dt.location) } diff --git a/datetime/duration.go b/datetime/duration.go index 365bc8f..c6687a0 100644 --- a/datetime/duration.go +++ b/datetime/duration.go @@ -1,72 +1,55 @@ package datetime import ( - "math" - "time" + "fmt" ) -// 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() +// Unit est une unité de durée. +type Unit uint - 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++ +// Duration est une durée entre deux repères de temps. +type Duration int + +// NewDuration retourne une durée à partir d’une valeur et d’une unité. +func NewDuration(value int, unit Unit) Duration { + if unit == NoUnit { + return DurationNil } - return int64(dy) + return (Duration(value) << bitsUnit) | Duration(unit) } -// DiffInMonths retourne (t1 - t2) exprimé en mois. -func DiffInMonths(t1, t2 time.Time) int64 { - y1, m1, d1 := t1.Date() - y2, m2, d2 := t2.Date() +// Value retourne la valeur de la durée, sans l’unité. +func (d Duration) Value() int { return int(d >> bitsUnit) } - 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) +// Unit retourne l’unité de durée. +func (d Duration) Unit() Unit { return Unit(d & maskUnit) } + +// Duration retourne la valeur et l’unité de la durée. +func (d Duration) Duration() (int, Unit) { return d.Value(), d.Unit() } + +// IsNil retourne vrai si la durée ne représente pas une durée valide. +func (d Duration) IsNil() bool { return d.Unit() == NoUnit } + +// IsClock retourne vrai si la durée est dans une unité de temps sur 24h. +func (d Duration) IsClock() bool { u := d.Unit(); return u > NoUnit && u < Day } + +// IsDate retourne vrai si la durée est dans une unité de date. +func (d Duration) IsDate() bool { return d.Unit() > Hour } + +// Abs retourne la durée en valeur absolue. +func (d Duration) Abs() Duration { return NewDuration(abs(d.Value()), d.Unit()) } + +// Neg retourne l’inverse négatif de la durée. +func (d Duration) Neg() Duration { return NewDuration(-d.Value(), d.Unit()) } + +// String retourne la représentation textuelle de la durée. +func (d Duration) String() string { + if d.IsNil() { + return "-" } - 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() + v, u := d.Value(), d.Unit() + + return fmt.Sprintf("%d%c", v, unitToByte[u]) } diff --git a/datetime/format.go b/datetime/format.go index db9b307..10eb2fe 100644 --- a/datetime/format.go +++ b/datetime/format.go @@ -6,46 +6,13 @@ import ( "time" ) -func abs[N ~int | ~int64](e N) N { - if e < 0 { - return -e - } - return e -} +func gmtDiff(t time.Time) (hour, minute int) { + g := toTZ(t, "GMT") + d0, h0, m0 := t.Day(), t.Hour(), t.Minute() + d1, h1, m1 := g.Day(), g.Hour(), g.Minute() -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 + m := (d0-d1)*MinutePerDay + (h0-h1)*MinutePerHour + (m0 - m1) + return m / MinutePerHour, m % MillisecondPerHour } // Jour @@ -68,32 +35,27 @@ 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())) } +func format_t(t time.Time) string { return fmt.Sprintf("%d", monthLen(t.Year(), uint(t.Month())+1)) } // 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()) { + if bissextil(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 { return amPmLow[t.Hour() < 12] } +func format_A(t time.Time) string { return amPmUp[t.Hour() < 12] } +func format_B(t time.Time) string { + h, m, s := t.Clock() + n := t.Nanosecond() / NanosecondPerMillisecond + + return fmt.Sprintf("%03d", sit(uint(h), uint(m), uint(s), uint(n))) } -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 { @@ -112,8 +74,12 @@ func format_h(t time.Time) string { 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) } +func format_v(t time.Time) string { + return fmt.Sprintf("%03d", t.Nanosecond()/NanosecondPerMillisecond) +} +func format_u(t time.Time) string { + return fmt.Sprintf("%06d", t.Nanosecond()/NanosecondPerMicrosecond) +} // Fuseau horaire func format_T(t time.Time) string { @@ -121,12 +87,12 @@ func format_T(t time.Time) string { if !strings.Contains(name, "/") { return name } - diff := gmtDiff(t) - h, m := diff/Hour, (diff%Hour)/Minute + h, m := gmtDiff(t) s := "+" if h < 0 { s = "-" } + if m == 0 { return fmt.Sprintf("%s%02d", s, abs(h)) } @@ -140,33 +106,37 @@ func format_I(t time.Time) string { return "0" } func format_O(t time.Time) string { - diff := gmtDiff(t) - h, m := diff/Hour, (diff%Hour)/Minute + h, m := gmtDiff(t) 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 + h, m := gmtDiff(t) 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 := gmtDiff(t) + diff := h*MinutePerHour + m 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) + h, m := gmtDiff(t) + diff := h*SecondPerHour + m*SecondPerMinute + + return fmt.Sprintf("%d", diff) } // Date et heure complète @@ -180,11 +150,7 @@ func format_r(t time.Time) string { } 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 { +func formatT(t time.Time, format string) string { var buffer strings.Builder runes := []rune(format) diff --git a/datetime/informations.go b/datetime/informations.go deleted file mode 100644 index 335678b..0000000 --- a/datetime/informations.go +++ /dev/null @@ -1,80 +0,0 @@ -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 l’anné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 l’anné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 l’année. -func IsEndOfYear(t time.Time) bool { return IsEndOfMonth(t) && t.Month() == time.December } - -// IsToday retourne vrai si la date est située aujourd’hui. -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) -} diff --git a/datetime/operations.go b/datetime/operations.go deleted file mode 100644 index d481cd7..0000000 --- a/datetime/operations.go +++ /dev/null @@ -1,266 +0,0 @@ -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 l’anné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 l’heure 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 l’heure 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 l’anné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 l’heure en cours. -func BeginOfHour(t time.Time) time.Time { return SetMinute(t, 0, 0, 0) } - -// EndOfHour retourne la date à la fin de l’heure 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 l’année en cours. -func BeginOfYear(t time.Time) time.Time { return BeginOfDay(SetMonth(t, 1, 1)) } - -// EndOfYear retourne la date à la fin de l’année en cours. -func EndOfYear(t time.Time) time.Time { return EndOfDay(SetMonth(t, 12, 31)) } diff --git a/datetime/parse.go b/datetime/parse.go index e0d552b..a68e745 100644 --- a/datetime/parse.go +++ b/datetime/parse.go @@ -1,23 +1,25 @@ package datetime import ( - "fmt" "strings" "time" . "gitea.zaclys.com/bvaudour/gob/option" ) +func isValueZero(e string) bool { + return e == "" || e == "0" || e == "0000-00-00 00:00:00" || e == "0000-00-00" || e == "00:00:00" +} + 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 + case '\\': // Indique que le caractère suivant ne doit pas être parsé buffer.WriteRune(runes[i+1]) i++ continue @@ -26,70 +28,43 @@ func format2layout(f string) string { } } } - 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])) - } +func parseInLocation(e, f string, tz ...string) (t Option[time.Time]) { + l := timezone(tz...) + if tt, err := time.ParseInLocation(f, e, l); err == nil { + t = Some(tt) } - - t, err := time.ParseInLocation(layout, value, location) - if err == nil { - return Ok(t) - } - return Err[time.Time](fmt.Errorf(errInvalidValue, value)) + return } -// Guess tente de parser la chaîne de caractères en date en essayant de deviner le format. -// Si le fuseau horaire n’est 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...) +func parseFromLayout(e, f string, tz ...string) (t Option[time.Time]) { + if isValueZero(e) { + return } + return parseInLocation(e, f, tz...) +} - switch value { +func guess(e string, tz ...string) (t Option[time.Time]) { + if isValueZero(e) { + return + } + switch e { case "now": - return Now(tz...) + return Some(now(tz...)) case "yesterday": - return Yesterday(tz...) + return Some(yesterday(tz...)) case "tomorrow": - return Tomorrow(tz...) + return Some(tomorrow(tz...)) } - - if len(tz) > 0 { - if _, err := time.LoadLocation(tz[0]); err != nil { - return Err[time.Time](fmt.Errorf(errInvalidTZ, tz[0])) + for _, l := range layouts { + if t = parseInLocation(e, l, tz...); t.IsDefined() { + break } } - - for _, layout := range layouts { - t := parseInLocation(layout, value, tz...) - if t.IsOk() { - return t - } - } - - return Err[time.Time](fmt.Errorf(errInvalidValue, value)) + return } - -// ParseFromLayout parse la chaîne de caractères en date à partir du layout (façon Go) fourni. -// Si le fuseau horaire n’est 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 n’est 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...) +func parse(e, f string, tz ...string) Option[time.Time] { + return parseFromLayout(e, format2layout(f), tz...) } diff --git a/datetime/range.go b/datetime/range.go index c8c8b54..0efeefe 100644 --- a/datetime/range.go +++ b/datetime/range.go @@ -1,119 +1,155 @@ 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 +// TimeComparator représente tout type d’indication de temps. +type TimeComparator[E any] interface { + Eq(E) bool + Ne(E) bool + Gt(E) bool + Ge(E) bool + Lt(E) bool + Le(E) bool + IsNow() bool + IsPast() bool + IsFuture() bool + Now() E } -// 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, +// Min retourne le temps le plus petit. +func Min[C TimeComparator[C]](e C, args ...C) C { + out := e + for _, a := range args { + if a.Lt(out) { + out = a + } } + return out } -// Begin retourne la date de début de la période. -func (r Range) Begin() time.Time { return r.begin } +// Max retourne le temps le plus grand. +func Max[C TimeComparator[C]](e C, args ...C) C { + out := e + for _, a := range args { + if a.Gt(out) { + out = a + } + } + return out +} -// End retourne la date de fin de la période. -func (r Range) End() time.Time { return r.end } +// Range représente une période entre deux bornes temporelles. +type Range[C TimeComparator[C]] struct { + begin, end C +} -// 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) } +// NewRange retourne une période entre begin et end. +// Si begin < end, les bornes sont inversées. +func NewRange[C TimeComparator[C]](begin, end C) Range[C] { + if begin.Gt(end) { + begin, end = end, begin + } -// 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) } + return Range[C]{begin, end} +} -// ContainsDate retourne vrai si t ∈ [begin; end]. -func (r Range) ContainsDate(t time.Time) bool { return Le(r.begin, t) && Ge(r.end, t) } +// Begin retourne le début de la période. +func (r Range[C]) Begin() C { return r.begin } -// ContainsDateStrictBegin retourne vrai si t ∈ ]begin; end]. -func (r Range) ContainsDateStrictBegin(t time.Time) bool { return Lt(r.begin, t) && Ge(r.end, t) } +// End retourne la fin de la période. +func (r Range[C]) End() C { return r.end } -// ContainsDateStrictEnd retourne vrai si t ∈ [begin; end[. -func (r Range) ContainsDateStrictEnd(t time.Time) bool { return Le(r.begin, t) && Gt(r.end, t) } +// Before retourne vrai si e est située avant la période. +func (r Range[C]) Before(e C) bool { return e.Lt(r.begin) } -// ContainsDateStrict retourne vrai si t ∈ ]begin; end[. -func (r Range) ContainsDateStrict(t time.Time) bool { return Lt(r.begin, t) && Gt(r.end, t) } +// After retourne vrai si e est située après la période. +func (r Range[C]) After(e C) bool { return e.Gt(r.end) } -// IsFuture retourne vrai si la période est située dans le futur. -func (r Range) IsFuture() bool { return r.AfterDate(time.Now()) } +// Contains retourne vrai si e ∈ [begin;end]. +func (r Range[C]) Contains(e C) bool { return e.Ge(r.begin) && e.Le(r.end) } -// IsPast retourne vrai si la période est située dans le passé. -func (r Range) IsPast() bool { return r.BeforeDate(time.Now()) } +// ContainsStrictLeft retourne vrai si e ∈ ]begin;end]. +func (r Range[C]) ContainsStrictLeft(e C) bool { return e.Gt(r.begin) && e.Le(r.end) } + +// ContainsStrictRight retourne vrai si e ∈ [begin;end[. +func (r Range[C]) ContainsStrictRight(e C) bool { return e.Ge(r.begin) && e.Lt(r.end) } + +// ContainsStrict retourne vrai si e ∈ ]begin;end[. +func (r Range[C]) ContainsStrict(e C) bool { return e.Gt(r.begin) && e.Lt(r.end) } // IsNow retourne vrai si la période est en cours. -func (r Range) IsNow() bool { return r.ContainsDate(time.Now()) } +func (r Range[C]) IsNow() bool { return r.Contains(r.begin.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) } +// IsPast retourne vrai si la période est située dans le passé. +func (r Range[C]) IsPast() bool { return r.Before(r.begin.Now()) } -// 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) } +// IsFuture retourne vrai la période est située dans le futur. +func (r Range[C]) IsFuture() bool { return r.After(r.begin.Now()) } -// 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) } +// BeforeRange retourne vrai si r1 est terminée avant que r2 commence. +func (r1 Range[C]) BeforeRange(r2 Range[C]) bool { return r1.end.Le(r2.begin) } -// In retourne vrai si r est complètement inclus dans r2. -func (r Range) In(r2 Range) bool { return r2.Contains(r) } +// AfterRange retourne vrai si r2 est terminée avec que r1 commence. +func (r1 Range[C]) AfterRange(r2 Range[C]) bool { return r2.BeforeRange(r1) } -// 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) } +// ContainsRange retourne vrai si r2 est intégralement comprise dans r1. +func (r1 Range[C]) ContainsRange(r2 Range[C]) bool { + return r1.begin.Le(r2.begin) && r1.end.Ge(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) } +// InRange retourne vrai si r2 comprend intégralement r1. +func (r1 Range[C]) InRange(r2 Range[C]) bool { return r2.ContainsRange(r1) } -// 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))) +// Excludes retourne vrai si r1 et r2 ne se chevauchent pas. +func (r1 Range[C]) Excludes(r2 Range[C]) bool { + return r1.end.Le(r2.begin) || r1.begin.Ge(r2.end) +} + +// Overlaps retourne vrai si r1 et r2 se chevauchent. +func (r1 Range[C]) Overlaps(r2 Range[C]) bool { + return r1.begin.Lt(r2.end) && r1.end.Gt(r2.begin) +} + +// Intersection retourne la période commune entre deux périodes. +func (r1 Range[C]) Intersection(r2 Range[C]) (result Option[Range[C]]) { + if r1.Overlaps(r2) { + result = Some(NewRange(Max(r1.begin, r2.begin), Min(r1.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))) +// Joins retourne la plus grande période contiguë formée par deux périodes. +func (r1 Range[C]) Joins(r2 Range[C]) (result Option[Range[C]]) { + if r1.begin.Le(r2.end) && r1.end.Ge(r2.begin) { + result = Some(NewRange(Min(r1.begin, r2.begin), Max(r1.end, r2.end))) } return } -// Diff retourne l’ensemble 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) +// Diff retourne la liste des périodes qui ne se chevauchent pas. +func (r1 Range[C]) Diff(r2 Range[C]) (result []Range[C]) { + if r1.begin.Ne(r2.begin) { + begin := Min(r1.begin, r2.begin) + var end C + if begin.Eq(r1.begin) { + end = Min(r1.end, r2.begin) } else { - end = Min(r2.end, r.begin) + end = Min(r2.end, r1.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) + if r1.end.Ne(r2.end) { + end := Max(r1.end, r2.end) + var begin C + if end.Eq(r1.end) { + begin = Max(r1.begin, r2.end) } else { - begin = Max(r2.begin, r.end) + begin = Max(r2.begin, r1.end) } result = append(result, NewRange(begin, end)) } diff --git a/datetime/util.go b/datetime/util.go new file mode 100644 index 0000000..e858648 --- /dev/null +++ b/datetime/util.go @@ -0,0 +1,133 @@ +package datetime + +import ( + "time" +) + +type integer interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +type uinteger interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 +} + +type float interface { + ~float32 | ~float64 +} + +type number interface { + integer | uinteger | float +} + +func cmp[N number](n1, n2 N) int { + switch { + case n1 < n2: + return -1 + case n1 == n2: + return 0 + default: + return 1 + } +} +func abs[N integer | float](n N) N { + if n < 0 { + return -n + } + + return n +} + +func spl[N integer | uinteger](e []N, n int) (ed, ec []N) { + ed = e + if len(e) > n { + ed, ec = e[:n], e[n:] + } + return +} + +func bissextil(y int) bool { return y%4 == 0 && !(y%100 == 0 && y%400 != 0) } +func monthLen(y int, m uint) uint { + switch m { + case February: + if bissextil(y) { + return 29 + } + return 28 + case April, June, September, November: + return 30 + case January, March, May, July, August, October, December: + return 31 + default: + return 0 + } +} +func yearLen(y int) uint { + if bissextil(y) { + return 366 + } + return 365 +} + +func sit(h, i uint, args ...uint) float64 { + var s, v uint + if len(args) > 0 { + s = args[0] + if len(args) > 1 { + v = args[1] + } + } + v += s*MillisecondPerSecond + i*MillisecondPerMinute + h*MillisecondPerHour + return float64(v) * float64(BeatPerDay) / float64(MillisecondPerDay) +} + +func timezone(tz ...string) *time.Location { + if len(tz) > 0 { + if l, err := time.LoadLocation(tz[0]); err == nil { + return l + } + } + return DefaultTZ +} +func toTZ(t time.Time, tz ...string) time.Time { return t.In(timezone(tz...)) } +func now(tz ...string) time.Time { return toTZ(time.Now(), tz...) } +func yesterday(tz ...string) time.Time { return now(tz...).AddDate(0, 0, -1) } +func tomorrow(tz ...string) time.Time { return now(tz...).AddDate(0, 0, 1) } + +func fromDT(y int, m, d, h, i, s, v uint, l *time.Location) time.Time { + return time.Date(y, time.Month(m-1), int(d), int(h), int(i), int(s), int(d*NanosecondPerMillisecond), l) +} +func fromD(y int, m, d uint, l *time.Location) time.Time { return fromDT(y, m, d, 0, 0, 0, 0, l) } +func fromT(h, i, s, v uint, l *time.Location) time.Time { + t := now().In(l) + y, m, d := t.Year(), uint(t.Month()-1), uint(t.Day()) + return fromDT(y, m, d, h, i, s, v, l) +} + +// Clock +func formatC(h, i uint, args ...uint) (uint, uint, uint, uint, Precision) { + l := len(args) + p, s, v := PrecisionMinute-Precision(l), uint(0), uint(0) + if l > 0 { + s = args[0] + if l > 1 { + v = args[1] + } + } + return h, i, s, v, p +} +func ms(h, i, s, n uint) uint { + return h*MillisecondPerHour + i*MillisecondPerMinute + s*MillisecondPerSecond + n +} + +// Date +func validM(m uint) bool { return m >= January && m <= December } +func validD(y int, m, d uint) bool { return validM(m) && d > 0 && d < monthLen(y, m) } +func getD[N integer | uinteger](d date, t string) N { return N((d & maskDate[t]) >> shiftDate[t]) } +func setD[N integer | uinteger](e N, t string) date { return date(e<