package datetime import ( "fmt" . "gitea.zaclys.com/bvaudour/gob/option" ) // 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 String() string IsNil() bool } // 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 } // 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 } func minB[C TimeComparator[C]](e1, e2 C) C { if e1.IsNil() { return e1 } else if e2.IsNil() { return e2 } return Min(e1, e2) } func maxB[C TimeComparator[C]](e1, e2 C) C { if e1.IsNil() { return e2 } else if e2.IsNil() { return e1 } return Max(e1, e2) } func minE[C TimeComparator[C]](e1, e2 C) C { if e1.IsNil() { return e2 } else if e2.IsNil() { return e1 } return Min(e1, e2) } func maxE[C TimeComparator[C]](e1, e2 C) C { if e1.IsNil() { return e1 } else if e2.IsNil() { return e2 } return Max(e1, e2) } // Range représente une période entre deux bornes temporelles begin et end. // Si begin est nul, cela représente ]-∞ ; end]. // Se end est nul, cela représent [begin; +∞[. type Range[C TimeComparator[C]] struct { begin, end C } // NewRange retourne une période entre begin et end. // Si begin < end, le programme panique. func NewRange[C TimeComparator[C]](begin, end C) Range[C] { if !begin.IsNil() && !end.IsNil() && begin.Gt(end) { panic("begin should be located in past of end.") } return Range[C]{begin, end} } func gt[C TimeComparator[C]](e C, strict ...bool) func(C) bool { if len(strict) == 0 || !strict[0] { return e.Ge } return e.Ge } func lt[C TimeComparator[C]](e C, strict ...bool) func(C) bool { if len(strict) == 0 || !strict[0] { return e.Le } return e.Le } func (r Range[C]) bE(e C, strict ...bool) bool { return r.end.IsNil() || lt(e, strict...)(r.end) } func (r Range[C]) aE(e C, strict ...bool) bool { return !r.end.IsNil() && gt(e, strict...)(r.end) } func (r Range[C]) bB(e C, strict ...bool) bool { return !r.begin.IsNil() || lt(e, strict...)(r.begin) } func (r Range[C]) aB(e C, strict ...bool) bool { return r.begin.IsNil() || gt(e, strict...)(r.begin) } // Begin retourne le début de la période. func (r Range[C]) Begin() C { return r.begin } // End retourne la fin de la période. func (r Range[C]) End() C { return r.end } // Before retourne vrai si e est située avant la période. func (r Range[C]) Before(e C) bool { return r.bB(e, true) } // After retourne vrai si e est située après la période. func (r Range[C]) After(e C) bool { return r.aE(e, true) } // Contains retourne vrai si e ∈ [begin;end]. func (r Range[C]) Contains(e C) bool { return r.aB(e) && r.bE(e) } // ContainsStrictLeft retourne vrai si e ∈ ]begin;end]. func (r Range[C]) ContainsStrictLeft(e C) bool { return r.aB(e, true) && r.bE(e) } // ContainsStrictRight retourne vrai si e ∈ [begin;end[. func (r Range[C]) ContainsStrictRight(e C) bool { return r.aB(e) && r.bE(e, true) } // ContainsStrict retourne vrai si e ∈ ]begin;end[. func (r Range[C]) ContainsStrict(e C) bool { return r.aB(e, true) && r.bE(e, true) } // IsNow retourne vrai si la période est en cours. func (r Range[C]) IsNow() bool { return r.Contains(r.begin.Now()) } // 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()) } // IsFuture retourne vrai la période est située dans le futur. func (r Range[C]) IsFuture() bool { return r.After(r.begin.Now()) } // BeforeRange retourne vrai si r1 est terminée avant que r2 commence. func (r1 Range[C]) BeforeRange(r2 Range[C]) bool { return !r1.end.IsNil() && r2.bB(r1.end) } // AfterRange retourne vrai si r2 est terminée avec que r1 commence. func (r1 Range[C]) AfterRange(r2 Range[C]) bool { return !r1.begin.IsNil() && r2.aE(r1.begin) } // ContainsRange retourne vrai si r2 est intégralement comprise dans r1. func (r1 Range[C]) ContainsRange(r2 Range[C]) bool { return ((r2.begin.IsNil() && r1.begin.IsNil()) || r1.aB(r2.begin)) && ((r2.end.IsNil() && r1.end.IsNil()) || r1.bE(r2.end)) } // InRange retourne vrai si r2 comprend intégralement r1. func (r1 Range[C]) InRange(r2 Range[C]) bool { return r2.ContainsRange(r1) } // Excludes retourne vrai si r1 et r2 ne se chevauchent pas. func (r1 Range[C]) Excludes(r2 Range[C]) bool { return (!r1.end.IsNil() && r2.bB(r1.end)) || (!r1.begin.IsNil() && r2.aE(r1.begin)) } // Overlaps retourne vrai si r1 et r2 se chevauchent. func (r1 Range[C]) Overlaps(r2 Range[C]) bool { return (r1.begin.IsNil() || r2.bE(r1.begin, true)) && (r1.end.IsNil() || r2.aB(r1.end, true)) } // 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(maxB(r1.begin, r2.begin), minE(r1.end, r2.end))) } return } // 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.IsNil() || r2.bE(r1.begin)) && (r1.end.IsNil() || r2.aB(r1.end)) { result = Some(NewRange(minB(r1.begin, r2.begin), maxE(r1.end, r2.end))) } return } // Diff retourne la liste des périodes qui ne se chevauchent pas. func (r1 Range[C]) Diff(r2 Range[C]) (result []Range[C]) { b1, b2 := minB(r1.begin, r2.begin), maxB(r1.begin, r2.begin) e1, e2 := minE(r1.end, r2.end), maxE(r1.end, r2.end) if b1.Ne(b2) { e := b2 if !e1.IsNil() { e = Min(b2, e1) } result = append(result, NewRange(b1, e)) } if e1.Ne(e2) { b := e1 if !b2.IsNil() { b = Max(b2, e1) } result = append(result, NewRange(b, e2)) } return } // String retourne la représentation textuelle de la période. func (r Range[C]) String() string { b, e := r.begin.String(), r.end.String() if r.begin.IsNil() { b = "-∞" } if r.end.IsNil() { e = "+∞" } return fmt.Sprintf("[%s ; %s]", b, e) }