Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
G
golang
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
golang
Commits
448aa49c
Commit
448aa49c
authored
Jan 08, 2010
by
Rob Pike
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add a parser to the time package, the inverse of time.Format
R=rsc CC=golang-dev
https://golang.org/cl/183141
parent
35403e4c
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
345 additions
and
1 deletion
+345
-1
format.go
src/pkg/time/format.go
+230
-0
time_test.go
src/pkg/time/time_test.go
+115
-1
No files found.
src/pkg/time/format.go
View file @
448aa49c
...
...
@@ -2,6 +2,7 @@ package time
import
(
"bytes"
"os"
"strconv"
)
...
...
@@ -104,6 +105,15 @@ var longMonthNames = []string{
"December"
,
}
func
lookup
(
tab
[]
string
,
val
string
)
(
int
,
os
.
Error
)
{
for
i
,
v
:=
range
tab
{
if
v
==
val
{
return
i
,
nil
}
}
return
-
1
,
errBad
}
func
charType
(
c
uint8
)
int
{
switch
{
case
'0'
<=
c
&&
c
<=
'9'
:
...
...
@@ -215,3 +225,223 @@ func (t *Time) Format(layout string) string {
// String returns a Unix-style representation of the time value.
func
(
t
*
Time
)
String
()
string
{
return
t
.
Format
(
UnixDate
)
}
var
errBad
=
os
.
ErrorString
(
"bad"
)
// just a marker; not returned to user
// ParseError describes a problem parsing a time string.
type
ParseError
struct
{
Layout
string
Value
string
LayoutElem
string
ValueElem
string
Message
string
}
// String is the string representation of a ParseError.
func
(
e
*
ParseError
)
String
()
string
{
if
e
.
Message
==
""
{
return
"parsing time "
+
strconv
.
Quote
(
e
.
Value
)
+
" as "
+
strconv
.
Quote
(
e
.
Layout
)
+
": cannot parse "
+
strconv
.
Quote
(
e
.
ValueElem
)
+
" as "
+
strconv
.
Quote
(
e
.
LayoutElem
)
}
return
"parsing time "
+
strconv
.
Quote
(
e
.
Value
)
+
e
.
Message
}
// Parse parses a formatted string and returns the time value it represents.
// The layout defines the format by showing the representation of a standard
// time, which is then used to describe the string to be parsed. Predefined
// layouts ANSIC, UnixDate, ISO8601 and others describe standard
// representations.
//
// Only those elements present in the value will be set in the returned time
// structure. Also, if the input string represents an inconsistent time
// (such as having the wrong day of the week), the returned value will also
// be inconsistent. In any case, the elements of the returned time will be
// sane: hours in 0..23, minutes in 0..59, day of month in 0..31, etc.
func
Parse
(
alayout
,
avalue
string
)
(
*
Time
,
os
.
Error
)
{
var
t
Time
const
formatErr
=
": different format from "
rangeErrString
:=
""
// set if a value is out of range
pmSet
:=
false
// do we need to add 12 to the hour?
// Each iteration steps along one piece
nextIsYear
:=
false
// whether next item is a Year; means we saw a minus sign.
layout
,
value
:=
alayout
,
avalue
for
len
(
layout
)
>
0
&&
len
(
value
)
>
0
{
c
:=
layout
[
0
]
pieceType
:=
charType
(
c
)
var
i
int
for
i
=
0
;
i
<
len
(
layout
)
&&
charType
(
layout
[
i
])
==
pieceType
;
i
++
{
}
reference
:=
layout
[
0
:
i
]
layout
=
layout
[
i
:
]
if
reference
==
"Z"
{
// Special case for ISO8601 time zone: "Z" or "-0800"
if
value
[
0
]
==
'Z'
{
i
=
1
}
else
if
len
(
value
)
>=
5
{
i
=
5
}
else
{
return
nil
,
&
ParseError
{
Layout
:
alayout
,
Value
:
avalue
,
Message
:
formatErr
+
alayout
}
}
}
else
{
c
=
value
[
0
]
if
charType
(
c
)
!=
pieceType
{
return
nil
,
&
ParseError
{
Layout
:
alayout
,
Value
:
avalue
,
Message
:
formatErr
+
alayout
}
}
for
i
=
0
;
i
<
len
(
value
)
&&
charType
(
value
[
i
])
==
pieceType
;
i
++
{
}
}
p
:=
value
[
0
:
i
]
value
=
value
[
i
:
]
// Separators must match except possibly for a following minus sign (for negative years)
if
pieceType
==
separator
{
if
len
(
p
)
!=
len
(
reference
)
{
// must be exactly a following minus sign
if
len
(
p
)
!=
len
(
reference
)
+
1
||
p
[
len
(
p
)
-
1
]
!=
'-'
{
return
nil
,
&
ParseError
{
Layout
:
alayout
,
Value
:
avalue
,
Message
:
formatErr
+
alayout
}
}
nextIsYear
=
true
continue
}
}
var
err
os
.
Error
switch
reference
{
case
stdYear
:
t
.
Year
,
err
=
strconv
.
Atoi64
(
p
)
if
t
.
Year
>=
69
{
// Unix time starts Dec 31 1969 in some time zones
t
.
Year
+=
1900
}
else
{
t
.
Year
+=
2000
}
case
stdLongYear
:
t
.
Year
,
err
=
strconv
.
Atoi64
(
p
)
if
nextIsYear
{
t
.
Year
=
-
t
.
Year
nextIsYear
=
false
}
case
stdMonth
:
t
.
Month
,
err
=
lookup
(
shortMonthNames
,
p
)
case
stdLongMonth
:
t
.
Month
,
err
=
lookup
(
longMonthNames
,
p
)
case
stdNumMonth
,
stdZeroMonth
:
t
.
Month
,
err
=
strconv
.
Atoi
(
p
)
if
t
.
Month
<=
0
||
12
<
t
.
Month
{
rangeErrString
=
"month"
}
case
stdWeekDay
:
t
.
Weekday
,
err
=
lookup
(
shortDayNames
,
p
)
case
stdLongWeekDay
:
t
.
Weekday
,
err
=
lookup
(
longDayNames
,
p
)
case
stdDay
,
stdZeroDay
:
t
.
Day
,
err
=
strconv
.
Atoi
(
p
)
if
t
.
Day
<
0
||
31
<
t
.
Day
{
// TODO: be more thorough in date check?
rangeErrString
=
"day"
}
case
stdHour
:
t
.
Hour
,
err
=
strconv
.
Atoi
(
p
)
if
t
.
Hour
<
0
||
24
<=
t
.
Hour
{
rangeErrString
=
"hour"
}
case
stdHour12
,
stdZeroHour12
:
t
.
Hour
,
err
=
strconv
.
Atoi
(
p
)
if
t
.
Hour
<
0
||
12
<
t
.
Hour
{
rangeErrString
=
"hour"
}
case
stdMinute
,
stdZeroMinute
:
t
.
Minute
,
err
=
strconv
.
Atoi
(
p
)
if
t
.
Minute
<
0
||
60
<=
t
.
Minute
{
rangeErrString
=
"minute"
}
case
stdSecond
,
stdZeroSecond
:
t
.
Second
,
err
=
strconv
.
Atoi
(
p
)
if
t
.
Second
<
0
||
60
<=
t
.
Second
{
rangeErrString
=
"second"
}
case
stdZulu
:
if
len
(
p
)
!=
4
{
err
=
os
.
ErrorString
(
"HHMM value must be 4 digits"
)
break
}
t
.
Hour
,
err
=
strconv
.
Atoi
(
p
[
0
:
2
])
if
err
!=
nil
{
t
.
Minute
,
err
=
strconv
.
Atoi
(
p
[
2
:
4
])
}
case
stdISO8601TZ
:
if
p
==
"Z"
{
t
.
Zone
=
"UTC"
break
}
// len(p) known to be 5: "-0800"
var
hr
,
min
int
hr
,
err
=
strconv
.
Atoi
(
p
[
1
:
3
])
if
err
!=
nil
{
min
,
err
=
strconv
.
Atoi
(
p
[
3
:
5
])
}
t
.
ZoneOffset
=
(
hr
*
60
+
min
)
*
60
// offset is in seconds
switch
p
[
0
]
{
case
'+'
:
case
'-'
:
t
.
ZoneOffset
=
-
t
.
ZoneOffset
default
:
err
=
errBad
}
case
stdPM
:
if
p
==
"PM"
{
pmSet
=
true
}
else
if
p
!=
"AM"
{
err
=
errBad
}
case
stdpm
:
if
p
==
"pm"
{
pmSet
=
true
}
else
if
p
!=
"am"
{
err
=
errBad
}
case
stdTZ
:
// Does it look like a time zone?
if
p
==
"UTC"
{
t
.
Zone
=
p
break
}
// All other time zones look like XXT or XXXT.
if
len
(
p
)
!=
3
&&
len
(
p
)
!=
4
||
p
[
len
(
p
)
-
1
]
!=
'T'
{
err
=
errBad
}
for
i
:=
0
;
i
<
len
(
p
);
i
++
{
if
p
[
i
]
<
'A'
||
'Z'
<
p
[
i
]
{
err
=
errBad
}
}
if
err
!=
nil
{
break
}
// It's a valid format.
t
.
Zone
=
p
// Can we find it in the table?
for
_
,
z
:=
range
zones
{
if
p
==
z
.
zone
.
name
{
t
.
ZoneOffset
=
z
.
zone
.
utcoff
break
}
}
}
if
nextIsYear
{
// Means we didn't see a year when we were expecting one
return
nil
,
&
ParseError
{
Layout
:
alayout
,
Value
:
value
,
Message
:
formatErr
+
alayout
}
}
if
rangeErrString
!=
""
{
return
nil
,
&
ParseError
{
alayout
,
avalue
,
reference
,
p
,
": "
+
rangeErrString
+
" out of range"
}
}
if
err
!=
nil
{
return
nil
,
&
ParseError
{
alayout
,
avalue
,
reference
,
p
,
""
}
}
}
if
pmSet
&&
t
.
Hour
<
12
{
t
.
Hour
+=
12
}
return
&
t
,
nil
}
src/pkg/time/time_test.go
View file @
448aa49c
...
...
@@ -6,6 +6,7 @@ package time_test
import
(
"os"
"strings"
"testing"
"testing/quick"
.
"time"
...
...
@@ -140,7 +141,7 @@ var formatTests = []FormatTest{
}
func
TestFormat
(
t
*
testing
.
T
)
{
// The numeric time represents Thu Feb 4 21:00:57
E
ST 2010
// The numeric time represents Thu Feb 4 21:00:57
P
ST 2010
time
:=
SecondsToLocalTime
(
1265346057
)
for
_
,
test
:=
range
formatTests
{
result
:=
time
.
Format
(
test
.
format
)
...
...
@@ -150,6 +151,113 @@ func TestFormat(t *testing.T) {
}
}
type
ParseTest
struct
{
name
string
format
string
value
string
hasTZ
bool
// contains a time zone
hasWD
bool
// contains a weekday
}
var
parseTests
=
[]
ParseTest
{
ParseTest
{
"ANSIC"
,
ANSIC
,
"Thu Feb 4 21:00:57 2010"
,
false
,
true
},
ParseTest
{
"UnixDate"
,
UnixDate
,
"Thu Feb 4 21:00:57 PST 2010"
,
true
,
true
},
ParseTest
{
"RFC850"
,
RFC850
,
"Thursday, 04-Feb-10 21:00:57 PST"
,
true
,
true
},
ParseTest
{
"RFC1123"
,
RFC1123
,
"Thu, 04 Feb 2010 21:00:57 PST"
,
true
,
true
},
ParseTest
{
"ISO8601"
,
ISO8601
,
"2010-02-04T21:00:57-0800"
,
true
,
false
},
}
func
TestParse
(
t
*
testing
.
T
)
{
for
_
,
test
:=
range
parseTests
{
time
,
err
:=
Parse
(
test
.
format
,
test
.
value
)
if
err
!=
nil
{
t
.
Errorf
(
"%s error: %v"
,
test
.
name
,
err
)
}
else
{
checkTime
(
time
,
&
test
,
t
)
}
}
}
func
checkTime
(
time
*
Time
,
test
*
ParseTest
,
t
*
testing
.
T
)
{
// The time should be Thu Feb 4 21:00:57 PST 2010
if
time
.
Year
!=
2010
{
t
.
Errorf
(
"%s: bad year: %d not %d
\n
"
,
test
.
name
,
time
.
Year
,
2010
)
}
if
time
.
Month
!=
2
{
t
.
Errorf
(
"%s: bad month: %d not %d
\n
"
,
test
.
name
,
time
.
Month
,
2
)
}
if
time
.
Day
!=
4
{
t
.
Errorf
(
"%s: bad day: %d not %d
\n
"
,
test
.
name
,
time
.
Day
,
4
)
}
if
time
.
Hour
!=
21
{
t
.
Errorf
(
"%s: bad hour: %d not %d
\n
"
,
test
.
name
,
time
.
Hour
,
21
)
}
if
time
.
Minute
!=
0
{
t
.
Errorf
(
"%s: bad minute: %d not %d
\n
"
,
test
.
name
,
time
.
Minute
,
0
)
}
if
time
.
Second
!=
57
{
t
.
Errorf
(
"%s: bad second: %d not %d
\n
"
,
test
.
name
,
time
.
Second
,
57
)
}
if
test
.
hasTZ
&&
time
.
ZoneOffset
!=
-
28800
{
t
.
Errorf
(
"%s: bad tz offset: %d not %d
\n
"
,
test
.
name
,
time
.
ZoneOffset
,
-
28800
)
}
if
test
.
hasWD
&&
time
.
Weekday
!=
4
{
t
.
Errorf
(
"%s: bad weekday: %d not %d
\n
"
,
test
.
name
,
time
.
Weekday
,
4
)
}
}
func
TestFormatAndParse
(
t
*
testing
.
T
)
{
const
fmt
=
"Mon MST "
+
ISO8601
// all fields
f
:=
func
(
sec
int64
)
bool
{
t1
:=
SecondsToLocalTime
(
sec
)
t2
,
err
:=
Parse
(
fmt
,
t1
.
Format
(
fmt
))
if
err
!=
nil
{
t
.
Errorf
(
"error: %s"
,
err
)
return
false
}
if
!
same
(
t1
,
t2
)
{
t
.
Errorf
(
"different: %q %q"
,
t1
,
t2
)
return
false
}
return
true
}
f32
:=
func
(
sec
int32
)
bool
{
return
f
(
int64
(
sec
))
}
cfg
:=
&
quick
.
Config
{
MaxCount
:
10000
}
// Try a reasonable date first, then the huge ones.
if
err
:=
quick
.
Check
(
f32
,
cfg
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
err
:=
quick
.
Check
(
f
,
cfg
);
err
!=
nil
{
t
.
Fatal
(
err
)
}
}
type
ParseErrorTest
struct
{
format
string
value
string
expect
string
// must appear within the error
}
var
parseErrorTests
=
[]
ParseErrorTest
{
ParseErrorTest
{
ANSIC
,
"Feb 4 21:00:60 2010"
,
"parse"
},
// cannot parse Feb as Mon
ParseErrorTest
{
ANSIC
,
"Thu Feb 4 21:00:57 @2010"
,
"format"
},
ParseErrorTest
{
ANSIC
,
"Thu Feb 4 21:00:60 2010"
,
"second out of range"
},
ParseErrorTest
{
ANSIC
,
"Thu Feb 4 21:61:57 2010"
,
"minute out of range"
},
ParseErrorTest
{
ANSIC
,
"Thu Feb 4 24:00:60 2010"
,
"hour out of range"
},
}
func
TestParseErrors
(
t
*
testing
.
T
)
{
for
_
,
test
:=
range
parseErrorTests
{
_
,
err
:=
Parse
(
test
.
format
,
test
.
value
)
if
err
==
nil
{
t
.
Errorf
(
"expected error for %q %q
\n
"
,
test
.
format
,
test
.
value
)
}
else
if
strings
.
Index
(
err
.
String
(),
test
.
expect
)
<
0
{
t
.
Errorf
(
"expected error with %q for %q %q; got %s
\n
"
,
test
.
expect
,
test
.
format
,
test
.
value
,
err
)
}
}
}
func
BenchmarkSeconds
(
b
*
testing
.
B
)
{
for
i
:=
0
;
i
<
b
.
N
;
i
++
{
Seconds
()
...
...
@@ -168,3 +276,9 @@ func BenchmarkFormat(b *testing.B) {
time
.
Format
(
"Mon Jan 2 15:04:05 2006"
)
}
}
func
BenchmarkParse
(
b
*
testing
.
B
)
{
for
i
:=
0
;
i
<
b
.
N
;
i
++
{
Parse
(
ANSIC
,
"Mon Jan 2 15:04:05 2006"
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment