Skip to content

Conversation

b5
Copy link
Contributor

@b5 b5 commented Dec 4, 2020

Signed-off-by: b5 sparkle_pony_2000@qri.io

A few notes:

  • I've added this to lib/time based on @alandonovan's feedback from Time and duration module #326
  • we're waiting on a chat with @srebhan about the naming of the constructors vs. parsing functions before this is final
  • We use a tool to extract documentation from this package in doc.go into other formats. I can remove this to conform to other packages, but if this info can stay & be maintained, I'd be deeply appreciated. Keeping documentation to the code itself has made our lives much easier.
  • If we merge this I'll take this back to our contributors on the starlib project & chat about relying on this package upstream. @alandonovan in the past you'd mentioned interest in other packages from the project, now might be the time to go shopping 😉

@google-cla
Copy link

google-cla bot commented Dec 4, 2020

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@b5 b5 mentioned this pull request Dec 4, 2020
@b5
Copy link
Contributor Author

b5 commented Dec 4, 2020

@googlebot I signed it!

@google-cla
Copy link

google-cla bot commented Dec 4, 2020

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

Copy link
Contributor

@alandonovan alandonovan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a first pass. Thanks for contributing!

@srebhan
Copy link

srebhan commented Dec 7, 2020

@b5: Thanks for your quick reaction! I have duration() and time() as "constructors" and use parse_{time,duration}() as parsing function. I think having the "class-name" as constructors is a matter of least-surprise and the the name for the parsing functions are obvious. However, I see that this would crush your current API...
Is there anything I can do to speed this up?

@alandonovan: I'm supporting this PR and will close mine once this one is merged.

@google-cla
Copy link

google-cla bot commented Dec 9, 2020

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@srebhan
Copy link

srebhan commented Dec 16, 2020

@b5 any news on this one? I'm eagerly waiting for time support! ;-P

@b5 b5 force-pushed the feat_time_module branch from f88a7d0 to adf86ee Compare December 18, 2020 17:21
@google-cla
Copy link

google-cla bot commented Dec 18, 2020

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed (or fixed any issues), please reply here with @googlebot I signed it! and we'll verify it.


What to do if you already signed the CLA

Individual signers
Corporate signers

ℹ️ Googlers: Go here for more info.

@b5
Copy link
Contributor Author

b5 commented Dec 18, 2020

hi all,

I still have a little more cleanup to do here, but wanted to get this set of changes up for review. I've made a bunch of adjustments to the Starlark API based on review feedback.

@srebhan, what do you think the signature for the time.time constructor should be? I've currently got it wrapping golang's time.Date, but could also see removing time.from_timestamp and using that instead.

@srebhan
Copy link

srebhan commented Dec 19, 2020

@b5 I had time([year[, month[, day[, hour[, minute[, second[, nanosecond[, location]]]]]]]]) to mimic the python API a bit, but I think a kwargs approach also works. Whatever you prefer to be honest! :-)

lib/time/time.go Outdated
Comment on lines 61 to 69
func parseLocation(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var s string
if err := starlark.UnpackArgs("location", args, kwargs, "s", &s); err != nil {
return nil, err
}
loc, err := time.LoadLocation(s)
if err != nil {
return nil, err
}

return starlark.String(loc.String()), nil
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get the use of this function... You put in a location string and get that string out? Why do you need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this validates system clock time zone locations, users can use it to both confirm the timezone in question is supported by the OS. In the future This could be expanded to return a starlark "location" type that supports timezone conversion

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we then please name the function validate_location() or similar and return a bool!? Otherwise it is very confusing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starlark's error-handling theory is not well developed. It doesn't have exceptions, and I don't think we really want to add them, but this does mean that callers need to "look before they leap", or need a mode in which they succeed but return an error value (e.g. None or perhaps a string) instead of failing.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alandonovan right, but parseLocation suggests that we somehow convert the given location to something else. Instead we simply return the same string in case of success or nil otherwise. Furthermore, the returned error is totally redundant with checking the string.... My suggestion is to simplify this to return a bool indicating if this is a valid location. So instead of

l, err := parseLocation(..., loc)
if err != nil {...}

you can call

if isValidLocation(..., loc) {...}

lib/time/time.go Outdated
return starlark.None, err
}

SleepFunc(time.Duration(dur))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to check SleepFunc being not nil otherwise we might segfault here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think that if users set SleepFunc to nil and try to call SleepFunc the program should panic.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I don't like panicking on this level. Imagine you expose setting the sleep function to the user script (for whatever reason). Now your whole program will explode without any way for the programmer to intervene. IMO we should either throw an error here or use the default sleep function in presence of SleepFunc being nil.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @b5 that it's fine to panic here, as it's an application programming error.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, but this means we never should expose setting SleepFunc to the interpreter and thus to the user. To be honest I would make SleepFunc == nil the default and then run time.Sleep to circumvent all of this, but it's not my decision. :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine to insist that the value be non-nil, and simply panic if someone violates that. Users: don't do that.

}

