-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: time: add civil time package #19700
Comments
And because the drinking age in California is 21. The ballot measure never happened. |
21 in NY too, it turns out. Edited comment. |
fun story... the drinking age is 21 in every state because of a law that withheld federal highway money unless states mandated 21 as the drinking age. https://en.wikipedia.org/wiki/National_Minimum_Drinking_Age_Act |
On hold for #17244, which in turn is essentially blocked on understanding the long-term plan for package management. Soon. |
I left a comment on the implementation in the CL. I'll mirror the parts that are not about implementation details here: In my opinion time.Duration already implements a civil time (see #20757 for a list of issues that may be interesting). Please clarify the difference between a civil date and just a normal date (dates don't have timezone issues to my knowledge, especially because they don't represent a moment in time). There are multiple tiny "date" packages that look more or less the same (I implemented one here https://godoc.org/github.com/infobaleen/date, but there are others which are practically identical). |
Since we need a civil DateTime type, and a civil Time type to support SQL, it seems reasonable to put the Date type in the same package with them. Since the |
Maybe I misunderstood the goal, as I don't see the range problem you are mentioning. Are you considering other calendars where a day doesn't have |
Other calendars are not a goal. The time package documents that:
|
Where does that type live? And what about the type that just represents a time of day (
I didn't understand your proposal. I thought you wanted to represent a DateTime as a duration from some single reference time, like Unix represents time as an offset from 1/1/1970. |
I don't have a strong opinion on that. I would suggest putting it in "exp" first and integrating it in "time" later
time.Duration is sufficient (look at it's metods). Maybe alias it. |
I've also needed a pure Date for SQL and other things, but I implemented it by wrapping |
i don't understand why is duplicate "time: add weekday helper functions #25469" (btw please see my last comment)... please don't take me wrong i only try to understand how do you think. |
Change https://golang.org/cl/38571 mentions this issue: |
If nothing is blocking this any longer, I'd like to proceed. I appreciate the suggestions for alternative implementations (wrapping |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
What's the status of this? I'm a bit unfamiliar with how these CLs work, but it looks like it hasn't seen any activity for about a month. Is it just waiting for some final confirmation on its inclusion? Right now we're importing the google package, which is a huge import, just to get access to the civil one (can't get subdirectory importing to work with go modules). |
I'd just like to put in a word for having both zoned and unzoned civil datetime types. For calendars, you want to be able to support implicit time zone, as well as explicit. |
Isn't a zoned civil datetime just a
|
To clarify, I'm saying that a date/time library needs to support at least all of the following:
|
@rsc (or more generally the Proposal committee) I would like to re-open this proposal for consideration in order to get closure on it. Why Now?The SQL Server Driver currently supports "https://godoc.org/cloud.google.com/go/civil" for date, time and date time types. However, "cloud.google.com/go" is a heavy module and I would prefer we do not require it, when I really just want the civil types from it. I have extracted it from that package as "github.com/golang-sql/civil" with a PR to switch to using it denisenkom/go-mssqldb#501 . However, if this proposal was accepted and https://golang.org/cl/38571 was accepted, I would want to use that. Why use Civil types at all?Business applications frequently use both Date, Timestamp and Timestampz (or equivalent) types. One type of bug I have encountered is where a DB Date type is used, but in certain instances time.Time will set the wrong date based on the time of day (due to timezone). This is fixable, but in newer code I've chosen to use civil.Date. This simplifies reading and understanding the code and prevents through fundamental design an entire class of bugs. When working with Dates in a date based scheduler, converting to civil.Date simplified the code over time.Time. Equality and inequality became much simpler cognitively and slightly simpler in code. When sending a parameter to a database instance, can be important to have proper types so they are represented correctly the the SQL:
What do I wantGo1.13 is almost out. I would like resolution whether x/time/civil will be a package or not. If not, I will use "github.com/golang-sql/civil". If it may, then I will wait until it gets merged. Once this is sorted out, I will encurage other database drivers to support these civil types as well. Date, DateTime, Time (of day), and Decimal types are the last types commonly supported by Databases. I'm addressing Decimal support with https://golang.org/issue/30870 . |
Seems like we still need to sort out #17244. But I'll take that one off hold. |
As background, most languages developers are familiar with have two time types, one for location-independent time instants (put aside the obvious physics-based objections) and one for human-readable times in specific locations. This goes back at least as far as C, which has More recently, Google's C++ team has been using the terms "absolute time" and "civil time" to denote the two different kinds of types. So in this nomenclature This API split, where you have to explicitly convert an absolute time to a civil time, fragments the ecosystem by time representation. Some code takes one representation, other code takes another. When you decide you want to store a time, you need to make a decision yourself. And then you're either in the The original Go time package followed the traditional C split and had all these drawbacks. In fact there were three time types, with functions to convert between them:
This kind of fragmentation is particularly unfortunate when trying to establish agreed-upon conventions for interfaces. For example, which time type should os.FileInfo's ModTime method return? Making that decision would force every implementation into one fragment of the ecosystem or the other. The major advance of the Go 1 time API was to unify the two types into the single type time.Time, removing those kinds of decisions and all the fragmentation and confusion they imply. Thanks to a careful, lightweight representation, the one time type works equally well for both The answer is that os.FileInfo's ModTime returns a time.Time, because that's the only possible answer. All this is a bit of a long-winded way to say that my response to the summary “add civil time” is: we already have a type for civil time: time.Time. That is, to the extent that civil time means time instants interpreted in locations with time zones and a Gregorian calendar, time.Time does that for us already. We should not add something else called a “civil time.” Doing so would be very confusing and put us right back into the ecosystem fragmentation present in most other systems and in Go before Go 1. That said, what I'm hearing in this issue is not so much “add civil time” as “add a type representing a day without a specific time” and maybe also “add a type representing a clock time without a specific day.” I realize that in some other languages these two things are called Date and Time, but those are already both exported names in package time, so they are off the table. My suggestion would be to use time.Day and time.Clock. Specifically:
I am not thrilled with the name Day, but I have not come up with a better one. And:
I am not 100% convinced Clock is necessary at all. |
@rsc The new types you are suggesting here accurately represent what I would want. In fact, they closely mirror the API and types I had begun porting from my own "old" C++ and C# versions. One additional API I would probably suggest is:
For naming, it's unfortunate that the most obvious/intuitive names are already taken. I have called the While I think Clock may not be absolutely necessary, it is useful enough that I have implemented it pretty much in its entirety for myself already. |
I'd be happy with |
👍 to Possibly terrible idea: could there be magic sentinel |
@jimmyfrasche I switched from I think I would be against the magic sentinel |
I don't believe this is a correct description of a civil time as defined by ABSL. (https://abseil.io/docs/cpp/guides/time) A civil time is not a time instant. A civil time is a tuple of (year, month, day, hour, minute, second). (Leave aside the question of fractional seconds for the moment.) A time.Time isn't a civil time precisely because a time.Time is a time instant. |
@rsc Your proposal looks on the right track to me, but I have two concerns. First, I feel the My second concern is that there still isn't a type for what SQL calls I know I'm biased, but I still think that |
@jba, the problem with putting this all in another package is (1) for database/sql to use it, the package has to be in the standard library, and (2) a new package would either have to reproduce large chunks of time (the types time.Month & time.Weekday; code for Parse and Format, since we are not going to use a completely different formatting approach for dates vs times; and so on) or else it would have to require the user to import time to get at those, at which point it's not really a separate package. |
@neild, I see that time.Time is not exactly Abseil's civil time concept, and that's fine with me. The point was that we reduced the number of time representations intentionally in the design of package time, and it would be good to continue that decision. I remain very skeptical about any widespread need for "times without locations". And on top of that it implies creating a second competing time representation and all the complexity and fragmentation that would accompany that. It seems to be all cost and no benefit. In contrast, I can very easily see the need for a representation for a specific day, and it brings essentially no incidental complexity with it. The arguments in favor of adding "time without location" seem to be "Google C++ has it" and "Java 8 has it". I don't find either of these compelling: the types here have to fit into the design of the time package overall, because they will live alongside it. Hence the long background section in my earlier response. @jba and I spent a long time (probably too long) looking through old SQL specs trying to understand use cases and the like and came up empty. In practice my working hypothesis is that almost no one has the hypothetical problem that "without timezone" solves. That's why I'm focusing on 'calendar day' and maybe 'clock time' and not 'time without location'. |
I don't think that's true, at least since a number of releases ago: between the Scanner & driver.Valuer interfaces, types from other packages can go in & out of databases. /cc @kardianos |
Not that my vote carries any real weight (since I'm really just a consumer of Go with some opinions) but I agree that "time without location" probably wouldn't be generally useful. I would even go so far as to argue that people using database columns to store timestamps without location/tz information are "doing it wrong", since those systems likely also have to make assumptions in code about what that missing time zone should be. I know we did at a prior job, which led us to convert every column to either Given those three storage representations, the two new proposed types + |
Individual drivers can support arbitrary types for parameters and scanning in. DateTime would be useful to ensure a correct mapping between db types without tz and Go, but some adapter function or adapter type might also work for that purpose. Civil date is most useful and would be welcome. |
I have gone down the "convention" road for dates-without-times and times-without-dates in the past. They were all basically used for data analysis, though some of the quantities did come from databases or other structured formats. The prime motivation is that humans use the clock on their wrist to think about time, and this typically doesn't consider time zone, date, daylight saving, or leap seconds. One case boils down to number of events in what a local human would call their day or the time, irrespective of any real 24 hour period. For example, in a global dashboard of "cups of coffee dispensed per day" I want to care about what the date was for the person who dispensed the coffee. To keep this example going, I'd be interested in whether more cups of coffee are dispensed in the "morning" (before noon at the dispenser) or on "Monday" (local time). I should also be able to compare whether these trends carry in different geographies without having to add timezone offsets to normalize to some "common" location (hint: don't try this). Another case where these distinctions have been really important is in computing SLA numbers. For example, the number of hours between submission of a code review and when the reviewer sent their first response. For this, we don't want to include non-business hours, weekends, and holidays. Both of these analyses involve the following quantities:
It is certainly possible to do all of this with the time.Time package as it stands today by establishing conventions, and we have. I have a background in timekeeping, and I still get it wrong and miss things in code reviews. Proper types for the above would help considerably. |
@rsc I like your proposal for time.Day. I feel like time.Clock would be useful, but the proposal presented isn't quite right I think. I would want a Clock to either: 1) be easy to convert to and from time.Duration, or 2) have some of these methods on time.Duration directly (Duration.Format, then retrieve days, hours, min, secs, nanos as integers rather then floats). These types don't have to be in the standard library for drivers to use them. While it isn't strictly necessary, I agree with @jba that having a DateTime (time.Time without zone) would be useful as it enhances code clarity; I've had bugs in Go today where the zone was incorrect going into or out of a database with a DateTime field that resulted in an incorrect value. The problem (as much as it is) is round tripping DateTime field values. If you put a DateTime into the database, you want to ensure that the same value is selected out. So if you put in a time.Time with a local timezone and just use the face (no zone) value, but then the sql scan returns a time.Time with a UTC timezone, it can get you in trouble. Should the scan as a localtime zone might be better, but the problem remains, just reversed UTC zone in, local out). This, can be fine, until you hit some other marshal function that has different assumptions (ie uses zone when you assume it should not). If a next step were to be taken, would it to decide where such a package would reside (stdlib/time, golang.org/x/time/civil)? |
For reference, here is a comment where the timezone causes ambiguity: |
Any updates on this? :) Would love to see this added. See above mentioned issue. |
I have used However it is nice if there is a package that would do date and time without time zones and I think this should live in x/time/civil to indicate that it could be included in the standard library one day, most likely never will though as it’s not common enough. I understand by saying this I give an opinion that isn’t robust at all considering the ‘what goes in golang.org/x’ discussion. |
I personally find it quite necessary. The usecase is representing a regular schedule, for example the timetable of a train.
As seen in this timetable excerpt above, train R 11361 leaves Brașov every day at 06:52. When combined with a date and the Europe/Bucharest location, on the 17 of December 2023 the ISO8601 timestamp is 2023-12-17T04:58:00Z, and on the 1st of July 2024 the timestamp is 2024-07-01T03:58:00Z. Another example is an alarm clock, which should also not be affected by timezone or DST changes. |
For a Time as in "point in day", a time.Duration would suffice, such that a time.Now().Truncate(24time.Hour).Add(6time.Hour+58*time.Minute). |
Your message is badly formatted because of not using a code block, so it's a bit harder to read. Assuming I understood it right, however, that's actually incorrect, particularly on days when DST changes. 6 hours and 58 minutes after midnight of the 31th of March 2024 is 07:58, and 6 hours and 58 minutes after midnight of the 27th of October 2024 is 05:58. However, on both of those days, the train I mentioned departs from the 2nd station at 06:58. package main
import (
"fmt"
"time"
)
func main() {
duration := 6*time.Hour + 58*time.Minute
location, _ := time.LoadLocation("Europe/Bucharest")
fmt.Println(time.Date(2024, time.March, 31, 0, 0, 0, 0, location).Add(duration))
fmt.Println(time.Date(2024, time.July, 1, 0, 0, 0, 0, location).Add(duration))
fmt.Println(time.Date(2024, time.October, 27, 0, 0, 0, 0, location).Add(duration))
} Using the example above, you can see that all of the printed hours are different. As such, a |
I propose a package with minimal implementations of the types Date, Time and DateTime, which represent times without a corresponding location.
A civil time or date does not represent a point or interval of time, but they are useful for representing events transpiring between humans. For example, your birthday begins at midnight on your birthdate regardless of where you are in the world. If you're turning 21, you can buy a drink in New York at midnight, but teleport instantaneously to San Francisco and you'll be denied, because it is 9 PM the day before.
In practice, the main motivation for these types is to represent values in database-like storage systems, like BigQuery and Spanner (and other, non-Google products).
The package currently exists at cloud.google.com/go/civil, and has been in use by the BigQuery and Spanner client libraries for a few months. For now, I'd like to move it to golang.org/x/time/civil. It is probably too esoteric to be worth including in the standard library, but if there were ever a "second-tier" set of packages that augmented the standard library, it could live there. (See #17244.)
A CL is in progress at https://go-review.googlesource.com/c/38571.
The text was updated successfully, but these errors were encountered: