| // Copyright 2021 The Bazel Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package time provides time-related constants and functions. |
| package time // import "go.starlark.net/lib/time" |
| |
| import ( |
| "fmt" |
| "sort" |
| "time" |
| |
| "go.starlark.net/starlark" |
| "go.starlark.net/starlarkstruct" |
| "go.starlark.net/syntax" |
| ) |
| |
| // Module time is a Starlark module of time-related functions and constants. |
| // The module defines the following functions: |
| // |
| // from_timestamp(sec, nsec) - Converts the given Unix time corresponding to the number of seconds |
| // and (optionally) nanoseconds since January 1, 1970 UTC into an object |
| // of type Time. For more details, refer to https://pkg.go.dev/time#Unix. |
| // |
| // is_valid_timezone(loc) - Reports whether loc is a valid time zone name. |
| // |
| // now() - Returns the current local time. Applications may replace this function by a deterministic one. |
| // |
| // parse_duration(d) - Parses the given duration string. For more details, refer to |
| // https://pkg.go.dev/time#ParseDuration. |
| // |
| // parse_time(x, format, location) - Parses the given time string using a specific time format and location. |
| // The expected arguments are a time string (mandatory), a time format |
| // (optional, set to RFC3339 by default, e.g. "2021-03-22T23:20:50.52Z") |
| // and a name of location (optional, set to UTC by default). For more details, |
| // refer to https://pkg.go.dev/time#Parse and https://pkg.go.dev/time#ParseInLocation. |
| // |
| // time(year, month, day, hour, minute, second, nanosecond, location) - Returns the Time corresponding to |
| // yyyy-mm-dd hh:mm:ss + nsec nanoseconds |
| // in the appropriate zone for that time |
| // in the given location. All the parameters |
| // are optional. |
| // The module also defines the following constants: |
| // |
| // nanosecond - A duration representing one nanosecond. |
| // microsecond - A duration representing one microsecond. |
| // millisecond - A duration representing one millisecond. |
| // second - A duration representing one second. |
| // minute - A duration representing one minute. |
| // hour - A duration representing one hour. |
| // |
| var Module = &starlarkstruct.Module{ |
| Name: "time", |
| Members: starlark.StringDict{ |
| "from_timestamp": starlark.NewBuiltin("from_timestamp", fromTimestamp), |
| "is_valid_timezone": starlark.NewBuiltin("is_valid_timezone", isValidTimezone), |
| "now": starlark.NewBuiltin("now", now), |
| "parse_duration": starlark.NewBuiltin("parse_duration", parseDuration), |
| "parse_time": starlark.NewBuiltin("parse_time", parseTime), |
| "time": starlark.NewBuiltin("time", newTime), |
| |
| "nanosecond": Duration(time.Nanosecond), |
| "microsecond": Duration(time.Microsecond), |
| "millisecond": Duration(time.Millisecond), |
| "second": Duration(time.Second), |
| "minute": Duration(time.Minute), |
| "hour": Duration(time.Hour), |
| }, |
| } |
| |
| // NowFunc is a function that generates the current time. Intentionally exported |
| // so that it can be overridden, for example by applications that require their |
| // Starlark scripts to be fully deterministic. |
| var NowFunc = time.Now |
| |
| func parseDuration(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var d Duration |
| err := starlark.UnpackPositionalArgs("parse_duration", args, kwargs, 1, &d) |
| return d, err |
| } |
| |
| func isValidTimezone(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var s string |
| if err := starlark.UnpackPositionalArgs("is_valid_timezone", args, kwargs, 1, &s); err != nil { |
| return nil, err |
| } |
| _, err := time.LoadLocation(s) |
| return starlark.Bool(err == nil), nil |
| } |
| |
| func parseTime(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var ( |
| x string |
| location = "UTC" |
| format = time.RFC3339 |
| ) |
| if err := starlark.UnpackArgs("parse_time", args, kwargs, "x", &x, "format?", &format, "location?", &location); err != nil { |
| return nil, err |
| } |
| |
| if location == "UTC" { |
| t, err := time.Parse(format, x) |
| if err != nil { |
| return nil, err |
| } |
| return Time(t), nil |
| } |
| |
| loc, err := time.LoadLocation(location) |
| if err != nil { |
| return nil, err |
| } |
| t, err := time.ParseInLocation(format, x, loc) |
| if err != nil { |
| return nil, err |
| } |
| return Time(t), nil |
| } |
| |
| func fromTimestamp(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var ( |
| sec int64 |
| nsec int64 = 0 |
| ) |
| if err := starlark.UnpackPositionalArgs("from_timestamp", args, kwargs, 1, &sec, &nsec); err != nil { |
| return nil, err |
| } |
| return Time(time.Unix(sec, nsec)), nil |
| } |
| |
| func now(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| return Time(NowFunc()), nil |
| } |
| |
| // Duration is a Starlark representation of a duration. |
| type Duration time.Duration |
| |
| // Assert at compile time that Duration implements Unpacker. |
| var _ starlark.Unpacker = (*Duration)(nil) |
| |
| // Unpack is a custom argument unpacker |
| func (d *Duration) Unpack(v starlark.Value) error { |
| switch x := v.(type) { |
| case Duration: |
| *d = x |
| return nil |
| case starlark.String: |
| dur, err := time.ParseDuration(string(x)) |
| if err != nil { |
| return err |
| } |
| |
| *d = Duration(dur) |
| return nil |
| } |
| |
| return fmt.Errorf("got %s, want a duration, string, or int", v.Type()) |
| } |
| |
| // String implements the Stringer interface. |
| func (d Duration) String() string { return time.Duration(d).String() } |
| |
| // Type returns a short string describing the value's type. |
| func (d Duration) Type() string { return "time.duration" } |
| |
| // Freeze renders Duration immutable. required by starlark.Value interface |
| // because duration is already immutable this is a no-op. |
| func (d Duration) Freeze() {} |
| |
| // Hash returns a function of x such that Equals(x, y) => Hash(x) == Hash(y) |
| // required by starlark.Value interface. |
| func (d Duration) Hash() (uint32, error) { |
| return uint32(d) ^ uint32(int64(d)>>32), nil |
| } |
| |
| // Truth reports whether the duration is non-zero. |
| func (d Duration) Truth() starlark.Bool { return d != 0 } |
| |
| // Attr gets a value for a string attribute, implementing dot expression support |
| // in starklark. required by starlark.HasAttrs interface. |
| func (d Duration) Attr(name string) (starlark.Value, error) { |
| switch name { |
| case "hours": |
| return starlark.Float(time.Duration(d).Hours()), nil |
| case "minutes": |
| return starlark.Float(time.Duration(d).Minutes()), nil |
| case "seconds": |
| return starlark.Float(time.Duration(d).Seconds()), nil |
| case "milliseconds": |
| return starlark.MakeInt64(time.Duration(d).Milliseconds()), nil |
| case "microseconds": |
| return starlark.MakeInt64(time.Duration(d).Microseconds()), nil |
| case "nanoseconds": |
| return starlark.MakeInt64(time.Duration(d).Nanoseconds()), nil |
| } |
| return nil, fmt.Errorf("unrecognized %s attribute %q", d.Type(), name) |
| } |
| |
| // AttrNames lists available dot expression strings. required by |
| // starlark.HasAttrs interface. |
| func (d Duration) AttrNames() []string { |
| return []string{ |
| "hours", |
| "minutes", |
| "seconds", |
| "milliseconds", |
| "microseconds", |
| "nanoseconds", |
| } |
| } |
| |
| // Cmp implements comparison of two Duration values. required by |
| // starlark.TotallyOrdered interface. |
| func (d Duration) Cmp(v starlark.Value, depth int) (int, error) { |
| if x, y := d, v.(Duration); x < y { |
| return -1, nil |
| } else if x > y { |
| return 1, nil |
| } |
| return 0, nil |
| } |
| |
| // Binary implements binary operators, which satisfies the starlark.HasBinary |
| // interface. operators: |
| // duration + duration = duration |
| // duration + time = time |
| // duration - duration = duration |
| // duration / duration = float |
| // duration / int = duration |
| // duration / float = duration |
| // duration // duration = int |
| // duration * int = duration |
| func (d Duration) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) { |
| x := time.Duration(d) |
| |
| switch op { |
| case syntax.PLUS: |
| switch y := y.(type) { |
| case Duration: |
| return Duration(x + time.Duration(y)), nil |
| case Time: |
| return Time(time.Time(y).Add(x)), nil |
| } |
| |
| case syntax.MINUS: |
| switch y := y.(type) { |
| case Duration: |
| return Duration(x - time.Duration(y)), nil |
| } |
| |
| case syntax.SLASH: |
| switch y := y.(type) { |
| case Duration: |
| if y == 0 { |
| return nil, fmt.Errorf("%s division by zero", d.Type()) |
| } |
| return starlark.Float(x.Nanoseconds()) / starlark.Float(time.Duration(y).Nanoseconds()), nil |
| case starlark.Int: |
| if side == starlark.Right { |
| return nil, fmt.Errorf("unsupported operation") |
| } |
| i, ok := y.Int64() |
| if !ok { |
| return nil, fmt.Errorf("int value out of range (want signed 64-bit value)") |
| } |
| if i == 0 { |
| return nil, fmt.Errorf("%s division by zero", d.Type()) |
| } |
| return d / Duration(i), nil |
| case starlark.Float: |
| f := float64(y) |
| if f == 0 { |
| return nil, fmt.Errorf("%s division by zero", d.Type()) |
| } |
| return Duration(float64(x.Nanoseconds()) / f), nil |
| } |
| |
| case syntax.SLASHSLASH: |
| switch y := y.(type) { |
| case Duration: |
| if y == 0 { |
| return nil, fmt.Errorf("%s division by zero", d.Type()) |
| } |
| return starlark.MakeInt64(x.Nanoseconds() / time.Duration(y).Nanoseconds()), nil |
| } |
| |
| case syntax.STAR: |
| switch y := y.(type) { |
| case starlark.Int: |
| i, ok := y.Int64() |
| if !ok { |
| return nil, fmt.Errorf("int value out of range (want signed 64-bit value)") |
| } |
| return d * Duration(i), nil |
| } |
| } |
| |
| return nil, nil |
| } |
| |
| // Time is a Starlark representation of a moment in time. |
| type Time time.Time |
| |
| func newTime(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var ( |
| year, month, day, hour, min, sec, nsec int |
| loc string |
| ) |
| if err := starlark.UnpackArgs("time", args, kwargs, |
| "year?", &year, |
| "month?", &month, |
| "day?", &day, |
| "hour?", &hour, |
| "minute?", &min, |
| "second?", &sec, |
| "nanosecond?", &nsec, |
| "location?", &loc, |
| ); err != nil { |
| return nil, err |
| } |
| if len(args) > 0 { |
| return nil, fmt.Errorf("time: unexpected positional arguments") |
| } |
| location, err := time.LoadLocation(loc) |
| if err != nil { |
| return nil, err |
| } |
| return Time(time.Date(year, time.Month(month), day, hour, min, sec, nsec, location)), nil |
| } |
| |
| // String returns the time formatted using the format string |
| // "2006-01-02 15:04:05.999999999 -0700 MST". |
| func (t Time) String() string { return time.Time(t).String() } |
| |
| // Type returns "time.time". |
| func (t Time) Type() string { return "time.time" } |
| |
| // Freeze renders time immutable. required by starlark.Value interface |
| // because Time is already immutable this is a no-op. |
| func (t Time) Freeze() {} |
| |
| // Hash returns a function of x such that Equals(x, y) => Hash(x) == Hash(y) |
| // required by starlark.Value interface. |
| func (t Time) Hash() (uint32, error) { |
| return uint32(time.Time(t).UnixNano()) ^ uint32(int64(time.Time(t).UnixNano())>>32), nil |
| } |
| |
| // Truth returns the truth value of an object required by starlark.Value |
| // interface. |
| func (t Time) Truth() starlark.Bool { return !starlark.Bool(time.Time(t).IsZero()) } |
| |
| // Attr gets a value for a string attribute, implementing dot expression support |
| // in starklark. required by starlark.HasAttrs interface. |
| func (t Time) Attr(name string) (starlark.Value, error) { |
| switch name { |
| case "year": |
| return starlark.MakeInt(time.Time(t).Year()), nil |
| case "month": |
| return starlark.MakeInt(int(time.Time(t).Month())), nil |
| case "day": |
| return starlark.MakeInt(time.Time(t).Day()), nil |
| case "hour": |
| return starlark.MakeInt(time.Time(t).Hour()), nil |
| case "minute": |
| return starlark.MakeInt(time.Time(t).Minute()), nil |
| case "second": |
| return starlark.MakeInt(time.Time(t).Second()), nil |
| case "nanosecond": |
| return starlark.MakeInt(time.Time(t).Nanosecond()), nil |
| case "unix": |
| return starlark.MakeInt64(time.Time(t).Unix()), nil |
| case "unix_nano": |
| return starlark.MakeInt64(time.Time(t).UnixNano()), nil |
| } |
| return builtinAttr(t, name, timeMethods) |
| } |
| |
| // AttrNames lists available dot expression strings for time. required by |
| // starlark.HasAttrs interface. |
| func (t Time) AttrNames() []string { |
| return append(builtinAttrNames(timeMethods), |
| "year", |
| "month", |
| "day", |
| "hour", |
| "minute", |
| "second", |
| "nanosecond", |
| "unix", |
| "unix_nano", |
| ) |
| } |
| |
| // Cmp implements comparison of two Time values. Required by |
| // starlark.TotallyOrdered interface. |
| func (t Time) Cmp(yV starlark.Value, depth int) (int, error) { |
| x := time.Time(t) |
| y := time.Time(yV.(Time)) |
| if x.Before(y) { |
| return -1, nil |
| } else if x.After(y) { |
| return 1, nil |
| } |
| return 0, nil |
| } |
| |
| // Binary implements binary operators, which satisfies the starlark.HasBinary |
| // interface |
| // time + duration = time |
| // time - duration = time |
| // time - time = duration |
| func (t Time) Binary(op syntax.Token, y starlark.Value, side starlark.Side) (starlark.Value, error) { |
| x := time.Time(t) |
| |
| switch op { |
| case syntax.PLUS: |
| switch y := y.(type) { |
| case Duration: |
| return Time(x.Add(time.Duration(y))), nil |
| } |
| case syntax.MINUS: |
| switch y := y.(type) { |
| case Duration: |
| return Time(x.Add(time.Duration(-y))), nil |
| case Time: |
| // time - time = duration |
| return Duration(x.Sub(time.Time(y))), nil |
| } |
| } |
| |
| return nil, nil |
| } |
| |
| var timeMethods = map[string]builtinMethod{ |
| "in_location": timeIn, |
| "format": timeFormat, |
| } |
| |
| func timeFormat(fnname string, recV starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var x string |
| if err := starlark.UnpackPositionalArgs("format", args, kwargs, 1, &x); err != nil { |
| return nil, err |
| } |
| |
| recv := time.Time(recV.(Time)) |
| return starlark.String(recv.Format(x)), nil |
| } |
| |
| func timeIn(fnname string, recV starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| var x string |
| if err := starlark.UnpackPositionalArgs("in_location", args, kwargs, 1, &x); err != nil { |
| return nil, err |
| } |
| loc, err := time.LoadLocation(x) |
| if err != nil { |
| return nil, err |
| } |
| |
| recv := time.Time(recV.(Time)) |
| return Time(recv.In(loc)), nil |
| } |
| |
| type builtinMethod func(fnname string, recv starlark.Value, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) |
| |
| func builtinAttr(recv starlark.Value, name string, methods map[string]builtinMethod) (starlark.Value, error) { |
| method := methods[name] |
| if method == nil { |
| return nil, nil // no such method |
| } |
| |
| // Allocate a closure over 'method'. |
| impl := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { |
| return method(b.Name(), b.Receiver(), args, kwargs) |
| } |
| return starlark.NewBuiltin(name, impl).BindReceiver(recv), nil |
| } |
| |
| func builtinAttrNames(methods map[string]builtinMethod) []string { |
| names := make([]string, 0, len(methods)) |
| for name := range methods { |
| names = append(names, name) |
| } |
| sort.Strings(names) |
| return names |
| } |