func now(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
return Time(NowFunc()), nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to check NowFunc for not being nil to avoid a segfault here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI NowFunc cannot return nil because it is actually time.Now

Copy link
Contributor Author

@b5 b5 Mar 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be nil if users override the package variable, setting time.NowFunc = nil. In discussion above Alan & I have agreed it's the package consumer's responsibility to not do that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@b5 thanks for the info, if I get it right, it is acceptable to let if fail then right (leave it as it is now)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep! I think it should be left as is

@essobedo
Copy link
Contributor

@b5 I've just removed it

@essobedo
Copy link
Contributor

FYI I renamed the package to starlarktime to remain consistent with other modules like the json module

@adonovan
Copy link
Collaborator

adonovan commented Feb 20, 2021 via email

assert.eq(10*60*60*1000000, d2.microseconds)
assert.eq(10*60*60*1000000000, d2.nanoseconds)

# duration == duration = boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove = boolean here and below

return Time(x.Add(time.Duration(-y))), nil
case Time:
// time - time = duration
if side == starlark.Left {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition is (I think) always true, because the left operand is always tested for HasBinary first, so the "else" case is unreachable. Simplify.

return names
}

// threeway interprets a three-way comparison value cmp (-1, 0, +1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: in hindsight, the API design that required threeway was a mistake. Originally it was intended to support floating point NaNs according to IEEE 754, which requires that x < NaN, Nan < x and NaN == x are all false, so you can't represent the result of a "float op float" comparison using a three-valued result (smaller, bigger, equal): one must also know the query operator. But we changed things recently so that floats are totally ordered (NaN > +Inf), otherwise you can't sort floats, so CompareSameType could in principle be simplified to:

CompareSameType(v Value, depth int) (signum int, err error)

@@ -0,0 +1,507 @@
package starlarktime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happened to the doc comment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alandonovan it has been removed since it was not in a standard format and there is no other module with this kind of documentation

@essobedo
Copy link
Contributor

@alandonovan Remarks applied, please check again

@ssoroka
Copy link

ssoroka commented Mar 3, 2021

Any update on this? :)

@essobedo
Copy link
Contributor

essobedo commented Mar 4, 2021

@b5 I cannot resolve conversations, could you please check if it is possible to give me the necessary rights?

@essobedo
Copy link
Contributor

essobedo commented Mar 4, 2021

@adonovan @alandonovan We really need this feature for https://github.com/influxdata/telegraf, so it is important for us to finish it and have it in Startlark. Can you still do the review or shall I ask someone else? and if so, who is the new project lead now please? (cc @ssoroka @srebhan)

Copy link
Collaborator

@adonovan adonovan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay. I'll do what I can to help you get this committed today.

lib/time/time.go Outdated
"parse_location": starlark.NewBuiltin("parse_location", parseLocation),
"now": starlark.NewBuiltin("now", now),
"time": starlark.NewBuiltin("time", newTime),
"parse_time": starlark.NewBuiltin("time", parseTime),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewBuiltin("parse_time")

lib/time/time.go Outdated

