ijt / go-anytime Goto Github PK
View Code? Open in Web Editor NEWParse natural and standardized dates/times and ranges in Go without knowing the format in advance
License: MIT License
Parse natural and standardized dates/times and ranges in Go without knowing the format in advance
License: MIT License
For example
s := "next week I'd like to set up a Japan trip from Jan 21st to Feb 15th just like the one from last year."
got, err := ReplaceRangesByFunc(s, now, func(r Range) string { return "RANGE" })
gives back an answer with incorrect whitespace.
That's kind of a glaring omission.
Let's handle them.
I forgot to add this.
It's expected to be 05:22 tomorrow, if current time passed 05:22, but it returns 05:22 today.
anytime.Parse(as, time.Now(), anytime.DefaultToFuture)
It could look something like this:
anytime.PartitionTimes("Let's meet Thursday or Friday if you're free")
=> []any{"Let's meet", time.Time{...}, "or", time.Time{...}, "if you're free"}
Alternatively there could be something based on callbacks:
anytime.PartitionTimesByFunc("Let's meet Thursday or Friday", func(s string) {/* non-time */}, func(t time.Time, s string) {/* time */})
That would be more type safe.
We may as well handle them.
For example, for now=time.Date(2022, 11, 1, 0, 0, 0, 0, time.UTC), and direction=future, we expect time.Date(2023, 3, 24, 0, 0, 0, 0, time.UTC), but get time.Date(2022, 3, 24, 0, 0, 0, 0, time.UTC).
This project is listed in Go awesome, so it could have a badge to say that.
I know at least one person who wants to write them this way.
It probably makes more sense for it to end at May 1.
Maybe it could be faster.
It looks like we need to put \b around all the short month names.
This comes up when using ReplaceTimesByFunc
.
It would be useful to have functions that replace the times or time ranges within a string, typically to put them into a standardized format or just to label them. For example,
ReplaceTimesByFunc("Let's meet on Tuesday at 11am UTC", time.Now(), func(t time.Time) string { return t.String() })
would return something like
"Let's meet 2022-10-25 12:00:00 +0000 UTC"
Or maybe it's still WIP, and I should use v1 instead. A bit confused.
Of course we want to handle "noon".
The whole point of this module is to not have to worry too much about the format of the input.
I expected it to be parsed as two dates.
It would be useful to have at least an approximation of the input string used for the parsing, in the results of the date and date range parsers.
For consistency, this would imply similar changes to other implicit ranges.
For example, if you run ReplaceTimesByFunc
on the string "1000 widgets" the current version of the date parser thinks 1000 is a year, so ReplaceTimesByFunc would replace that number with whatever is returned by the passed-in func(time.Time) string
.
There are at least three possible ways to deal with this:
Option 2 would mean ints that happen to match 2\d{3}
would always get treated as years. That seems problematic.
Option 3 would have been nice to have included in the original signature. However, it's not backwards compatible with the existing release, so it would require a major version bump. Also, it would not necessarily provide enough context for the caller to determine whether a year really is meant.
Option 1 seems like the best of the three since it is more likely to produce the intended result.
func BenchmarkReplaceTimesByFunc(b *testing.B) {
s := `'Twas brillig and the slithy toves did gyre and gimble in 50 wabe. All mimsy were the borogroves and the mome raths outgrabe. Beware the jabberwock my son, the jaws that bite January 2020 the claws that catch. Avoid the jubjub bird and shun the frumious bandersnatch. He took his vorpal sword in hand, longtime...`
want := strings.ReplaceAll(s, "January 2020", "<time>")
for i := 0; i < b.N; i++ {
got, err := ReplaceTimesByFunc(s, now, func(t time.Time) string {
return "<time>"
})
if err != nil {
b.Fatal(err)
}
if got != want {
b.Fatalf("\ngot %q\nwant %q", got, want)
}
}
}
Output:
/private/var/folders/b7/h46kx5zx4m985db8s__9trdc0000gn/T/GoLand/___BenchmarkReplaceTimesByFunc_in_github_com_ijt_go_anytime.test -test.v -test.paniconexit0 -test.bench ^\QBenchmarkReplaceTimesByFunc\E$ -test.run ^$
goos: darwin
goarch: arm64
pkg: github.com/ijt/go-anytime
BenchmarkReplaceTimesByFunc
BenchmarkReplaceTimesByFunc-10 738 1495768 ns/op
PASS
This leaves a lot to be desired for heavy use.
I'm currently working on implicit date ranges like "today", "last week", and the like. For example, "today" begins at 0:0:0 today and ends at 0:0:0 tomorrow, exclusive, so that defines a range.
One way to detect those ranges in many cases is to find the first time field that is a default (1 for date fields, 0 for time fields) and then go up a level and add one unit of that. So for example with "today" you typically find that the first defaulted field is the hours (truncated to 0) and so you add a day (24h) to today and get tomorrow at 0:0:0. However, that fails in cases like when the user specifies Jan 2022 since Jan is month 1. They don't mean all of the year 2022. They mean the month of January in 2022.
So the resolution info has to come through somehow, though it's not currently available in the parser generated by RangeParser(). One way to solve the problem would be to have the datetime Parser() func make a parser returning a Range. Probably to make it more explicit, the Range would be changed to have a start time and a duration. The duration would only be used when the datetime is used as an implicit range.
That affects the API, though the change can be kept to a minimum. The result of Parser() can return a Range, so that is different. However, Parse() can still extract the time.Time and return that as before.
How do we do this in a way that keeps code changes and risk to a minimum? One way would be to embed the start time in the range so you can use it as a time, keeping a lot of code unchanged:
type Range struct {
time.Time
Duration time.Duration
}
func (r Range) Start() time.Time {
return r.time.Time // not sure about the exact syntax here
}
func (r Range) End() time.Time {
return r.Start().Add(r.Duration)
}
This is idiomatic so it would be good to support it.
The desired behavior is to parse "december" and leave the "40" part alone.
Let's do them.
It would be useful to have something like ReplaceDateRangesByFunc
that replaces anything with a time scale of more than a day.
This would give us access to the underlying strings that went into the times, whereas PartitionTimes
does not.
The day of month needs to require a word boundary or a "nd|th|rd".
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.