...

Source file src/go.starlark.net/lib/time/time.go

Documentation: go.starlark.net/lib/time

     1  // Copyright 2021 The Bazel Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package time provides time-related constants and functions.
     6  package time // import "go.starlark.net/lib/time"
     7  
     8  import (
     9  	"fmt"
    10  	"sort"
    11  	"time"
    12  
    13  	"go.starlark.net/starlark"
    14  	"go.starlark.net/starlarkstruct"
    15  	"go.starlark.net/syntax"
    16  )
    17  
    18  // Module time is a Starlark module of time-related functions and constants.
    19  // The module defines the following functions:
    20  //
    21  //     from_timestamp(sec, nsec) - Converts the given Unix time corresponding to the number of seconds
    22  //                                 and (optionally) nanoseconds since January 1, 1970 UTC into an object
    23  //                                 of type Time. For more details, refer to https://pkg.go.dev/time#Unix.
    24  //
    25  //     is_valid_timezone(loc) - Reports whether loc is a valid time zone name.
    26  //
    27  //     now() - Returns the current local time. Applications may replace this function by a deterministic one.
    28  //
    29  //     parse_duration(d) - Parses the given duration string. For more details, refer to
    30  //                         https://pkg.go.dev/time#ParseDuration.
    31  //
    32  //     parse_time(x, format, location) - Parses the given time string using a specific time format and location.
    33  //                                      The expected arguments are a time string (mandatory), a time format
    34  //                                      (optional, set to RFC3339 by default, e.g. "2021-03-22T23:20:50.52Z")
    35  //                                      and a name of location (optional, set to UTC by default). For more details,
    36  //                                      refer to https://pkg.go.dev/time#Parse and https://pkg.go.dev/time#ParseInLocation.
    37  //
    38  //     time(year, month, day, hour, minute, second, nanosecond, location) - Returns the Time corresponding to
    39  //	                                                                        yyyy-mm-dd hh:mm:ss + nsec nanoseconds
    40  //                                                                          in the appropriate zone for that time
    41  //                                                                          in the given location. All the parameters
    42  //                                                                          are optional.
    43  // The module also defines the following constants:
    44  //
    45  //     nanosecond - A duration representing one nanosecond.
    46  //     microsecond - A duration representing one microsecond.
    47  //     millisecond - A duration representing one millisecond.
    48  //     second - A duration representing one second.
    49  //     minute - A duration representing one minute.
    50  //     hour - A duration representing one hour.
    51  //
    52  var Module = &starlarkstruct.Module{
    53  	Name: "time",
    54  	Members: starlark.StringDict{
    55  		"from_timestamp":    starlark.NewBuiltin("from_timestamp", fromTimestamp),
    56  		"is_valid_timezone": starlark.NewBuiltin("is_valid_timezone", isValidTimezone),
    57  		"now":               starlark.NewBuiltin("now", now),
    58  		"parse_duration":    starlark.NewBuiltin("parse_duration", parseDuration),
    59  		"parse_time":        starlark.NewBuiltin("parse_time", parseTime),
    60  		"time":              starlark.NewBuiltin("time", newTime),
    61  
    62  		"nanosecond":  Duration(time.Nanosecond),
    63  		"microsecond": Duration(time.Microsecond),
    64  		"millisecond": Duration(time.Millisecond),
    65  		"second":      Duration(time.Second),
    66  		"minute":      Duration(time.Minute),
    67  		"hour":        Duration(time.Hour),
    68  	},
    69  }
    70  
    71  // NowFunc is a function that generates the current time. Intentionally exported
    72  // so that it can be overridden, for example by applications that require their
    73  // Starlark scripts to be fully deterministic.
    74  var NowFunc = time.Now
    75  
    76  func parseDuration(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    77  	var d Duration
    78  	err := starlark.UnpackPositionalArgs("parse_duration", args, kwargs, 1, &d)
    79  	return d, err
    80  }
    81  
    82  func isValidTimezone(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    83  	var s string
    84  	if err := starlark.UnpackPositionalArgs("is_valid_timezone", args, kwargs, 1, &s); err != nil {
    85  		return nil, err
    86  	}
    87  	_, err := time.LoadLocation(s)
    88  	return starlark.Bool(err == nil), nil
    89  }
    90  
    91  func parseTime(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    92  	var (
    93  		x        string
    94  		location = "UTC"
    95  		format   = time.RFC3339
    96  	)
    97  	if err := starlark.UnpackArgs("parse_time", args, kwargs, "x", &x, "format?", &format, "location?", &location); err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	if location == "UTC" {
   102  		t, err := time.Parse(format, x)
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  		return Time(t), nil
   107  	}
   108  
   109  	loc, err := time.LoadLocation(location)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	t, err := time.ParseInLocation(format, x, loc)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	return Time(t), nil
   118  }
   119  
   120  func fromTimestamp(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   121  	var (
   122  		sec  int64
   123  		nsec int64 = 0
   124  	)
   125  	if err := starlark.UnpackPositionalArgs("from_timestamp", args, kwargs, 1, &sec, &nsec); err != nil {
   126  		return nil, err
   127  	}
   128  	return Time(time.Unix(sec, nsec)), nil
   129  }
   130  
   131  func now(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   132  	return Time(NowFunc()), nil
   133  }
   134  
   135  // Duration is a Starlark representation of a duration.
   136  type Duration time.Duration
   137  
   138  // Assert at compile time that Duration implements Unpacker.
   139  var _ starlark.Unpacker = (*Duration)(nil)
   140  
   141  // Unpack is a custom argument unpacker
   142  func (d *Duration) Unpack(v starlark.Value) error {
   143  	switch x := v.(type) {
   144  	case Duration:
   145  		*d = x
   146  		return nil
   147  	case starlark.String:
   148  		dur, err := time.ParseDuration(string(x))
   149  		if err != nil {
   150  			return err
   151  		}
   152  
   153  		*d = Duration(dur)
   154  		return nil
   155  	}
   156  
   157  	return fmt.Errorf("got %s, want a duration, string, or int", v.Type())
   158  }
   159  
   160  // String implements the Stringer interface.
   161  func (d Duration) String() string { return time.Duration(d).String() }
   162  
   163  // Type returns a short string describing the value's type.
   164  func (d Duration) Type() string { return "time.duration" }
   165  
   166  // Freeze renders Duration immutable. required by starlark.Value interface
   167  // because duration is already immutable this is a no-op.
   168  func (d Duration) Freeze() {}
   169  
   170  // Hash returns a function of x such that Equals(x, y) => Hash(x) == Hash(y)
   171  // required by starlark.Value interface.
   172  func (d Duration) Hash() (uint32, error) {
   173  	return uint32(d) ^ uint32(int64(d)>>32), nil
   174  }
   175  
   176  // Truth reports whether the duration is non-zero.
   177  func (d Duration) Truth() starlark.Bool { return d != 0 }
   178  
   179  // Attr gets a value for a string attribute, implementing dot expression support
   180  // in starklark. required by starlark.HasAttrs interface.
   181  func (d Duration) Attr(name string) (starlark.Value, error) {
   182  	switch name {
   183  	case "hours":
   184  		return starlark.Float(time.Duration(d).Hours()), nil
   185  	case "minutes":
   186  		return starlark.Float(time.Duration(d).Minutes()), nil
   187  	case "seconds":
   188  		return starlark.Float(time.Duration(d).Seconds()), nil
   189  	case "milliseconds":
   190  		return starlark.MakeInt64(time.Duration(d).Milliseconds()), nil
   191  	case "microseconds":
   192  		return starlark.MakeInt64(time.Duration(d).Microseconds()), nil
   193  	case "nanoseconds":
   194  		return starlark.MakeInt64(time.Duration(d).Nanoseconds()), nil
   195  	}
   196  	return nil, fmt.Errorf("unrecognized %s attribute %q", d.Type(), name)
   197  }
   198  
   199  // AttrNames lists available dot expression strings. required by
   200  // starlark.HasAttrs interface.
   201  func (d Duration) AttrNames() []string {
   202  	return []string{
   203  		"hours",
   204  		"minutes",
   205  		"seconds",
   206  		"milliseconds",
   207  		"microseconds",
   208  		"nanoseconds",
   209  	}
   210  }
   211  
   212  // Cmp implements comparison of two Duration values. required by
   213  // starlark.TotallyOrdered interface.
   214  func (d Duration) Cmp(v starlark.Value, depth int) (int, error) {
   215  	if x, y := d, v.(Duration); x < y {
   216  		return -1, nil
   217  	} else if x > y {
   218  		return 1, nil
   219  	}
   220  	return 0, nil
   221  }
   222  
   223  // Binary implements binary operators, which satisfies the starlark.HasBinary
   224  // interface. operators:
   225  //    duration + duration = duration
   226  //    duration + time = time
   227  //    duration - duration = duration
   228  //    duration / duration = float
   229  //    duration / int = duration
   230  //    duration / float = duration
   231  //    duration // duration = int
   232  //    duration * int = duration
   233  func (d Duration) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
   234  	x := time.Duration(d)
   235  
   236  	switch op {
   237  	case syntax.PLUS:
   238  		switch y := y.(type) {
   239  		case Duration:
   240  			return Duration(x + time.Duration(y)), nil
   241  		case Time:
   242  			return Time(time.Time(y).Add(x)), nil
   243  		}
   244  
   245  	case syntax.MINUS:
   246  		switch y := y.(type) {
   247  		case Duration:
   248  			return Duration(x - time.Duration(y)), nil
   249  		}
   250  
   251  	case syntax.SLASH:
   252  		switch y := y.(type) {
   253  		case Duration:
   254  			if y == 0 {
   255  				return nil, fmt.Errorf("%s division by zero", d.Type())
   256  			}
   257  			return starlark.Float(x.Nanoseconds()) / starlark.Float(time.Duration(y).Nanoseconds()), nil
   258  		case starlark.Int:
   259  			if side == starlark.Right {
   260  				return nil, fmt.Errorf("unsupported operation")
   261  			}
   262  			i, ok := y.Int64()
   263  			if !ok {
   264  				return nil, fmt.Errorf("int value out of range (want signed 64-bit value)")
   265  			}
   266  			if i == 0 {
   267  				return nil, fmt.Errorf("%s division by zero", d.Type())
   268  			}
   269  			return d / Duration(i), nil
   270  		case starlark.Float:
   271  			f := float64(y)
   272  			if f == 0 {
   273  				return nil, fmt.Errorf("%s division by zero", d.Type())
   274  			}
   275  			return Duration(float64(x.Nanoseconds()) / f), nil
   276  		}
   277  
   278  	case syntax.SLASHSLASH:
   279  		switch y := y.(type) {
   280  		case Duration:
   281  			if y == 0 {
   282  				return nil, fmt.Errorf("%s division by zero", d.Type())
   283  			}
   284  			return starlark.MakeInt64(x.Nanoseconds() / time.Duration(y).Nanoseconds()), nil
   285  		}
   286  
   287  	case syntax.STAR:
   288  		switch y := y.(type) {
   289  		case starlark.Int:
   290  			i, ok := y.Int64()
   291  			if !ok {
   292  				return nil, fmt.Errorf("int value out of range (want signed 64-bit value)")
   293  			}
   294  			return d * Duration(i), nil
   295  		}
   296  	}
   297  
   298  	return nil, nil
   299  }
   300  
   301  // Time is a Starlark representation of a moment in time.
   302  type Time time.Time
   303  
   304  func newTime(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   305  	var (
   306  		year, month, day, hour, min, sec, nsec int
   307  		loc                                    string
   308  	)
   309  	if err := starlark.UnpackArgs("time", args, kwargs,
   310  		"year?", &year,
   311  		"month?", &month,
   312  		"day?", &day,
   313  		"hour?", &hour,
   314  		"minute?", &min,
   315  		"second?", &sec,
   316  		"nanosecond?", &nsec,
   317  		"location?", &loc,
   318  	); err != nil {
   319  		return nil, err
   320  	}
   321  	if len(args) > 0 {
   322  		return nil, fmt.Errorf("time: unexpected positional arguments")
   323  	}
   324  	location, err := time.LoadLocation(loc)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  	return Time(time.Date(year, time.Month(month), day, hour, min, sec, nsec, location)), nil
   329  }
   330  
   331  // String returns the time formatted using the format string
   332  //	"2006-01-02 15:04:05.999999999 -0700 MST".
   333  func (t Time) String() string { return time.Time(t).String() }
   334  
   335  // Type returns "time.time".
   336  func (t Time) Type() string { return "time.time" }
   337  
   338  // Freeze renders time immutable. required by starlark.Value interface
   339  // because Time is already immutable this is a no-op.
   340  func (t Time) Freeze() {}
   341  
   342  // Hash returns a function of x such that Equals(x, y) => Hash(x) == Hash(y)
   343  // required by starlark.Value interface.
   344  func (t Time) Hash() (uint32, error) {
   345  	return uint32(time.Time(t).UnixNano()) ^ uint32(int64(time.Time(t).UnixNano())>>32), nil
   346  }
   347  
   348  // Truth returns the truth value of an object required by starlark.Value
   349  // interface.
   350  func (t Time) Truth() starlark.Bool { return !starlark.Bool(time.Time(t).IsZero()) }
   351  
   352  // Attr gets a value for a string attribute, implementing dot expression support
   353  // in starklark. required by starlark.HasAttrs interface.
   354  func (t Time) Attr(name string) (starlark.Value, error) {
   355  	switch name {
   356  	case "year":
   357  		return starlark.MakeInt(time.Time(t).Year()), nil
   358  	case "month":
   359  		return starlark.MakeInt(int(time.Time(t).Month())), nil
   360  	case "day":
   361  		return starlark.MakeInt(time.Time(t).Day()), nil
   362  	case "hour":
   363  		return starlark.MakeInt(time.Time(t).Hour()), nil
   364  	case "minute":
   365  		return starlark.MakeInt(time.Time(t).Minute()), nil
   366  	case "second":
   367  		return starlark.MakeInt(time.Time(t).Second()), nil
   368  	case "nanosecond":
   369  		return starlark.MakeInt(time.Time(t).Nanosecond()), nil
   370  	case "unix":
   371  		return starlark.MakeInt64(time.Time(t).Unix()), nil
   372  	case "unix_nano":
   373  		return starlark.MakeInt64(time.Time(t).UnixNano()), nil
   374  	}
   375  	return builtinAttr(t, name, timeMethods)
   376  }
   377  
   378  // AttrNames lists available dot expression strings for time. required by
   379  // starlark.HasAttrs interface.
   380  func (t Time) AttrNames() []string {
   381  	return append(builtinAttrNames(timeMethods),
   382  		"year",
   383  		"month",
   384  		"day",
   385  		"hour",
   386  		"minute",
   387  		"second",
   388  		"nanosecond",
   389  		"unix",
   390  		"unix_nano",
   391  	)
   392  }
   393  
   394  // Cmp implements comparison of two Time values. Required by
   395  // starlark.TotallyOrdered interface.
   396  func (t Time) Cmp(yV starlark.Value, depth int) (int, error) {
   397  	x := time.Time(t)
   398  	y := time.Time(yV.(Time))
   399  	if x.Before(y) {
   400  		return -1, nil
   401  	} else if x.After(y) {
   402  		return 1, nil
   403  	}
   404  	return 0, nil
   405  }
   406  
   407  // Binary implements binary operators, which satisfies the starlark.HasBinary
   408  // interface
   409  //    time + duration = time
   410  //    time - duration = time
   411  //    time - time = duration
   412  func (t Time) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) {
   413  	x := time.Time(t)
   414  
   415  	switch op {
   416  	case syntax.PLUS:
   417  		switch y := y.(type) {
   418  		case Duration:
   419  			return Time(x.Add(time.Duration(y))), nil
   420  		}
   421  	case syntax.MINUS:
   422  		switch y := y.(type) {
   423  		case Duration:
   424  			return Time(x.Add(time.Duration(-y))), nil
   425  		case Time:
   426  			// time - time = duration
   427  			return Duration(x.Sub(time.Time(y))), nil
   428  		}
   429  	}
   430  
   431  	return nil, nil
   432  }
   433  
   434  var timeMethods = map[string]builtinMethod{
   435  	"in_location": timeIn,
   436  	"format":      timeFormat,
   437  }
   438  
   439  func timeFormat(fnname string, recV starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   440  	var x string
   441  	if err := starlark.UnpackPositionalArgs("format", args, kwargs, 1, &x); err != nil {
   442  		return nil, err
   443  	}
   444  
   445  	recv := time.Time(recV.(Time))
   446  	return starlark.String(recv.Format(x)), nil
   447  }
   448  
   449  func timeIn(fnname string, recV starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   450  	var x string
   451  	if err := starlark.UnpackPositionalArgs("in_location", args, kwargs, 1, &x); err != nil {
   452  		return nil, err
   453  	}
   454  	loc, err := time.LoadLocation(x)
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  
   459  	recv := time.Time(recV.(Time))
   460  	return Time(recv.In(loc)), nil
   461  }
   462  
   463  type builtinMethod func(fnname string, recv starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error)
   464  
   465  func builtinAttr(recv starlark.Value, name string, methods map[string]builtinMethod) (starlark.Value, error) {
   466  	method := methods[name]
   467  	if method == nil {
   468  		return nil, nil // no such method
   469  	}
   470  
   471  	// Allocate a closure over 'method'.
   472  	impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   473  		return method(b.Name(), b.Receiver(), args, kwargs)
   474  	}
   475  	return starlark.NewBuiltin(name, impl).BindReceiver(recv), nil
   476  }
   477  
   478  func builtinAttrNames(methods map[string]builtinMethod) []string {
   479  	names := make([]string, 0, len(methods))
   480  	for name := range methods {
   481  		names = append(names, name)
   482  	}
   483  	sort.Strings(names)
   484  	return names
   485  }
   486  

View as plain text