go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/lucicfg/duration.go (about)

     1  // Copyright 2019 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package lucicfg
    16  
    17  import (
    18  	"fmt"
    19  	"time"
    20  
    21  	"go.starlark.net/starlark"
    22  	"go.starlark.net/syntax"
    23  )
    24  
    25  var zero = starlark.MakeInt64(0)
    26  
    27  // duration wraps an integer, making it a distinct integer-like type.
    28  type duration struct {
    29  	starlark.Int // milliseconds
    30  }
    31  
    32  // Type returns 'duration', to make the type different from ints.
    33  func (x duration) Type() string {
    34  	return "duration"
    35  }
    36  
    37  // String formats the duration using Go's time.Duration rules.
    38  func (x duration) String() string {
    39  	ms, ok := x.Int64()
    40  	if !ok {
    41  		return "<invalid-duration>" // probably very-very large
    42  	}
    43  	return (time.Duration(ms) * time.Millisecond).String()
    44  }
    45  
    46  // Cmp makes durations comparable by comparing them as integers.
    47  func (x duration) Cmp(y starlark.Value, depth int) (int, error) {
    48  	return x.Int.Cmp(y.(duration).Int, depth)
    49  }
    50  
    51  // Binary implements binary operations between durations and ints.
    52  func (x duration) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
    53  	switch y := y.(type) {
    54  	case starlark.Int:
    55  		switch {
    56  		case op == syntax.STAR:
    57  			return duration{x.Int.Mul(y)}, nil
    58  		case (op == syntax.SLASH || op == syntax.SLASHSLASH) && side == starlark.Left:
    59  			return duration{x.Int.Div(y)}, nil
    60  		}
    61  
    62  	case duration:
    63  		switch {
    64  		case op == syntax.PLUS:
    65  			return duration{x.Int.Add(y.Int)}, nil
    66  		case op == syntax.MINUS && side == starlark.Left:
    67  			return duration{x.Int.Sub(y.Int)}, nil
    68  		case op == syntax.MINUS && side == starlark.Right:
    69  			return duration{y.Int.Sub(x.Int)}, nil
    70  		case (op == syntax.SLASH || op == syntax.SLASHSLASH) && side == starlark.Left:
    71  			return x.Int.Div(y.Int), nil
    72  		case (op == syntax.SLASH || op == syntax.SLASHSLASH) && side == starlark.Right:
    73  			return y.Int.Div(x.Int), nil
    74  		case (op == syntax.PERCENT) && side == starlark.Left:
    75  			return x.Int.Mod(y.Int), nil
    76  		case (op == syntax.PERCENT) && side == starlark.Right:
    77  			return y.Int.Mod(x.Int), nil
    78  		}
    79  	}
    80  
    81  	// All other combinations aren't supported.
    82  	return nil, nil
    83  }
    84  
    85  // Unary implements +-.
    86  func (x duration) Unary(op syntax.Token) (starlark.Value, error) {
    87  	switch op {
    88  	case syntax.PLUS:
    89  		return x, nil
    90  	case syntax.MINUS:
    91  		return duration{zero.Sub(x.Int)}, nil
    92  	}
    93  	return nil, nil
    94  }
    95  
    96  func init() {
    97  	// make_duration(milliseconds) returns a 'duration' value.
    98  	declNative("make_duration", func(call nativeCall) (starlark.Value, error) {
    99  		var ms starlark.Int
   100  		if err := call.unpack(0, &ms); err != nil {
   101  			return nil, err
   102  		}
   103  		return duration{ms}, nil
   104  	})
   105  
   106  	// epoch(layout, value, location) returns int epoch seconds for value parsed as a time per layout in location.
   107  	declNative("epoch", func(call nativeCall) (starlark.Value, error) {
   108  		var layout starlark.String
   109  		var value starlark.String
   110  		var location starlark.String
   111  		if err := call.unpack(3, &layout, &value, &location); err != nil {
   112  			return nil, err
   113  		}
   114  		loc, err := time.LoadLocation(location.GoString())
   115  		if err != nil {
   116  			return nil, fmt.Errorf("time.epoch: %s", err)
   117  		}
   118  		t, err := time.ParseInLocation(layout.GoString(), value.GoString(), loc)
   119  		if err != nil {
   120  			return nil, fmt.Errorf("time.epoch: %s", err)
   121  		}
   122  		return starlark.MakeInt(int(t.UnixNano() / 1000000000)), nil
   123  	})
   124  }