t := time.Parse(fmt, input)
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond(), time.Local)
There's actually more to this, as the user might be allowed to use one
of several different formats, so a series of calls to Parse with
different formats, checking for error, must be used.
It would be nice if the time pkg had a function that would take a slice
of format strings, the string to be parsed, and defaults for elided
values, and return a Time value based on the first format string that
fit the user input, using the programmer-provided defaults for values
that the user did not specify.
...Marvin
(I'm subscribed, no need to CC me.)
Thanks, Kyle, for the suggestion.
However, this doesn't do what is needed in the case I gave. Suppose the
user input is "2012-01-19 01:38". He did not bother to specify that the
time zone is EST. Using Parse, I get a Time value that represents the
instant in time "2012-01-19 01:38 UTC". If I apply .In(time.Local) to
this value (where Local is my time zone, EST), I get back a time value
that represents the same instant in time, but in the Eastern time zone:
"2012-01-18 20:38 EST". This is clearly not the time the user intended.
What I want is to be able to tell Parse that the user might input the
date in several possible formats, give Parse the defaults for values
that the user might not enter, and have Parse return the correct time
value. To be useful for handling user input, it must be easy to use
Parse when the specific format is not known at compile time, and the
user must be allowed to omit parts that might reasonably be omitted if
he were writing the date or time on a piece of paper; implied values
should be supplied for him (either by the programmer or by the time
package).
The Unix date command allows specifying a date for the -d option in many
different formats, and it does a good job of figuring out what date was
intended. Try date -d with the following arguments: "3:45",
"3:45pm", "3/21", "3/21 3:45pm", "2010-03-21 15:45", "Mar 21, 2010
15:45", "2010-03-21 15:45 UTC", and "2010-02-21 15:45 UTC". date gives
reasonable and predictable results for each of these.
...Marvin
Offset may be different in winter and in summer. Your algorithm may
work wrong if time.Now() is in winter but user enters summer date.
if you know that some time string, say t, parsed with a given format, say f,
then you can get that time in some arbitrary other time zone,
by appending the time zone to both t and f.
in fact you can use this technique to add any information
that's missing in one format to another.
here's an example that goes through a list of time formats
and returns the first one that matches, with any missing
information taken from the current time.
package main
import (
"fmt"
"time"
)
type formatInfo struct {
format string
needed string // all the missing information from the format
}
var formats = []formatInfo{
{time.ANSIC, " -0700"},
{time.UnixDate, ""},
{time.RubyDate, ""},
{time.RFC822, ""},
{time.RFC822Z, ""},
{time.RFC850, ""},
{time.RFC1123, ""},
{time.RFC1123Z, ""},
{time.RFC3339, ""},
{time.Kitchen, " 05 -0700 01/02/2006"},
{"15:04", " 05 -0700 01/02/2006"},
{"02/01/2006", " -0700 15:04:05"},
}
func parse(s string, deflt time.Time) (time.Time, error) {
for _, f := range formats {
format := f.format
t, err := time.Parse(format, s)
if err != nil {
continue
}
t, err = time.Parse(f.format+f.needed, s+deflt.Format(f.needed))
if err != nil {
panic("unexpectedly failed parse")
}
return t, nil
}
return time.Time{}, fmt.Errorf("failed to parse time")
}
func main() {
t, err := parse("23:45", time.Now())
if err != nil {
fmt.Printf("error: %v\n", err)
} else {
fmt.Printf("%v\n", t)
}
}
it looks right to me. how is it wrong?
oh, i've just realised, you're saying that the time
zone name is wrong, not the date..
unfortunately the time package doesn't know
how to map from time instant to zone name (and in
general there's no correct such mapping), so unless you know the
default time zone name you can't do much better.
but if you ignore the time zone names and use
numeric offsets only, i think the technique works
ok.
> It seems that there isn't better solution to assume some default
> timezone for user input (or for MySQL text output in my case) than use
> time.Parse followed by time.Date.
how is using time.Date better (other than being somewhat
more efficient) ?
Sorry, I was gone all day.
zuitek, thank you for explaining the subtleties of this so well; you
clearly understand why I believe the "Right" place for this is in the
time package and not in user code.
I actually thought of roger's solution and rejected it for the reason
you give. A variation of roger's code that does work is to use
time.Parse() with different strings until one works, then use
time.Date() to mix the parsed values with the correct defaults,
including Location rather than an offset. This is still cumbersome and
is code that should be written once in the system library rather than
multiple times, half of them wrong, in user code.
I've read the contribution guidelines, so I'm asking here what people
think about a new function in the time package that parses using a slice
of strings for the possible formats and a set of individual default
values. I don't have a good name for the method; suggestions are
encouraged. ParseMulti or ParseMultiFormat comes to mind. My suggested
signature is
func ParseMultiFormat(layouts []string, value string, year int, month Month,
day, hour, min, sec, nsec int, loc *Location) (Time, error)
I would include an exported variable that includes most or all of the
pre-defined formats. (Suggestions for the name? MultiFormats?)
Would such a function be welcome in the time package? Is it too late
for Go 1?
...Marvin
You can also write a setter in the following way:
func OverrideLocation(t time.Time, loc *time.Location) (time.Time) {
y, m, d := t.Date()
H, M, S := t.Clock()
return time.Date(y, m, d, H, M, S, t.Nanoseconds(), loc)
}
I'd say it's a bit easier to digest than:
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(),
t.Second(), t.Nanoseconds(), loc)
that was proposed earlier. Maybe I'd prefer to add the reciprocal of Date():
func (t Time)Split() (year int, month Month, day, hour, min, sec, nsec
int, loc *Location)
Rémy.