├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── date.go ├── date_test.go ├── example_test.go ├── format.go ├── format_test.go ├── rep.go └── rep_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | 6 | install: 7 | - go get -t -v ./... 8 | - go get github.com/mattn/goveralls 9 | 10 | script: 11 | - go test -v -covermode=count -coverprofile=coverage.out ./... 12 | - go tool cover -func=coverage.out 13 | - $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN 14 | 15 | env: 16 | secure: "kcksCWXVeZKmFUWcyi2S/j87iwUmXMxZXxA2DG9ymc11QP43QoPNSG9pBjA/DDjvzt4WdKIFphTrxVfvawii/9j3oXA1aPmAcHGu87i4iOVg4IIZ4bPZLfUo0e7s6XP5FakzegYvPP6HWV5Xr5h+Q6osrjq3czOnPY+rVII6MRrxXMOfsqo8HEER+YIOOD6vj5LV2/quY8d0XHtThqgGvQ1cz4OB3vbd4KFBl48kmfXKefTrRG1NoqoQMMpwUVzU395JIEAg1eWbGkquhWU5v13gRwk3VMVWF75jZna8TSiqWha0P5iQdaED30kNCz3poIaBI1MLdxktJxwUQJZ5AaYIMCxh7ZCiW0FXTYCRu3EoeYusTPMLqy1ghK+gIlA46sNd26cKk5/OngXRrHo/J0aF5NWjydlk5FLHfKm9ih/Y426M9nV2zYNQAcVKgO8zVNb2IkJ3e7aTB2NH4DpkvjSV4D4hlnmW9xxmo14TKF+gXJ9Hw9ssKbigRHoL6S92aQHcpkdjGGnI5YSTy1fZh/nIE3HDmx+hcK4/ZtPHj9KnXopKYxBGyNswN5Eko+q6h3BB/Q7LwALtEexdDbznwsRmcZXJsOU4chvcjAKgIAi6cbeTwq8kG5E/w8TTY2wGeRm2ZFysQu6Jf8hgTZDQTV343RG80STWeUbiD0o7/WY=" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # date 2 | 3 | [![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fxtlabs/date) 4 | [![Build Status](https://api.travis-ci.org/fxtlabs/date.svg?branch=master)](https://travis-ci.org/fxtlabs/date) 5 | [![Coverage Status](https://coveralls.io/repos/fxtlabs/date/badge.svg?branch=master&service=github)](https://coveralls.io/github/fxtlabs/date?branch=master) 6 | 7 | Package `date` provides functionality for working with dates. 8 | 9 | This package introduces a light-weight `Date` type that is storage-efficient 10 | and covenient for calendrical calculations and date parsing and formatting 11 | (including years outside the [0,9999] interval). 12 | 13 | See [package documentation](https://godoc.org/github.com/fxtlabs/date) for 14 | full documentation and examples. 15 | 16 | ## Installation 17 | 18 | go get -u github.com/fxtlabs/date 19 | 20 | ## Credits 21 | 22 | This package follows very closely the design of package 23 | [`time`](http://golang.org/pkg/time/) in the standard library; 24 | many of the `Date` methods are implemented using the corresponding methods 25 | of the `time.Time` type and much of the documentation is copied directly 26 | from that package. 27 | 28 | -------------------------------------------------------------------------------- /date.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package date provides functionality for working with dates. 6 | // 7 | // This package introduces a light-weight Date type that is storage-efficient 8 | // and covenient for calendrical calculations and date parsing and formatting 9 | // (including years outside the [0,9999] interval). 10 | // 11 | // Credits 12 | // 13 | // This package follows very closely the design of package time 14 | // (http://golang.org/pkg/time/) in the standard library, many of the Date 15 | // methods are implemented using the corresponding methods of the time.Time 16 | // type, and much of the documentation is copied directly from that package. 17 | // 18 | // References 19 | // 20 | // https://golang.org/src/time/time.go 21 | // 22 | // https://en.wikipedia.org/wiki/Gregorian_calendar 23 | // 24 | // https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar 25 | // 26 | // https://en.wikipedia.org/wiki/Astronomical_year_numbering 27 | // 28 | // https://en.wikipedia.org/wiki/ISO_8601 29 | // 30 | // https://tools.ietf.org/html/rfc822 31 | // 32 | // https://tools.ietf.org/html/rfc850 33 | // 34 | // https://tools.ietf.org/html/rfc1123 35 | // 36 | // https://tools.ietf.org/html/rfc3339 37 | // 38 | package date 39 | 40 | import ( 41 | "errors" 42 | "fmt" 43 | "math" 44 | "time" 45 | ) 46 | 47 | // A Date represents a date under the (proleptic) Gregorian calendar as 48 | // used by ISO 8601. This calendar uses astronomical year numbering, 49 | // so it includes a year 0 and represents earlier years as negative numbers 50 | // (i.e. year 0 is 1 BC; year -1 is 2 BC, and so on). 51 | // 52 | // A Date value requires 4 bytes of storage and can represent dates from 53 | // Tue, 23 Jun -5,877,641 (5,877,642 BC) to Fri, 11 Jul 5,881,580. 54 | // Dates outside that range will "wrap around". 55 | // 56 | // Programs using dates should typically store and pass them as values, 57 | // not pointers. That is, date variables and struct fields should be of 58 | // type date.Date, not *date.Date. A Date value can be used by 59 | // multiple goroutines simultaneously. 60 | // 61 | // Date values can be compared using the Before, After, and Equal methods 62 | // as well as the == and != operators. 63 | // The Sub method subtracts two dates, returning the number of days between 64 | // them. 65 | // The Add method adds a Date and a number of days, producing a Date. 66 | // 67 | // The zero value of type Date is Thursday, January 1, 1970. 68 | // As this date is unlikely to come up in practice, the IsZero method gives 69 | // a simple way of detecting a date that has not been initialized explicitly. 70 | // 71 | type Date struct { 72 | // day gives the number of days elapsed since date zero. 73 | day int32 74 | } 75 | 76 | // New returns the Date value corresponding to the given year, month, and day. 77 | // 78 | // The month and day may be outside their usual ranges and will be normalized 79 | // during the conversion. 80 | func New(year int, month time.Month, day int) Date { 81 | t := time.Date(year, month, day, 12, 0, 0, 0, time.UTC) 82 | return Date{encode(t)} 83 | } 84 | 85 | // NewAt returns the Date value corresponding to the given time. 86 | // Note that the date is computed relative to the time zone specified by 87 | // the given Time value. 88 | func NewAt(t time.Time) Date { 89 | return Date{encode(t)} 90 | } 91 | 92 | // Today returns today's date according to the current local time. 93 | func Today() Date { 94 | t := time.Now() 95 | return Date{encode(t)} 96 | } 97 | 98 | // TodayUTC returns today's date according to the current UTC time. 99 | func TodayUTC() Date { 100 | t := time.Now().UTC() 101 | return Date{encode(t)} 102 | } 103 | 104 | // TodayIn returns today's date according to the current time relative to 105 | // the specified location. 106 | func TodayIn(loc *time.Location) Date { 107 | t := time.Now().In(loc) 108 | return Date{encode(t)} 109 | } 110 | 111 | // Min returns the smallest representable date. 112 | func Min() Date { 113 | return Date{math.MinInt32} 114 | } 115 | 116 | // Max returns the largest representable date. 117 | func Max() Date { 118 | return Date{math.MaxInt32} 119 | } 120 | 121 | // UTC returns a Time value corresponding to midnight on the given date, 122 | // UTC time. Note that midnight is the beginning of the day rather than the end. 123 | func (d Date) UTC() time.Time { 124 | return decode(d.day) 125 | } 126 | 127 | // Local returns a Time value corresponding to midnight on the given date, 128 | // local time. Note that midnight is the beginning of the day rather than the end. 129 | func (d Date) Local() time.Time { 130 | return d.In(time.Local) 131 | } 132 | 133 | // In returns a Time value corresponding to midnight on the given date, 134 | // relative to the specified time zone. Note that midnight is the beginning 135 | // of the day rather than the end. 136 | func (d Date) In(loc *time.Location) time.Time { 137 | t := decode(d.day).In(loc) 138 | _, offset := t.Zone() 139 | return t.Add(time.Duration(-offset) * time.Second) 140 | } 141 | 142 | // Date returns the year, month, and day of d. 143 | func (d Date) Date() (year int, month time.Month, day int) { 144 | t := decode(d.day) 145 | return t.Date() 146 | } 147 | 148 | // Day returns the day of the month specified by d. 149 | // The first day of the month is 1. 150 | func (d Date) Day() int { 151 | t := decode(d.day) 152 | return t.Day() 153 | } 154 | 155 | // Month returns the month of the year specified by d. 156 | func (d Date) Month() time.Month { 157 | t := decode(d.day) 158 | return t.Month() 159 | } 160 | 161 | // Year returns the year specified by d. 162 | func (d Date) Year() int { 163 | t := decode(d.day) 164 | return t.Year() 165 | } 166 | 167 | // YearDay returns the day of the year specified by d, in the range [1,365] for 168 | // non-leap years, and [1,366] in leap years. 169 | func (d Date) YearDay() int { 170 | t := decode(d.day) 171 | return t.YearDay() 172 | } 173 | 174 | // Weekday returns the day of the week specified by d. 175 | func (d Date) Weekday() time.Weekday { 176 | // Date zero, January 1, 1970, fell on a Thursday 177 | wdayZero := time.Thursday 178 | // Taking into account potential for overflow and negative offset 179 | return time.Weekday((int32(wdayZero) + d.day%7 + 7) % 7) 180 | } 181 | 182 | // ISOWeek returns the ISO 8601 year and week number in which d occurs. 183 | // Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to 184 | // week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1 185 | // of year n+1. 186 | func (d Date) ISOWeek() (year, week int) { 187 | t := decode(d.day) 188 | return t.ISOWeek() 189 | } 190 | 191 | // IsZero reports whether t represents the zero date. 192 | func (d Date) IsZero() bool { 193 | return d.day == 0 194 | } 195 | 196 | // Equal reports whether d and u represent the same date. 197 | func (d Date) Equal(u Date) bool { 198 | return d.day == u.day 199 | } 200 | 201 | // Before reports whether the date d is before u. 202 | func (d Date) Before(u Date) bool { 203 | return d.day < u.day 204 | } 205 | 206 | // After reports whether the date d is after u. 207 | func (d Date) After(u Date) bool { 208 | return d.day > u.day 209 | } 210 | 211 | // Add returns the date d plus the given number of days. 212 | func (d Date) Add(days int) Date { 213 | return Date{d.day + int32(days)} 214 | } 215 | 216 | // AddDate returns the date corresponding to adding the given number of years, 217 | // months, and days to d. For example, AddData(-1, 2, 3) applied to 218 | // January 1, 2011 returns March 4, 2010. 219 | func (d Date) AddDate(years, months, days int) Date { 220 | t := decode(d.day) 221 | t = t.AddDate(years, months, days) 222 | return Date{encode(t)} 223 | } 224 | 225 | // Sub returns d-u as the number of days between the two dates. 226 | func (d Date) Sub(u Date) (days int) { 227 | return int(d.day - u.day) 228 | } 229 | 230 | // MarshalBinary implements the encoding.BinaryMarshaler interface. 231 | func (d Date) MarshalBinary() ([]byte, error) { 232 | enc := []byte{ 233 | byte(d.day >> 24), 234 | byte(d.day >> 16), 235 | byte(d.day >> 8), 236 | byte(d.day), 237 | } 238 | return enc, nil 239 | } 240 | 241 | // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. 242 | func (d *Date) UnmarshalBinary(data []byte) error { 243 | if len(data) == 0 { 244 | return errors.New("Date.UnmarshalBinary: no data") 245 | } 246 | if len(data) != 4 { 247 | return errors.New("Date.UnmarshalBinary: invalid length") 248 | } 249 | 250 | d.day = int32(data[3]) | int32(data[2])<<8 | int32(data[1])<<16 | int32(data[0])<<24 251 | 252 | return nil 253 | } 254 | 255 | // GobEncode implements the gob.GobEncoder interface. 256 | func (d Date) GobEncode() ([]byte, error) { 257 | return d.MarshalBinary() 258 | } 259 | 260 | // GobDecode implements the gob.GobDecoder interface. 261 | func (d *Date) GobDecode(data []byte) error { 262 | return d.UnmarshalBinary(data) 263 | } 264 | 265 | // MarshalJSON implements the json.Marshaler interface. 266 | // The date is a quoted string in ISO 8601 extended format (e.g. "2006-01-02"). 267 | // If the year of the date falls outside the [0,9999] range, this format 268 | // produces an expanded year representation with possibly extra year digits 269 | // beyond the prescribed four-digit minimum and with a + or - sign prefix 270 | // (e.g. , "+12345-06-07", "-0987-06-05"). 271 | func (d Date) MarshalJSON() ([]byte, error) { 272 | return []byte(`"` + d.String() + `"`), nil 273 | } 274 | 275 | // UnmarshalJSON implements the json.Unmarshaler interface. 276 | // The date is expected to be a quoted string in ISO 8601 extended format 277 | // (e.g. "2006-01-02", "+12345-06-07", "-0987-06-05"); 278 | // the year must use at least 4 digits and if outside the [0,9999] range 279 | // must be prefixed with a + or - sign. 280 | func (d *Date) UnmarshalJSON(data []byte) (err error) { 281 | value := string(data) 282 | n := len(value) 283 | if n < 2 || value[0] != '"' || value[n-1] != '"' { 284 | return fmt.Errorf("Date.UnmarshalJSON: missing double quotes (%s)", value) 285 | } 286 | u, err := ParseISO(value[1 : n-1]) 287 | if err != nil { 288 | return err 289 | } 290 | d.day = u.day 291 | return nil 292 | } 293 | 294 | // MarshalText implements the encoding.TextMarshaler interface. 295 | // The date is given in ISO 8601 extended format (e.g. "2006-01-02"). 296 | // If the year of the date falls outside the [0,9999] range, this format 297 | // produces an expanded year representation with possibly extra year digits 298 | // beyond the prescribed four-digit minimum and with a + or - sign prefix 299 | // (e.g. , "+12345-06-07", "-0987-06-05"). 300 | func (d Date) MarshalText() ([]byte, error) { 301 | return []byte(d.String()), nil 302 | } 303 | 304 | // UnmarshalText implements the encoding.TextUnmarshaler interface. 305 | // The date is expected to be in ISO 8601 extended format 306 | // (e.g. "2006-01-02", "+12345-06-07", "-0987-06-05"); 307 | // the year must use at least 4 digits and if outside the [0,9999] range 308 | // must be prefixed with a + or - sign. 309 | func (d *Date) UnmarshalText(data []byte) error { 310 | u, err := ParseISO(string(data)) 311 | if err != nil { 312 | return err 313 | } 314 | d.day = u.day 315 | return nil 316 | } 317 | -------------------------------------------------------------------------------- /date_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package date_test 6 | 7 | import ( 8 | "bytes" 9 | "encoding/gob" 10 | "encoding/json" 11 | "testing" 12 | "time" 13 | 14 | "github.com/fxtlabs/date" 15 | ) 16 | 17 | func same(d date.Date, t time.Time) bool { 18 | yd, wd := d.ISOWeek() 19 | yt, wt := t.ISOWeek() 20 | return d.Year() == t.Year() && 21 | d.Month() == t.Month() && 22 | d.Day() == t.Day() && 23 | d.Weekday() == t.Weekday() && 24 | d.YearDay() == t.YearDay() && 25 | yd == yt && wd == wt 26 | } 27 | 28 | func TestNew(t *testing.T) { 29 | cases := []string{ 30 | "0000-01-01T00:00:00+00:00", 31 | "0001-01-01T00:00:00+00:00", 32 | "1614-01-01T01:02:03+04:00", 33 | "1970-01-01T00:00:00+00:00", 34 | "1815-12-10T05:06:07+00:00", 35 | "1901-09-10T00:00:00-05:00", 36 | "1998-09-01T00:00:00-08:00", 37 | "2000-01-01T00:00:00+00:00", 38 | "9999-12-31T00:00:00+00:00", 39 | } 40 | for _, c := range cases { 41 | tIn, err := time.Parse(time.RFC3339, c) 42 | if err != nil { 43 | t.Errorf("New(%v) cannot parse input: %v", c, err) 44 | continue 45 | } 46 | dOut := date.New(tIn.Year(), tIn.Month(), tIn.Day()) 47 | if !same(dOut, tIn) { 48 | t.Errorf("New(%v) == %v, want date of %v", c, dOut, tIn) 49 | } 50 | dOut = date.NewAt(tIn) 51 | if !same(dOut, tIn) { 52 | t.Errorf("NewAt(%v) == %v, want date of %v", c, dOut, tIn) 53 | } 54 | } 55 | } 56 | 57 | func TestToday(t *testing.T) { 58 | today := date.Today() 59 | now := time.Now() 60 | if !same(today, now) { 61 | t.Errorf("Today == %v, want date of %v", today, now) 62 | } 63 | today = date.TodayUTC() 64 | now = time.Now().UTC() 65 | if !same(today, now) { 66 | t.Errorf("TodayUTC == %v, want date of %v", today, now) 67 | } 68 | cases := []int{-10, -5, -3, 0, 1, 4, 8, 12} 69 | for _, c := range cases { 70 | location := time.FixedZone("zone", c*60*60) 71 | today = date.TodayIn(location) 72 | now = time.Now().In(location) 73 | if !same(today, now) { 74 | t.Errorf("TodayIn(%v) == %v, want date of %v", c, today, now) 75 | } 76 | } 77 | } 78 | 79 | func TestTime(t *testing.T) { 80 | cases := []struct { 81 | year int 82 | month time.Month 83 | day int 84 | }{ 85 | {-1234, time.February, 5}, 86 | {0, time.April, 12}, 87 | {1, time.January, 1}, 88 | {1946, time.February, 4}, 89 | {1970, time.January, 1}, 90 | {1976, time.April, 1}, 91 | {1999, time.December, 1}, 92 | {1111111, time.June, 21}, 93 | } 94 | zones := []int{-12, -10, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 8, 12} 95 | for _, c := range cases { 96 | d := date.New(c.year, c.month, c.day) 97 | tUTC := d.UTC() 98 | if !same(d, tUTC) { 99 | t.Errorf("TimeUTC(%v) == %v, want date part %v", d, tUTC, d) 100 | } 101 | if tUTC.Location() != time.UTC { 102 | t.Errorf("TimeUTC(%v) == %v, want %v", d, tUTC.Location(), time.UTC) 103 | } 104 | tLocal := d.Local() 105 | if !same(d, tLocal) { 106 | t.Errorf("TimeLocal(%v) == %v, want date part %v", d, tLocal, d) 107 | } 108 | if tLocal.Location() != time.Local { 109 | t.Errorf("TimeLocal(%v) == %v, want %v", d, tLocal.Location(), time.Local) 110 | } 111 | for _, z := range zones { 112 | location := time.FixedZone("zone", z*60*60) 113 | tInLoc := d.In(location) 114 | if !same(d, tInLoc) { 115 | t.Errorf("TimeIn(%v) == %v, want date part %v", d, tInLoc, d) 116 | } 117 | if tInLoc.Location() != location { 118 | t.Errorf("TimeIn(%v) == %v, want %v", d, tInLoc.Location(), location) 119 | } 120 | } 121 | } 122 | } 123 | 124 | func TestPredicates(t *testing.T) { 125 | // The list of case dates must be sorted in ascending order 126 | cases := []struct { 127 | year int 128 | month time.Month 129 | day int 130 | }{ 131 | {-1234, time.February, 5}, 132 | {0, time.April, 12}, 133 | {1, time.January, 1}, 134 | {1946, time.February, 4}, 135 | {1970, time.January, 1}, 136 | {1976, time.April, 1}, 137 | {1999, time.December, 1}, 138 | {1111111, time.June, 21}, 139 | } 140 | for i, ci := range cases { 141 | di := date.New(ci.year, ci.month, ci.day) 142 | for j, cj := range cases { 143 | dj := date.New(cj.year, cj.month, cj.day) 144 | p := di.Equal(dj) 145 | q := i == j 146 | if p != q { 147 | t.Errorf("Equal(%v, %v) == %v, want %v", di, dj, p, q) 148 | } 149 | p = di.Before(dj) 150 | q = i < j 151 | if p != q { 152 | t.Errorf("Before(%v, %v) == %v, want %v", di, dj, p, q) 153 | } 154 | p = di.After(dj) 155 | q = i > j 156 | if p != q { 157 | t.Errorf("After(%v, %v) == %v, want %v", di, dj, p, q) 158 | } 159 | p = di == dj 160 | q = i == j 161 | if p != q { 162 | t.Errorf("Equal(%v, %v) == %v, want %v", di, dj, p, q) 163 | } 164 | p = di != dj 165 | q = i != j 166 | if p != q { 167 | t.Errorf("Equal(%v, %v) == %v, want %v", di, dj, p, q) 168 | } 169 | } 170 | } 171 | 172 | // Test IsZero 173 | zero := date.Date{} 174 | if !zero.IsZero() { 175 | t.Errorf("IsZero(%v) == false, want true", zero) 176 | } 177 | today := date.Today() 178 | if today.IsZero() { 179 | t.Errorf("IsZero(%v) == true, want false", today) 180 | } 181 | } 182 | 183 | func TestArithmetic(t *testing.T) { 184 | cases := []struct { 185 | year int 186 | month time.Month 187 | day int 188 | }{ 189 | {-1234, time.February, 5}, 190 | {0, time.April, 12}, 191 | {1, time.January, 1}, 192 | {1946, time.February, 4}, 193 | {1970, time.January, 1}, 194 | {1976, time.April, 1}, 195 | {1999, time.December, 1}, 196 | {1111111, time.June, 21}, 197 | } 198 | offsets := []int{-1000000, -9999, -555, -99, -22, -1, 0, 1, 22, 99, 555, 9999, 1000000} 199 | for _, c := range cases { 200 | d := date.New(c.year, c.month, c.day) 201 | for _, days := range offsets { 202 | d2 := d.Add(days) 203 | days2 := d2.Sub(d) 204 | if days2 != days { 205 | t.Errorf("AddSub(%v,%v) == %v, want %v", d, days, days2, days) 206 | } 207 | d3 := d2.Add(-days) 208 | if d3 != d { 209 | t.Errorf("AddNeg(%v,%v) == %v, want %v", d, days, d3, d) 210 | } 211 | } 212 | } 213 | } 214 | 215 | func TestGobEncoding(t *testing.T) { 216 | var b bytes.Buffer 217 | encoder := gob.NewEncoder(&b) 218 | decoder := gob.NewDecoder(&b) 219 | cases := []date.Date{ 220 | date.New(-11111, time.February, 3), 221 | date.New(-1, time.December, 31), 222 | date.New(0, time.January, 1), 223 | date.New(1, time.January, 1), 224 | date.New(1970, time.January, 1), 225 | date.New(2012, time.June, 25), 226 | date.New(12345, time.June, 7), 227 | } 228 | for _, c := range cases { 229 | var d date.Date 230 | err := encoder.Encode(&c) 231 | if err != nil { 232 | t.Errorf("Gob(%v) encode error %v", c, err) 233 | } else { 234 | err = decoder.Decode(&d) 235 | if err != nil { 236 | t.Errorf("Gob(%v) decode error %v", c, err) 237 | } 238 | } 239 | } 240 | } 241 | 242 | func TestInvalidGob(t *testing.T) { 243 | cases := []struct { 244 | bytes []byte 245 | want string 246 | }{ 247 | {[]byte{}, "Date.UnmarshalBinary: no data"}, 248 | {[]byte{1, 2, 3}, "Date.UnmarshalBinary: invalid length"}, 249 | } 250 | for _, c := range cases { 251 | var ignored date.Date 252 | err := ignored.GobDecode(c.bytes) 253 | if err == nil || err.Error() != c.want { 254 | t.Errorf("InvalidGobDecode(%v) == %v, want %v", c.bytes, err, c.want) 255 | } 256 | err = ignored.UnmarshalBinary(c.bytes) 257 | if err == nil || err.Error() != c.want { 258 | t.Errorf("InvalidUnmarshalBinary(%v) == %v, want %v", c.bytes, err, c.want) 259 | } 260 | } 261 | } 262 | 263 | func TestJSONMarshalling(t *testing.T) { 264 | var d date.Date 265 | cases := []struct { 266 | value date.Date 267 | want string 268 | }{ 269 | {date.New(-11111, time.February, 3), `"-11111-02-03"`}, 270 | {date.New(-1, time.December, 31), `"-0001-12-31"`}, 271 | {date.New(0, time.January, 1), `"0000-01-01"`}, 272 | {date.New(1, time.January, 1), `"0001-01-01"`}, 273 | {date.New(1970, time.January, 1), `"1970-01-01"`}, 274 | {date.New(2012, time.June, 25), `"2012-06-25"`}, 275 | {date.New(12345, time.June, 7), `"+12345-06-07"`}, 276 | } 277 | for _, c := range cases { 278 | bytes, err := json.Marshal(c.value) 279 | if err != nil { 280 | t.Errorf("JSON(%v) marshal error %v", c, err) 281 | } else if string(bytes) != c.want { 282 | t.Errorf("JSON(%v) == %v, want %v", c.value, string(bytes), c.want) 283 | } else { 284 | err = json.Unmarshal(bytes, &d) 285 | if err != nil { 286 | t.Errorf("JSON(%v) unmarshal error %v", c.value, err) 287 | } 288 | } 289 | } 290 | } 291 | 292 | func TestInvalidJSON(t *testing.T) { 293 | cases := []struct { 294 | value string 295 | want string 296 | }{ 297 | {`"not-a-date"`, `Date.ParseISO: cannot parse not-a-date`}, 298 | {`2015-08-15"`, `Date.UnmarshalJSON: missing double quotes (2015-08-15")`}, 299 | {`"2015-08-15`, `Date.UnmarshalJSON: missing double quotes ("2015-08-15)`}, 300 | {`"215-08-15"`, `Date.ParseISO: cannot parse 215-08-15`}, 301 | } 302 | for _, c := range cases { 303 | var d date.Date 304 | err := d.UnmarshalJSON([]byte(c.value)) 305 | if err == nil || err.Error() != c.want { 306 | t.Errorf("InvalidJSON(%v) == %v, want %v", c.value, err, c.want) 307 | } 308 | } 309 | } 310 | 311 | func TestTextMarshalling(t *testing.T) { 312 | var d date.Date 313 | cases := []struct { 314 | value date.Date 315 | want string 316 | }{ 317 | {date.New(-11111, time.February, 3), "-11111-02-03"}, 318 | {date.New(-1, time.December, 31), "-0001-12-31"}, 319 | {date.New(0, time.January, 1), "0000-01-01"}, 320 | {date.New(1, time.January, 1), "0001-01-01"}, 321 | {date.New(1970, time.January, 1), "1970-01-01"}, 322 | {date.New(2012, time.June, 25), "2012-06-25"}, 323 | {date.New(12345, time.June, 7), "+12345-06-07"}, 324 | } 325 | for _, c := range cases { 326 | bytes, err := c.value.MarshalText() 327 | if err != nil { 328 | t.Errorf("Text(%v) marshal error %v", c, err) 329 | } else if string(bytes) != c.want { 330 | t.Errorf("Text(%v) == %v, want %v", c.value, string(bytes), c.want) 331 | } else { 332 | err = d.UnmarshalText(bytes) 333 | if err != nil { 334 | t.Errorf("Text(%v) unmarshal error %v", c.value, err) 335 | } 336 | } 337 | } 338 | } 339 | 340 | func TestInvalidText(t *testing.T) { 341 | cases := []struct { 342 | value string 343 | want string 344 | }{ 345 | {`not-a-date`, `Date.ParseISO: cannot parse not-a-date`}, 346 | {`215-08-15`, `Date.ParseISO: cannot parse 215-08-15`}, 347 | } 348 | for _, c := range cases { 349 | var d date.Date 350 | err := d.UnmarshalText([]byte(c.value)) 351 | if err == nil || err.Error() != c.want { 352 | t.Errorf("InvalidText(%v) == %v, want %v", c.value, err, c.want) 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package date_test 6 | 7 | import ( 8 | "fmt" 9 | "time" 10 | 11 | "github.com/fxtlabs/date" 12 | ) 13 | 14 | func ExampleMax() { 15 | d := date.Max() 16 | fmt.Println(d) 17 | // Output: +5881580-07-11 18 | } 19 | 20 | func ExampleMin() { 21 | d := date.Min() 22 | fmt.Println(d) 23 | // Output: -5877641-06-23 24 | } 25 | 26 | func ExampleNew() { 27 | d := date.New(9999, time.December, 31) 28 | fmt.Printf("The world ends on %s\n", d) 29 | // Output: The world ends on 9999-12-31 30 | } 31 | 32 | func ExampleParse() { 33 | // longForm shows by example how the reference date would be 34 | // represented in the desired layout. 35 | const longForm = "Mon, January 2, 2006" 36 | d, _ := date.Parse(longForm, "Tue, February 3, 2013") 37 | fmt.Println(d) 38 | 39 | // shortForm is another way the reference date would be represented 40 | // in the desired layout. 41 | const shortForm = "2006-Jan-02" 42 | d, _ = date.Parse(shortForm, "2013-Feb-03") 43 | fmt.Println(d) 44 | 45 | // Output: 46 | // 2013-02-03 47 | // 2013-02-03 48 | } 49 | 50 | func ExampleParseISO() { 51 | d, _ := date.ParseISO("+12345-06-07") 52 | year, month, day := d.Date() 53 | fmt.Println(year) 54 | fmt.Println(month) 55 | fmt.Println(day) 56 | // Output: 57 | // 12345 58 | // June 59 | // 7 60 | } 61 | 62 | func ExampleDate_AddDate() { 63 | d := date.New(1000, time.January, 1) 64 | // Months and days do not need to be constrained to [1,12] and [1,365]. 65 | u := d.AddDate(0, 14, -1) 66 | fmt.Println(u) 67 | // Output: 1001-02-28 68 | } 69 | 70 | func ExampleDate_Format() { 71 | // layout shows by example how the reference time should be represented. 72 | const layout = "Jan 2, 2006" 73 | d := date.New(2009, time.November, 10) 74 | fmt.Println(d.Format(layout)) 75 | // Output: Nov 10, 2009 76 | } 77 | 78 | func ExampleDate_FormatISO() { 79 | // According to legend, Rome was founded on April 21, 753 BC. 80 | // Note that with astronomical year numbering, 753 BC becomes -752 81 | // because 1 BC is actually year 0. 82 | d := date.New(-752, time.April, 21) 83 | fmt.Println(d.FormatISO(5)) 84 | // Output: -00752-04-21 85 | } 86 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package date 6 | 7 | import ( 8 | "fmt" 9 | "regexp" 10 | "strconv" 11 | "time" 12 | ) 13 | 14 | // These are predefined layouts for use in Date.Format and Date.Parse. 15 | // The reference date used in the layouts is the same date used by the 16 | // time package in the standard library: 17 | // Monday, Jan 2, 2006 18 | // To define your own format, write down what the reference date would look 19 | // like formatted your way; see the values of the predefined layouts for 20 | // examples. The model is to demonstrate what the reference date looks like 21 | // so that the Parse function and Format method can apply the same 22 | // transformation to a general date value. 23 | const ( 24 | ISO8601 = "2006-01-02" // ISO 8601 extended format 25 | ISO8601B = "20060102" // ISO 8601 basic format 26 | RFC822 = "02-Jan-06" 27 | RFC822W = "Mon, 02-Jan-06" // RFC822 with day of the week 28 | RFC850 = "Monday, 02-Jan-06" 29 | RFC1123 = "02 Jan 2006" 30 | RFC1123W = "Mon, 02 Jan 2006" // RFC1123 with day of the week 31 | RFC3339 = "2006-01-02" 32 | ) 33 | 34 | // reISO8601 is the regular expression used to parse date strings in the 35 | // ISO 8601 extended format, with or without an expanded year representation. 36 | var reISO8601 = regexp.MustCompile(`^([-+]?\d{4,})-(\d{2})-(\d{2})$`) 37 | 38 | // ParseISO parses an ISO 8601 formatted string and returns the date value it represents. 39 | // In addition to the common extended format (e.g. 2006-01-02), this function 40 | // accepts date strings using the expanded year representation 41 | // with possibly extra year digits beyond the prescribed four-digit minimum 42 | // and with a + or - sign prefix (e.g. , "+12345-06-07", "-0987-06-05"). 43 | // 44 | // Note that ParseISO is a little looser than the ISO 8601 standard and will 45 | // be happy to parse dates with a year longer than the four-digit minimum even 46 | // if they are missing the + sign prefix. 47 | // 48 | // Function Date.Parse can be used to parse date strings in other formats, but it 49 | // is currently not able to parse ISO 8601 formatted strings that use the 50 | // expanded year format. 51 | func ParseISO(value string) (Date, error) { 52 | m := reISO8601.FindStringSubmatch(value) 53 | if len(m) != 4 { 54 | return Date{}, fmt.Errorf("Date.ParseISO: cannot parse %s", value) 55 | } 56 | // No need to check for errors since the regexp guarantees the matches 57 | // are valid integers 58 | year, _ := strconv.Atoi(m[1]) 59 | month, _ := strconv.Atoi(m[2]) 60 | day, _ := strconv.Atoi(m[3]) 61 | 62 | t := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) 63 | 64 | return Date{encode(t)}, nil 65 | } 66 | 67 | // Parse parses a formatted string and returns the Date value it represents. 68 | // The layout defines the format by showing how the reference date, defined 69 | // to be 70 | // Monday, Jan 2, 2006 71 | // would be interpreted if it were the value; it serves as an example of the 72 | // input format. The same interpretation will then be made to the input string. 73 | // 74 | // This function actually uses time.Parse to parse the input and can use any 75 | // layout accepted by time.Parse, but returns only the date part of the 76 | // parsed Time value. 77 | // 78 | // This function cannot currently parse ISO 8601 strings that use the expanded 79 | // year format; you should use Date.ParseISO to parse those strings correctly. 80 | func Parse(layout, value string) (Date, error) { 81 | t, err := time.Parse(layout, value) 82 | if err != nil { 83 | return Date{0}, err 84 | } 85 | return Date{encode(t)}, nil 86 | } 87 | 88 | // String returns the time formatted in ISO 8601 extended format 89 | // (e.g. "2006-01-02"). If the year of the date falls outside the 90 | // [0,9999] range, this format produces an expanded year representation 91 | // with possibly extra year digits beyond the prescribed four-digit minimum 92 | // and with a + or - sign prefix (e.g. , "+12345-06-07", "-0987-06-05"). 93 | func (d Date) String() string { 94 | year, month, day := d.Date() 95 | if 0 <= year && year < 10000 { 96 | return fmt.Sprintf("%04d-%02d-%02d", year, month, day) 97 | } 98 | return fmt.Sprintf("%+05d-%02d-%02d", year, month, day) 99 | } 100 | 101 | // FormatISO returns a textual representation of the date value formatted 102 | // according to the expanded year variant of the ISO 8601 extended format; 103 | // the year of the date is represented as a signed integer using the 104 | // specified number of digits (ignored if less than four). 105 | // The string representation of the year will take more than the specified 106 | // number of digits if the magnitude of the year is too large to fit. 107 | // 108 | // Function Date.Format can be used to format Date values in other formats, 109 | // but it is currently not able to format dates according to the expanded 110 | // year variant of the ISO 8601 format. 111 | func (d Date) FormatISO(yearDigits int) string { 112 | n := 5 // four-digit minimum plus sign 113 | if yearDigits > 4 { 114 | n += yearDigits - 4 115 | } 116 | year, month, day := d.Date() 117 | return fmt.Sprintf("%+0*d-%02d-%02d", n, year, month, day) 118 | } 119 | 120 | // Format returns a textual representation of the date value formatted according 121 | // to layout, which defines the format by showing how the reference date, 122 | // defined to be 123 | // Mon, Jan 2, 2006 124 | // would be displayed if it were the value; it serves as an example of the 125 | // desired output. 126 | // 127 | // This function actually uses time.Format to format the input and can use any 128 | // layout accepted by time.Format by extending its date to a time at 129 | // 00:00:00.000 UTC. 130 | // 131 | // This function cannot currently format Date values according to the expanded 132 | // year variant of ISO 8601; you should use Date.FormatISO to that effect. 133 | func (d Date) Format(layout string) string { 134 | t := decode(d.day) 135 | return t.Format(layout) 136 | } 137 | -------------------------------------------------------------------------------- /format_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package date_test 6 | 7 | import ( 8 | "testing" 9 | "time" 10 | 11 | "github.com/fxtlabs/date" 12 | ) 13 | 14 | func TestParseISO(t *testing.T) { 15 | cases := []struct { 16 | value string 17 | year int 18 | month time.Month 19 | day int 20 | }{ 21 | {"1969-12-31", 1969, time.December, 31}, 22 | {"+1970-01-01", 1970, time.January, 1}, 23 | {"+01970-01-02", 1970, time.January, 2}, 24 | {"2000-02-28", 2000, time.February, 28}, 25 | {"+2000-02-29", 2000, time.February, 29}, 26 | {"+02000-03-01", 2000, time.March, 1}, 27 | {"+002004-02-28", 2004, time.February, 28}, 28 | {"2004-02-29", 2004, time.February, 29}, 29 | {"2004-03-01", 2004, time.March, 1}, 30 | {"0000-01-01", 0, time.January, 1}, 31 | {"+0001-02-03", 1, time.February, 3}, 32 | {"+00019-03-04", 19, time.March, 4}, 33 | {"0100-04-05", 100, time.April, 5}, 34 | {"2000-05-06", 2000, time.May, 6}, 35 | {"+30000-06-07", 30000, time.June, 7}, 36 | {"+400000-07-08", 400000, time.July, 8}, 37 | {"+5000000-08-09", 5000000, time.August, 9}, 38 | {"-0001-09-11", -1, time.September, 11}, 39 | {"-0019-10-12", -19, time.October, 12}, 40 | {"-00100-11-13", -100, time.November, 13}, 41 | {"-02000-12-14", -2000, time.December, 14}, 42 | {"-30000-02-15", -30000, time.February, 15}, 43 | {"-0400000-05-16", -400000, time.May, 16}, 44 | {"-5000000-09-17", -5000000, time.September, 17}, 45 | } 46 | for _, c := range cases { 47 | d, err := date.ParseISO(c.value) 48 | if err != nil { 49 | t.Errorf("ParseISO(%v) == %v", c.value, err) 50 | } 51 | year, month, day := d.Date() 52 | if year != c.year || month != c.month || day != c.day { 53 | t.Errorf("ParseISO(%v) == %v, want (%v, %v, %v)", c.value, d, c.year, c.month, c.day) 54 | } 55 | } 56 | 57 | badCases := []string{ 58 | "1234-05", 59 | "1234-5-6", 60 | "1234-05-6", 61 | "1234-5-06", 62 | "12340506", 63 | "1234/05/06", 64 | "1234-0A-06", 65 | "1234-05-0B", 66 | "1234-05-06trailing", 67 | "padding1234-05-06", 68 | "1-02-03", 69 | "10-11-12", 70 | "100-02-03", 71 | "+1-02-03", 72 | "+10-11-12", 73 | "+100-02-03", 74 | "-123-05-06", 75 | } 76 | for _, c := range badCases { 77 | d, err := date.ParseISO(c) 78 | if err == nil { 79 | t.Errorf("ParseISO(%v) == %v", c, d) 80 | } 81 | } 82 | } 83 | 84 | func TestParse(t *testing.T) { 85 | // Test ability to parse a few common date formats 86 | cases := []struct { 87 | layout string 88 | value string 89 | year int 90 | month time.Month 91 | day int 92 | }{ 93 | {date.ISO8601, "1969-12-31", 1969, time.December, 31}, 94 | {date.ISO8601B, "19700101", 1970, time.January, 1}, 95 | {date.RFC822, "29-Feb-00", 2000, time.February, 29}, 96 | {date.RFC822W, "Mon, 01-Mar-04", 2004, time.March, 1}, 97 | {date.RFC850, "Wednesday, 12-Aug-15", 2015, time.August, 12}, 98 | {date.RFC1123, "05 Dec 1928", 1928, time.December, 5}, 99 | {date.RFC1123W, "Mon, 05 Dec 1928", 1928, time.December, 5}, 100 | {date.RFC3339, "2345-06-07", 2345, time.June, 7}, 101 | } 102 | for _, c := range cases { 103 | d, err := date.Parse(c.layout, c.value) 104 | if err != nil { 105 | t.Errorf("Parse(%v) == %v", c.value, err) 106 | } 107 | year, month, day := d.Date() 108 | if year != c.year || month != c.month || day != c.day { 109 | t.Errorf("Parse(%v) == %v, want (%v, %v, %v)", c.value, d, c.year, c.month, c.day) 110 | } 111 | } 112 | 113 | // Test inability to parse ISO 8601 expanded year format 114 | badCases := []string{ 115 | "+1234-05-06", 116 | "+12345-06-07", 117 | "-1234-05-06", 118 | "-12345-06-07", 119 | } 120 | for _, c := range badCases { 121 | d, err := date.Parse(date.ISO8601, c) 122 | if err == nil { 123 | t.Errorf("Parse(%v) == %v", c, d) 124 | } 125 | } 126 | } 127 | 128 | func TestFormatISO(t *testing.T) { 129 | cases := []struct { 130 | value string 131 | n int 132 | }{ 133 | {"-5000-02-03", 4}, 134 | {"-05000-02-03", 5}, 135 | {"-005000-02-03", 6}, 136 | {"+0000-01-01", 4}, 137 | {"+00000-01-01", 5}, 138 | {"+1000-01-01", 4}, 139 | {"+01000-01-01", 5}, 140 | {"+1970-01-01", 4}, 141 | {"+001999-12-31", 6}, 142 | {"+999999-12-31", 6}, 143 | } 144 | for _, c := range cases { 145 | d, err := date.ParseISO(c.value) 146 | if err != nil { 147 | t.Errorf("FormatISO(%v) cannot parse input: %v", c.value, err) 148 | continue 149 | } 150 | value := d.FormatISO(c.n) 151 | if value != c.value { 152 | t.Errorf("FormatISO(%v) == %v, want %v", c, value, c.value) 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /rep.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package date 6 | 7 | import "time" 8 | 9 | const secondsPerDay = 60 * 60 * 24 10 | 11 | // encode returns the number of days elapsed from date zero to the date 12 | // corresponding to the given Time value. 13 | func encode(t time.Time) int32 { 14 | // Compute the number of seconds elapsed since January 1, 1970 00:00:00 15 | // in the location specified by t and not necessarily UTC. 16 | // A Time value is represented internally as an offset from a UTC base 17 | // time; because we want to extract a date in the time zone specified 18 | // by t rather than in UTC, we need to compensate for the time zone 19 | // difference. 20 | _, offset := t.Zone() 21 | secs := t.Unix() + int64(offset) 22 | // Unfortunately operator / rounds towards 0, so negative values 23 | // must be handled differently 24 | if secs >= 0 { 25 | return int32(secs / secondsPerDay) 26 | } 27 | return -int32((secondsPerDay - 1 - secs) / secondsPerDay) 28 | } 29 | 30 | // decode returns the Time value corresponding to 00:00:00 UTC of the date 31 | // represented by d, the number of days elapsed since date zero. 32 | func decode(d int32) time.Time { 33 | secs := int64(d) * secondsPerDay 34 | return time.Unix(secs, 0).UTC() 35 | } 36 | -------------------------------------------------------------------------------- /rep_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package date 6 | 7 | import ( 8 | "math/rand" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestEncode(t *testing.T) { 14 | cases := []int{ 15 | 0, 1, 28, 30, 31, 32, 364, 365, 366, 367, 500, 1000, 10000, 100000, 16 | } 17 | tBase := time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) 18 | for i, c := range cases { 19 | d := encode(tBase.AddDate(0, 0, c)) 20 | if d != int32(c) { 21 | t.Errorf("Encode(%v) == %v, want %v", i, d, c) 22 | } 23 | d = encode(tBase.AddDate(0, 0, -c)) 24 | if d != int32(-c) { 25 | t.Errorf("Encode(%v) == %v, want %v", i, d, c) 26 | } 27 | } 28 | } 29 | 30 | func TestEncodeDecode(t *testing.T) { 31 | cases := []struct { 32 | year int 33 | month time.Month 34 | day int 35 | }{ 36 | {1969, time.December, 31}, 37 | {1970, time.January, 1}, 38 | {1970, time.January, 2}, 39 | {2000, time.February, 28}, 40 | {2000, time.February, 29}, 41 | {2000, time.March, 1}, 42 | {2004, time.February, 28}, 43 | {2004, time.February, 29}, 44 | {2004, time.March, 1}, 45 | {2100, time.February, 28}, 46 | {2100, time.February, 29}, 47 | {2100, time.March, 1}, 48 | {0, time.January, 1}, 49 | {1, time.February, 3}, 50 | {19, time.March, 4}, 51 | {100, time.April, 5}, 52 | {2000, time.May, 6}, 53 | {30000, time.June, 7}, 54 | {400000, time.July, 8}, 55 | {5000000, time.August, 9}, 56 | {-1, time.September, 11}, 57 | {-19, time.October, 12}, 58 | {-100, time.November, 13}, 59 | {-2000, time.December, 14}, 60 | {-30000, time.February, 15}, 61 | {-400000, time.May, 16}, 62 | {-5000000, time.September, 17}, 63 | } 64 | for _, c := range cases { 65 | tIn := time.Date(c.year, c.month, c.day, 0, 0, 0, 0, time.UTC) 66 | d := encode(tIn) 67 | tOut := decode(d) 68 | if !tIn.Equal(tOut) { 69 | t.Errorf("EncodeDecode(%v) == %v, want %v", c, tOut, tIn) 70 | } 71 | } 72 | } 73 | 74 | func TestDecodeEncode(t *testing.T) { 75 | for i := 0; i < 1000; i++ { 76 | c := rand.Int31() 77 | d := encode(decode(c)) 78 | if d != c { 79 | t.Errorf("DecodeEncode(%v) == %v, want %v", i, d, c) 80 | } 81 | } 82 | for i := 0; i < 1000; i++ { 83 | c := -rand.Int31() 84 | d := encode(decode(c)) 85 | if d != c { 86 | t.Errorf("DecodeEncode(%v) == %v, want %v", i, d, c) 87 | } 88 | } 89 | } 90 | 91 | // TestZone checks that the conversions between a time.Time value and the 92 | // internal representation of a Date value correctly handle time zones other 93 | // than UTC, especially in cases where the local date at a given time is 94 | // different from the UTC date for that same time. 95 | func TestZone(t *testing.T) { 96 | cases := []string{ 97 | "2015-07-29 15:12:34 +0000", 98 | "2015-07-29 15:12:34 -0500", 99 | "2015-07-29 15:12:34 +0500", 100 | "2015-07-29 21:12:34 -0500", 101 | "2015-07-29 21:12:34 -0500", 102 | "2015-07-29 03:12:34 +0500", 103 | "2015-07-29 03:12:34 +0500", 104 | } 105 | for _, c := range cases { 106 | tIn, err := time.Parse("2006-01-02 15:04:05 -0700", c) 107 | if err != nil { 108 | t.Errorf("Zone(%v) cannot parse %v", c, c) 109 | } 110 | d := encode(tIn) 111 | tOut := decode(d) 112 | yIn, mIn, dIn := tIn.Date() 113 | yOut, mOut, dOut := tOut.Date() 114 | if yIn != yOut { 115 | t.Errorf("Zone(%v).y == %v, want %v", c, yOut, yIn) 116 | } 117 | if mIn != mOut { 118 | t.Errorf("Zone(%v).m == %v, want %v", c, mOut, mIn) 119 | } 120 | if dIn != dOut { 121 | t.Errorf("Zone(%v).d == %v, want %v", c, dOut, dIn) 122 | } 123 | } 124 | } 125 | --------------------------------------------------------------------------------