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