func parseLocation(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var s string
if err := starlark.UnpackPositionalArgs("location", args, kwargs, 1, &s); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"parse_location" (for consistency)

Though I agree with earlier remarks that "parse" is the wrong verb: it's really "validate" or "canonicalize". Where's the Starlark documentation for this function? Does it ever return a value different from its argument? The Go documentation for LoadLocation suggests the answer is: yes, but only when s is "" (an alias for "UTC"), or "Local", in which case it uses local time: but this doesn't seem like a concept we want to expose to Starlark since it breaks determinism.

I would be inclined to disallow both of these, which would then allow us to make this function be: is_valid_timezone(string) bool.

In any case, we need to spell out the intended behavior of all the functions in this file.

lib/time/time.go Outdated
case syntax.SLASH:
switch y := y.(type) {
case Duration:
if int64(y) == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if y == 0

lib/time/time.go Outdated
if int64(y) == 0 {
return nil, fmt.Errorf("%s division by zero", d.Type())
}
return starlark.Float(float64(x.Nanoseconds()) / float64(time.Duration(y).Nanoseconds())), nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

starlark.Float(x.Nanoseconds()) / starlark.Float(time.Duration(y).Nanoseconds())

lib/time/time.go Outdated
if !ok {
return nil, fmt.Errorf("int value out of range (want signed 64-bit value)")
}
if int64(i) == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if i == 0

lib/time/time.go Outdated
if int64(i) == 0 {
return nil, fmt.Errorf("%s division by zero", d.Type())
}
return Duration(x.Nanoseconds() / i), nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simpler: x / Duration(i)

(This is what you used for //, and the behavior is identical.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adonovan you meant d / Duration(i), right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, quite right.

lib/time/time.go Outdated
func (d Duration) CompareSameType(op syntax.Token, v starlark.Value, depth int) (bool, error) {
x := time.Duration(d)
y := time.Duration(v.(Duration))
switch op {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return threeway(op, x, y), nil

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adonovan I'm not sure, I get what you expect here. You would to see the content of the switch block moved into a new private function?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and that function already exists below: it's threeway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adonovan I probably miss something obvious but the current function has only 2 parameters while you are trying to call a function with 3 parameters. At best the second parameter could be somehow x-y but we could get an overflow.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, you're right; I'm not really using my brain. What I mean was this, which is less concise that I had imagined:

	cmp := 0
	if x, y := d, v.(Duration); x < y {
		cmp = -1
	} else if x > y {
		cmp = 1
	}
	return threeway(op, cmp)

@essobedo
Copy link
Contributor

essobedo commented Mar 4, 2021

@adonovan Thx for the feedbacks, it is hard to follow it properly since I have no way to resolve the discussions but I think that I addressed your feedbacks, could you please check it again?

Copy link
Collaborator

@adonovan adonovan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for your patience!

lib/time/time.go Outdated
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
func parseDuration(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var d Duration
err := starlark.UnpackPositionalArgs("duration", args, kwargs, 1, &d)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"parse_duration"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alandonovan indeed, fixed

@b5
Copy link
Contributor Author

b5 commented Mar 5, 2021

quick think you to everyone who got this off the ground! @srebhan for motivating this in the first place, and @essobedo for putting in a huge amount of work to implement @adonovan's careful review 😄

@srebhan
Copy link

srebhan commented Mar 5, 2021

@b5 I also want to thank you for your continuous effort in bringing this forward. I know it's not easy to find the time. Special thanks to @essobedo for investing so much and pushing this over the finishing line!

@essobedo
Copy link
Contributor

essobedo commented Mar 5, 2021

@adonovan Thanks for the impressive review, can you still somehow merge ? or do you at least know who can merge?

@adonovan adonovan merged commit 6a590ae into google:master Mar 5, 2021
@b5 b5 deleted the feat_time_module branch March 5, 2021 15:11
b5 added a commit to qri-io/starlib that referenced this pull request Apr 8, 2021
…am versions

After a *large* community effort, we've successfully moved two packages from
starlib into the "real" standard library in go-starlark:
* math: google/starlark-go#357
* time: google/starlark-go#327

Both of these packages have had a great deal of vetting & improvement in the
process of migrating. While this does introduce many breaking changes for both
packages, both packages have better stability & performance characteristics.

Moving forward, we'll try to follow the pattern of developing & testing packages
here, and for those deemed worthy, move them up into go-starlark. Once a package
lands there, we'll switch starlib to being a strict import-and-document-only,
which effectively locks their API. Keeping package APIs locked will cut down on
drift, benefitting the broader starlark ecosystem.

BREAKING CHANGE:
math & time modules have been overhauled. Refer to package documentation for
details
b5 added a commit to qri-io/starlib that referenced this pull request Apr 8, 2021
…am versions

After a *large* community effort, we've successfully moved two packages from
starlib into the "real" standard library in go-starlark:
* math: google/starlark-go#357
* time: google/starlark-go#327

In addition, both time & math imports have lost thier ".star" suffixes:

load("time.star", "time") -> load("time", "time")
load("math.star, "math") -> load("math", "math")

Both of these packages have had a great deal of vetting & improvement in the
process of migrating. While this does introduce many breaking changes for both
packages, both packages have better stability & performance characteristics.

Moving forward, we'll try to follow the pattern of developing & testing packages
here, and for those deemed worthy, move them up into go-starlark. Once a package
lands there, we'll switch starlib to being a strict import-and-document-only,
which effectively locks their API. Keeping package APIs locked will cut down on
drift, benefitting the broader starlark ecosystem.

BREAKING CHANGE:
math & time modules have been overhauled. Refer to package documentation for
details
b5 added a commit to qri-io/starlib that referenced this pull request Apr 8, 2021
…am versions

After a *large* community effort, we've successfully moved two packages from
starlib into the "real" standard library in go-starlark:
* math: google/starlark-go#357
* time: google/starlark-go#327

In addition, both time & math imports have lost thier ".star" suffixes:

load("time.star", "time") -> load("time", "time")
load("math.star, "math") -> load("math", "math")

Both of these packages have had a great deal of vetting & improvement in the
process of migrating. While this does introduce many breaking changes for both
packages, both packages have better stability & performance characteristics.

Moving forward, we'll try to follow the pattern of developing & testing packages
here, and for those deemed worthy, move them up into go-starlark. Once a package
lands there, we'll switch starlib to being a strict import-and-document-only,
which effectively locks their API. Keeping package APIs locked will cut down on
drift, benefitting the broader starlark ecosystem.

BREAKING CHANGE:
math & time modules have been overhauled. Refer to package documentation for
details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants