view pkg/common/time.go @ 3332:c86a8e70b40f

Made time interpolation more precise and added a unit test.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 20 May 2019 16:36:19 +0200
parents 8c4c1b3fd856
children ecb4baa2be1a
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2018, 2019 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
//  * Bernhard E. Reiter <bernhard.reiter@intevation.de>

package common

import (
	"math"
	"time"
)

const (
	// time.RFC3339 equals "simplified ISO format as defined by ECMA-262"
	//   https://tc39.github.io/ecma262/#sec-date-time-string-format
	// and "SHOULD be used in new protocols on the Internet." (RFC section 5.6)
	TimeFormat = time.RFC3339
	DateFormat = "2006-01-02"
)

type ValueRangeKind int

const (
	ValueBelow  ValueRangeKind = -1
	ValueInside ValueRangeKind = 0
	ValueAbove  ValueRangeKind = +1
)

var utc0 = time.Unix(0, 0)

func InterpolateValueByTime(t1 time.Time, m1 float64, t2 time.Time, m2 float64) func(time.Time) (float64, ValueRangeKind) {

	// f(t1) = m1
	// f(t2) = m2
	// m1 = t1*a + b <=> b = m1 - t1*a
	// m2 = t2*a + b
	// m1 - m2 = a*(t1 - t2)
	// a = (m1 - m2)/(t1 - t2) for t1 != t2

	if t1.Equal(t2) {
		return func(t time.Time) (float64, ValueRangeKind) {
			switch {
			case t.Before(t1):
				return 0, ValueBelow
			case t.After(t1):
				return 0, ValueAbove
			default:
				return m1 + (m2-m1)/2, ValueInside
			}
		}
	}
	var min, max time.Time
	if t1.Before(t2) {
		min, max = t1, t2
	} else {
		min, max = t2, t1
	}

	a := (m1 - m2) / t1.Sub(t2).Seconds()
	b := m1 - a*t1.Sub(utc0).Seconds()

	return func(t time.Time) (float64, ValueRangeKind) {
		switch {
		case t.Before(min):
			return 0, ValueBelow
		case t.After(max):
			return 0, ValueAbove
		default:
			return a*t.Sub(utc0).Seconds() + b, ValueInside
		}
	}
}

func InterpolateTimeByValue(t1 time.Time, m1 float64, t2 time.Time, m2 float64) func(float64) (time.Time, ValueRangeKind) {

	// f(m1) = t1
	// f(m2) = t2
	// t1 = m1*a + b <=> b = t1 - m1*a
	// t2 = m2*a + b

	// t1 - t2 = a*(m1 - m2)
	// a = (t1-t2)/(m1 - m2) for m1 != m2

	if m1 == m2 {
		return func(m float64) (time.Time, ValueRangeKind) {
			switch {
			case m < m1:
				return time.Time{}, ValueBelow
			case m > m1:
				return time.Time{}, ValueAbove
			default:
				return t1.Add(t2.Sub(t1) / 2), ValueInside
			}
		}
	}

	min, max := math.Min(m1, m2), math.Max(m1, m2)

	a := t1.Sub(t2).Seconds() / (m1 - m2)
	b := t1.Sub(utc0).Seconds() - m1*a

	return func(m float64) (time.Time, ValueRangeKind) {
		switch {
		case m < min:
			return time.Time{}, ValueBelow
		case m > max:
			return time.Time{}, ValueAbove
		default:
			x := m*a + b
			secs := math.Ceil(x)
			nsecs := math.Ceil((x - secs) * (999999999 + 1))
			return time.Unix(int64(secs), int64(nsecs)), ValueInside
		}
	}
}