diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..728cc34 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: go + +go: + - 1.5.x + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - 1.10.x + - tip + +before_install: + - go get -t -v ./... + +script: + - ./go.test.sh + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index d53aa6b..6a82e21 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![Software License](https://img.shields.io/github/license/danhunsaker/calends.svg?style=flat-square)](LICENSE) [![Gitter](https://img.shields.io/gitter/room/danhunsaker/calends.svg?style=flat-square)](https://gitter.im/danhunsaker/calends) +[![GoDoc Documentation](https://img.shields.io/badge/GoDoc-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/danhunsaker/calends) [![Latest Stable Version](https://img.shields.io/github/release/danhunsaker/calends.svg?label=stable&style=flat-square)](https://github.com/danhunsaker/calends/releases) [![Latest Unstable Version](https://img.shields.io/github/release/danhunsaker/calends/all.svg?label=unstable&style=flat-square)](https://github.com/danhunsaker/calends/releases) @@ -120,7 +121,231 @@ the library ever need to touch. Another is the `TAI64NAXURTime` class, used to store and manipulate the instants of time which make up a `Calends` instance. The rest are interfaces for extending the library's functionality. -_**.TODO.**_ +`Calends` objects are immutable - all methods return a new `Calends` object +where they might otherwise alter the current one. This is true even of the +`Set*()` methods. This has the side effect of using more memory to perform +manipulations than updating values on an existing object would. It makes many +operations safer, though, than mutable objects would allow. + +Language-specific documentation is available, and may give a more concrete idea +of how to use Calends in a given language/environment, but the general usage +information given here should be valid for all of them. + +### Create + +- `calends.Create(value, calendar, format)` + + Creates a new `Calends` object, using `calendar` to select a calendar system, + and `format` to parse the contents of `value` into the `Calends` object's + internal instants. The contents of `value` can vary based on the calendar + system itself, but generally speaking can always be a string. In any case, the + value can always be a string→value map (associative array, hash, or whatever + your language of choice prefers to call it), where the keys are any two of + `start`, `end`, and `duration`. If all three are provided, `duration` is + ignored in favor of calculating it directly. If only one is provided, `value` + is passed to the calendar system itself unchanged. The calendar system then + converts `value` to a `TAI64TAI64NAXURTime` instant, which the `Calends` + object sets to the appropriate internal value. + +### Read + +- `c.Date(calendar, format)` / `c.EndDate(calendar, format)` + + Retrieves the start or end date of the `Calends` object `c` as a string. The + value is generated by the calendar system given in `calendar`, according to + the format string in `format`. + +- `c.Duration()` + + Retrieves the duration of the `Calends` object `c` as an arbitrary-precision + floating point number. This value will be `0` if the `Calends` object is an + instant. + +### Update + +- `c.SetDate(value, calendar, format)` / `c.SetEndDate(value, calendar, format)` + + Sets the start or end date of a `Calends` object, based on the `Calends` + object `c`. The inputs are the same as for `Create()`, above, except the + string→value map option isn't available, since you're already specifically + setting the start or end value explicitly, depending on which method you call. + +- `c.SetDuration(duration, calendar)` / `c.SetDurationFromEnd(duration, calendar)` + + Sets the duration of a `Calends` object, adjusting the end or start point + accordingly, based on the `Calends` object `c`. The `duration` value is + interpreted by the calendar system given in `calendar`, so is subject to any + of its rules. `SetDurationFromEnd()` will adjust the start point, using the + end as the anchor for the duration. + +### Manipulate + +- `c.Add(offset, calendar)` / `c.AddFromEnd(offset, calendar)` + + Increases the corresponding date in the `Calends` object `c` by `offset`, as + interpreted by the calendar system given in `calendar`. + +- `c.Subtract(offset, calendar)` / `c.SubtractFromEnd(offset, calendar)` + + Works the same as `Add()` / `AddFromEnd()`, except it decreases the + corresponding date, rather than increasing it. + +- `c.Next(offset, calendar)` / `c.Previous(offset, calendar)` + + Returns a `Calends` object of `offset` duration (as interpreted by the + calendar system given in `calendar`), which abuts the `Calends` object `c`. If + `offset` is empty, `calendar` is ignored, and the duration of `c` is used + instead. + +### Combine + +- `c1.Merge(c2)` + + Returns a `Calends` object spanning from the earliest start date to the latest + end date between `Calends` objects `c1` and `c2`. + +- `c1.Intersect(c2)` + + Returns a `Calends` object spanning the overlap between `Calends` objects `c1` + and `c2`. If `c1` and `c2` don't overlap, returns an error. + +- `c1.Gap(c2)` + + Returns a `Calends` object spanning the gap between `Calends` objects `c1` and + `c2`. If `c1` and `c2` overlap (and there is, therefore, no gap to return), + returns an error. + +### Compare + +- `c1.Difference(c2, mode)` + + Returns the difference of `Calends` object `c1` minus `c2`, using `mode` to + select which values to use in the calculation. Valid `mode`s include: + + - `start` - use the start date of both `c1` and `c2` + - `duration` - use the duration of both `c1` and `c2` + - `end` - use the end date of both `c1` and `c2` + - `start-end` - use the start of `c1`, and the end of `c2` + - `end-start` - use the end of `c1`, and the start of `c2` + +- `c1.Compare(c2, mode)` + + Returns `-1` if `Calends` object `c1` is less than `Calends` object `c2`, `0` + if they are equal, and `1` if `c1` is greater than `c2`, using `mode` to + select which values to use in the comparison. Valid modes are the same as for + `Difference()`, above. + +- `c1.Contains(c2)` + + Returns a boolean value indicating whether `Calends` object `c1` contains all + of `Calends` object `c2`. + +- `c1.Overlaps(c2)` + + Returns a boolean value indicating whether `Calends` object `c1` overlaps with + `Calends` object `c2`. + +- `c1.Abuts(c2)` + + Returns a boolean value indicating whether `Calends` object `c1` abuts + `Calends` object `c2` (that is, whether one begins at the same instant the + other ends). + +- `c1.IsSame(c2)` + + Returns a boolean value indicating whether `Calends` object `c1` covers the + same span of time as `Calends` object `c2`. + + +- `c1.IsShorter(c2)` / `c1.IsSameDuration(c2)` / `c1.IsLonger(c2)` + + Returns a boolean comparing the duration of `Calends` objects `c1` and `c2`. + +- `c1.IsBefore(c2)` / `c1.StartsBefore(c2)` / `c1.EndsBefore(c2)` + + Returns a boolean comparing `Calends` object `c1` with the start date of + `Calends` object `c2`. `IsBefore` compares the entirety of `c1` with `c2`; + `StartsBefore` compares only the start date of `c1`; `EndsBefore` compares + only the end date of `c1`. + +- `c1.IsDuring(c2)` / `c1.StartsDuring(c2)` / `c1.EndsDuring(c2)` + + Returns a boolean indicating whether `Calends` object `c1` lies between the + start and end dates of `Calends` object `c2`. `IsDuring` compares the entirety + of `c1` with `c2`; `StartsDuring` compares only the start date of `c1`; + `EndsDuring` compares only the end date of `c1`. + +- `c1.IsAfter(c2)` / `c1.StartsAfter(c2)` / `c1.EndsAfter(c2)` + + Returns a boolean comparing `Calends` object `c1` with the end date of + `Calends` object `c2`. `IsAfter` compares the entirety of `c1` with `c2`; + `StartsAfter` compares only the start date of `c1`; `EndsAfter` compares only + the end date of `c1`. + +### Calendar Systems + +Currently supported calendar systems, and the options available for each, are +listed below. Formats in **bold** are the default format for that calendar. + +- `tai64` + - Formats + - Calends `TAI64NAXURTime` object _(input only)_ + - string with TAI instant representation in one of the following layouts: + - `decimal` - number of seconds since 1970.01.01 00:00:00 TAI + - `tai64` - TAI64 External Representation; the hexadecimal version of + `decimal` plus 2^62, with no fractional seconds (16 hexits total) + - `tai64n` - TAI64N External Representation; `tai64` with 9 decimal places + encoded as 8 additional hexadecimal digits (24 hexits total) + - `tai64na` - TAI64NA External Representation; `tai64n` with 9 more + decimal places (18 total) encoded as 8 additional hexadecimal digits (32 + hexits total) + - `tai64nax` - TAI64NAX External Representation; `tai64na` with 9 more + decimal places (27 total) encoded as 8 additional hexadecimal digits (40 + hexits total) + - `tai64naxu` - TAI64NAXU External Representation; `tai64nax` with 9 more + decimal places (36 total) encoded as 8 additional hexadecimal digits (48 + hexits total) + - **`tai64naxur` - TAI64NAXUR External Representation; `tai64naxu` with 9 + more decimal places (45 total) encoded as 8 additional hexadecimal + digits (56 hexits total)** + - Offsets + - Calends `TAI64NAXURTime` object + - arbitrary-precision floating point number of seconds + - string with `decimal` format layout (above) + +- `unix` + - Formats + - **number of seconds since 1970-01-01 00:00:00 UTC** + - input can be integer or float, in either numeric or string representation + - output uses Golang `fmt.Print()` conventions + - Offsets + - number of seconds + - same input formatting as above + +- `gregorian` + - Formats + - Golang `time.Time` object _(input only)_ + - Golang `time` package format strings (**RFC1123 layout**) + - C-style `strptime()`/`strftime()` format strings + - Offsets + - Golang `time.Duration` object + - string with Gregorian time units + - must be relative times + - use full words instead of abbreviations for time units (such as + `seconds` instead of just `s`) + +## Contributing + +Pull requests are always welcome! That said, please be open to discussing the PR +content, and possibly revising it if requested. Not all requests can be merged, +and not all changes are desired. + +## Security Reporting + +Report all security-related issues to [dan (dot) hunsaker (plus) calends (at) +gmail](dan.hunsaker+calends@gmail.com), and use PGP or GPG protections on your +message. Security issues will be addressed internally before making any +vulnerability announcements. [GitHub]:https://github.com/danhunsaker/calends [TAI64]:http://cr.yp.to/libtai/tai64.html diff --git a/calendars/0calendars.go b/calendars/0calendars.go index 5fc940e..ed18693 100644 --- a/calendars/0calendars.go +++ b/calendars/0calendars.go @@ -56,12 +56,7 @@ parsing or formatting. */ func RegisterClass(name string, definition CalendarDefinition, defaultFormat string) { - registeredCalendars[canonCalendarName(name)] = calendarRegistration{ - ToInternal: definition.ToInternal, - FromInternal: definition.FromInternal, - Offset: definition.Offset, - DefaultFormat: defaultFormat, - } + RegisterElements(name, definition.ToInternal, definition.FromInternal, definition.Offset, defaultFormat) } // RegisterElements registers a calendar system from its distinct functions. @@ -107,6 +102,10 @@ func ToInternal(calendar string, date interface{}, format string) (TAI64NAXURTim return TAI64NAXURTime{}, UnknownCalendarError } + if format == "" { + format = DefaultFormat(calendar) + } + // fmt.Printf("ToInternal: %#v (%#v) [%#v]\n", canonCalendarName(calendar), format, date) return registeredCalendars[canonCalendarName(calendar)].ToInternal(date, format) @@ -118,6 +117,10 @@ func FromInternal(calendar string, stamp TAI64NAXURTime, format string) (string, return "", UnknownCalendarError } + if format == "" { + format = DefaultFormat(calendar) + } + // fmt.Printf("FromInternal: %#v (%#v) [%#v]\n", canonCalendarName(calendar), format, stamp) return registeredCalendars[canonCalendarName(calendar)].FromInternal(stamp, format) diff --git a/calendars/0calendars_test.go b/calendars/0calendars_test.go new file mode 100644 index 0000000..d260c13 --- /dev/null +++ b/calendars/0calendars_test.go @@ -0,0 +1,147 @@ +package calendars + +import ( + "errors" + "testing" +) + +var testCalendarElements calendarRegistration + +type testCalendarClass struct { + DefaultFormat string +} + +func (testCalendarClass) ToInternal(in interface{}, mod string) (out TAI64NAXURTime, err error) { + err = errors.New("testToInternal") + return +} + +func (testCalendarClass) FromInternal(in TAI64NAXURTime, mod string) (out string, err error) { + err = errors.New("testFromInternal") + return +} + +func (testCalendarClass) Offset(in TAI64NAXURTime, mod interface{}) (out TAI64NAXURTime, err error) { + err = errors.New("testOffset") + return +} + +func init() { + testCalendarElements.ToInternal = func(in interface{}, mod string) (out TAI64NAXURTime, err error) { + return + } + testCalendarElements.FromInternal = func(in TAI64NAXURTime, mod string) (out string, err error) { + return + } + testCalendarElements.Offset = func(in TAI64NAXURTime, mod interface{}) (out TAI64NAXURTime, err error) { + return + } + testCalendarElements.DefaultFormat = "test" +} + +func TestRegisterClass(t *testing.T) { + instance := testCalendarClass{} + RegisterClass("testCalendarClass", instance, instance.DefaultFormat) + + if !Registered("testCalendarClass") { + t.Errorf("RegisterClass(%#v, %#v) failed", "testCalendarClass", instance) + } +} + +func TestRegisterElements(t *testing.T) { + RegisterElements("testCalendarElements", testCalendarElements.ToInternal, testCalendarElements.FromInternal, testCalendarElements.Offset, testCalendarElements.DefaultFormat) + + if !Registered("testCalendarElements") { + t.Errorf("RegisterElements(%#v, %#v) failed", "testCalendarElements", testCalendarElements) + } +} + +func TestRegistered(t *testing.T) { + if Registered("testCalendar") { + t.Errorf("Registered(testCalendar) failed - the calendar should not be registered before the test starts") + } + + instance := testCalendarClass{"test"} + RegisterClass("testCalendar", instance, instance.DefaultFormat) + + if !Registered("testCalendar") { + t.Errorf("Registered(testCalendar) failed - the calendar should be registered before the test ends") + } +} + +func TestDefaultFormat(t *testing.T) { + if !Registered("testCalendar") { + TestRegistered(t) + } + + if DefaultFormat("testCalendar") != "test" { + t.Errorf("DefaultFormat(testCalendar) failed - got %#v, but wanted %#v", DefaultFormat("testCalendar"), "test") + } + + if DefaultFormat("invalid") != "" { + t.Errorf("DefaultFormat(invalid) failed - got %#v, but wanted %#v", DefaultFormat("invalid"), "") + } +} + +func TestToInternal(t *testing.T) { + if !Registered("testCalendar") { + TestRegistered(t) + } + + _, err := ToInternal("testCalendar", "", "") + + if err == nil || err.Error() != "testToInternal" { + t.Errorf("ToInternal(\"testCalendar\", \"\", \"\") failed - got %#v, but wanted %#v", err.Error(), "testToInternal") + } + + _, err = ToInternal("invalid", "", "") + + if err != UnknownCalendarError { + t.Errorf("ToInternal(\"invalid\", \"\", \"\") failed - got %q, but wanted %q", err, UnknownCalendarError) + } +} + +func TestFromInternal(t *testing.T) { + if !Registered("testCalendar") { + TestRegistered(t) + } + + _, err := FromInternal("testCalendar", TAI64NAXURTime{}, "") + + if err == nil || err.Error() != "testFromInternal" { + t.Errorf("FromInternal(\"testCalendar\", TAI64NAXURTime{}, \"\") failed - got %#v, but wanted %#v", err.Error(), "testFromInternal") + } + + _, err = FromInternal("invalid", TAI64NAXURTime{}, "") + + if err != UnknownCalendarError { + t.Errorf("FromInternal(\"invalid\", \"\", \"\") failed - got %q, but wanted %q", err, UnknownCalendarError) + } +} + +func TestOffset(t *testing.T) { + if !Registered("testCalendar") { + TestRegistered(t) + } + + _, err := Offset("testCalendar", TAI64NAXURTime{}, "") + + if err == nil || err.Error() != "testOffset" { + t.Errorf("Offset(\"testCalendar\", TAI64NAXURTime{}, \"\") failed - got %#v, but wanted %#v", err.Error(), "testOffset") + } + + _, err = Offset("invalid", TAI64NAXURTime{}, "") + + if err != UnknownCalendarError { + t.Errorf("Offset(\"invalid\", \"\", \"\") failed - got %q, but wanted %q", err, UnknownCalendarError) + } +} + +func TestCanonCalendarName(t *testing.T) { + in := "testCalendar" + out := canonCalendarName(in) + + if out != "Testcalendar" { + t.Errorf("canonCalendarName(%#v) failed - got %#v, but wanted %#v", in, out, "Testcalendar") + } +} diff --git a/calendars/0internal.go b/calendars/0internal.go index 7c716b2..e79029d 100644 --- a/calendars/0internal.go +++ b/calendars/0internal.go @@ -83,47 +83,12 @@ func (t TAI64NAXURTime) String() string { return out } -// TAI64NAXURTimeFromDecimalString calculates a TAI64NAXURTime from its decimal -// string representation. -func TAI64NAXURTimeFromDecimalString(in string) TAI64NAXURTime { - out, _ := ToInternal("tai64", in, "decimal") - // fmt.Printf("TAI64NAXURTimeFromDecimalString: %#v → %#v [%#v]\n", in, out, err) - return out -} - -// TAI64NAXURTimeFromHexString calculates a TAI64NAXURTime from its hexadecimal -// string representation. -func TAI64NAXURTimeFromHexString(in string) TAI64NAXURTime { - out, _ := ToInternal("tai64", in, "tai64naxur") - // fmt.Printf("TAI64NAXURTimeFromHexString: %#v → %#v [%#v]\n", in, out, err) - return out -} - -// Float returns the math.big.Float representation of the TAI64NAXURTime value. +// Float returns the math/big.Float representation of the TAI64NAXURTime value. func (t TAI64NAXURTime) Float() *big.Float { out, _, _ := big.ParseFloat(t.String(), 10, 176, big.ToNearestAway) return out } -// TAI64NAXURTimeFromFloat calculates a TAI64NAXURTime from its math.big.Float -// representation. -func TAI64NAXURTimeFromFloat(in big.Float) TAI64NAXURTime { - // fmt.Printf("TAI64NAXURTimeFromFloat: %#v\n", in) - return TAI64NAXURTimeFromDecimalString(in.Text('f', 45)) -} - -func rollOverAt9(value int32) (roll int32, remain uint32) { - if value >= 0 { - roll = value / 1000000000 - } else { - roll = 1 - } - - remain = uint32(math.Mod(float64(value), 1000000000)) - - return -} - // MarshalText implements the encoding.TextMarshaler interface. func (t TAI64NAXURTime) MarshalText() ([]byte, error) { out, err := FromInternal("tai64", t, "tai64naxur") @@ -133,7 +98,7 @@ func (t TAI64NAXURTime) MarshalText() ([]byte, error) { // UnmarshalText implements the encoding.TextUnmarshaler interface. func (t *TAI64NAXURTime) UnmarshalText(in []byte) error { tmp, err := ToInternal("tai64", in, "tai64naxur") - t = &tmp + *t = tmp return err } @@ -144,18 +109,39 @@ func (t *TAI64NAXURTime) MarshalBinary() (out []byte, err error) { return } - _, err = hex.Decode(out, in) + out, err = hex.DecodeString(string(in)) return } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. func (t *TAI64NAXURTime) UnmarshalBinary(in []byte) error { - var out []byte + out := hex.EncodeToString(in) - _ = hex.Encode(out, in) + return t.UnmarshalText([]byte(out)) +} - return t.UnmarshalText(out) +// TAI64NAXURTimeFromDecimalString calculates a TAI64NAXURTime from its decimal +// string representation. +func TAI64NAXURTimeFromDecimalString(in string) TAI64NAXURTime { + out, _ := ToInternal("tai64", in, "decimal") + // fmt.Printf("TAI64NAXURTimeFromDecimalString: %#v → %#v [%#v]\n", in, out, err) + return out +} + +// TAI64NAXURTimeFromHexString calculates a TAI64NAXURTime from its hexadecimal +// string representation. +func TAI64NAXURTimeFromHexString(in string) TAI64NAXURTime { + out, _ := ToInternal("tai64", in, "tai64naxur") + // fmt.Printf("TAI64NAXURTimeFromHexString: %#v → %#v [%#v]\n", in, out, err) + return out +} + +// TAI64NAXURTimeFromFloat calculates a TAI64NAXURTime from its math/big.Float +// representation. +func TAI64NAXURTimeFromFloat(in big.Float) TAI64NAXURTime { + // fmt.Printf("TAI64NAXURTimeFromFloat: %#v\n", in) + return TAI64NAXURTimeFromDecimalString(in.Text('f', 45)) } // UTCtoTAI removes the UTC leap second offset from a TAI64NAXURTime value. @@ -184,3 +170,15 @@ func getTAIOffset(year, month, day int) (offset TAI64NAXURTime, err error) { // Look up the offset return } + +func rollOverAt9(value int32) (roll int32, remain uint32) { + if value >= 0 { + roll = value / 1000000000 + } else { + roll = 1 + } + + remain = uint32(math.Mod(float64(value), 1000000000)) + + return +} diff --git a/calendars/0internal_test.go b/calendars/0internal_test.go new file mode 100644 index 0000000..8b44ce8 --- /dev/null +++ b/calendars/0internal_test.go @@ -0,0 +1,183 @@ +package calendars + +import ( + "math/big" + "testing" +) + +func TestAdd(t *testing.T) { + in := TAI64NAXURTime{Seconds: 1} + and := TAI64NAXURTime{Seconds: 2} + want := TAI64NAXURTime{Seconds: 3} + got := in.Add(and) + + if got != want { + t.Errorf("%#v.Add(%#v) failed\ngot %#v\nwant %#v", in, and, got, want) + } +} + +func TestSub(t *testing.T) { + in := TAI64NAXURTime{Seconds: 1} + and := TAI64NAXURTime{Seconds: 2} + want := TAI64NAXURTime{Seconds: -1} + got := in.Sub(and) + + if got != want { + t.Errorf("%#v.Sub(%#v) failed\ngot %#v\nwant %#v", in, and, got, want) + } +} + +func TestString(t *testing.T) { + in := TAI64NAXURTime{Seconds: 1} + want := "1" + got := in.String() + + if got != want { + t.Errorf("%#v.String() failed\ngot %#v\nwant %#v", in, got, want) + } +} + +func TestFloat(t *testing.T) { + in := TAI64NAXURTime{Seconds: 1} + want := big.NewFloat(1) + got := in.Float() + + if got.String() != want.String() { + t.Errorf("%#v.Float() failed\ngot %#v\nwant %#v", in, got, want) + } +} + +func TestMarshalText(t *testing.T) { + in := TAI64NAXURTime{Seconds: 1} + want := "40000000000000010000000000000000000000000000000000000000" + got, _ := in.MarshalText() + + if string(got) != want { + t.Errorf("%#v.MarshalText() failed\ngot %#v\nwant %#v", in, string(got), want) + } +} + +func TestUnmarshalText(t *testing.T) { + var got TAI64NAXURTime + in := []byte("40000000000000010000000000000000000000000000000000000000") + want := TAI64NAXURTime{Seconds: 1} + got.UnmarshalText(in) + + if got != want { + t.Errorf("empty.UnmarshalText(%#v) failed\ngot %#v\nwant %#v", string(in), got, want) + } +} + +func TestMarshalBinary(t *testing.T) { + in := TAI64NAXURTime{Seconds: 1} + want := "@\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + got, _ := in.MarshalBinary() + + if string(got) != want { + t.Errorf("%#v.MarshalBinary() failed\ngot %#v\nwant %#v", in, string(got), want) + } +} + +func TestUnmarshalBinary(t *testing.T) { + var got TAI64NAXURTime + in := []byte("@\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") + want := TAI64NAXURTime{Seconds: 1} + got.UnmarshalBinary(in) + + if got != want { + t.Errorf("empty.UnmarshalBinary(%#v) failed\ngot %#v\nwant %#v", string(in), got, want) + } +} + +func TestTAI64NAXURTimeFromDecimalString(t *testing.T) { + in := "1" + want := TAI64NAXURTime{Seconds: 1} + got := TAI64NAXURTimeFromDecimalString(in) + + if got != want { + t.Errorf("TAI64NAXURTimeFromDecimalString(%#v) failed\ngot %#v\nwant %#v", in, got, want) + } +} + +func TestTAI64NAXURTimeFromHexString(t *testing.T) { + in := "40000000000000010000000000000000000000000000000000000000" + want := TAI64NAXURTime{Seconds: 1} + got := TAI64NAXURTimeFromHexString(in) + + if got != want { + t.Errorf("TAI64NAXURTimeFromHexString(%#v) failed\ngot %#v\nwant %#v", in, got, want) + } +} + +func TestTAI64NAXURTimeFromFloat(t *testing.T) { + in := *big.NewFloat(1) + want := TAI64NAXURTime{Seconds: 1} + got := TAI64NAXURTimeFromFloat(in) + + if got != want { + t.Errorf("TAI64NAXURTimeFromFloat(%#v) failed\ngot %#v\nwant %#v", in, got, want) + } +} + +func TestUTCtoTAI(t *testing.T) { + in := TAI64NAXURTime{Seconds: 1} + want := TAI64NAXURTime{Seconds: 1} + got, err := UTCtoTAI(in) + + if err != nil { + t.Errorf("UTCtoTAI(%#v) returned error %q", in, err) + } + if got != want { + t.Errorf("UTCtoTAI(%#v) failed\ngot %#v\nwant %#v", in, got, want) + } +} + +func TestTAItoUTC(t *testing.T) { + in := TAI64NAXURTime{Seconds: 1} + want := TAI64NAXURTime{Seconds: 1} + got, err := TAItoUTC(in) + + if err != nil { + t.Errorf("TAItoUTC(%#v) returned error %q", in, err) + } + if got != want { + t.Errorf("TAItoUTC(%#v) failed\ngot %#v\nwant %#v", in, got, want) + } +} + +func TestGetTAIOffset(t *testing.T) { + year, month, day := 1970, 1, 1 + want := TAI64NAXURTime{} + got, err := getTAIOffset(year, month, day) + + if err != nil { + t.Errorf("getTAIOffset(%#v, %#v, %#v) returned error %q", year, month, day, err) + } + if got != want { + t.Errorf("getTAIOffset(%#v, %#v, %#v) failed\ngot %#v\nwant %#v", year, month, day, got, want) + } +} + +func TestRollOverAt9(t *testing.T) { + type result struct { + Roll int32 + Remain uint32 + } + var got result + + in := int32(1000000001) + want := result{1, 1} + got.Roll, got.Remain = rollOverAt9(in) + + if got != want { + t.Errorf("rollOverAt9(%#v) failed\ngot %#v\nwant %#v", in, got, want) + } + + in = int32(-1000000000) + want = result{1, 0} + got.Roll, got.Remain = rollOverAt9(in) + + if got != want { + t.Errorf("rollOverAt9(%#v) failed\ngot %#v\nwant %#v", in, got, want) + } +} diff --git a/calendars/gregorian.go b/calendars/gregorian.go index add0647..1716c52 100644 --- a/calendars/gregorian.go +++ b/calendars/gregorian.go @@ -10,7 +10,7 @@ Supported Input Types: - time.Time (time.Duration for Offset) Supported Format Strings: - - any format supported by the time library or github.com/jeffjen/datefmt + - any format supported by the time library or github.com/danhunsaker/go-datefmt (or github.com/olebedev/when for Offset) */ @@ -21,7 +21,7 @@ import ( "strings" "time" - datefmt "github.com/jeffjen/datefmt" + datefmt "github.com/danhunsaker/go-datefmt" when "github.com/olebedev/when" when_common "github.com/olebedev/when/rules/common" when_en "github.com/olebedev/when/rules/en" @@ -44,7 +44,8 @@ func init() { case []byte: str = string(date.([]byte)) default: - err = InvalidFormatError + err = UnsupportedInputError + return } if str != "" { @@ -80,7 +81,11 @@ func init() { switch offset.(type) { case time.Duration: - str = offset.(time.Duration).String() + dur := offset.(time.Duration) + r := time.Unix(in.Seconds, int64(in.Nano)).Add(dur) + out.Seconds = r.Unix() + out.Nano = uint32(r.Nanosecond()) + return case string: str = offset.(string) case []byte: diff --git a/calendars/gregorian_test.go b/calendars/gregorian_test.go new file mode 100644 index 0000000..257fb2b --- /dev/null +++ b/calendars/gregorian_test.go @@ -0,0 +1,71 @@ +package calendars + +import ( + "testing" + "time" +) + +func TestGregorianToInternal(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{"Thu, 01 Jan 1970 00:00:01 UTC", ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"1970-01-01 00:00:01 UTC", "2006-01-02 15:04:05 MST"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"1970-01-01 00:00:01 UTC", "%Y-%m-%d %H:%M:%S %Z"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{[]byte("Thu, 01 Jan 1970 00:00:01 UTC"), ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{time.Unix(1, 0), ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + + {"in": []interface{}{1, ""}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + {"in": []interface{}{1., ""}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + {"in": []interface{}{1.0, ""}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, ""}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + } + + for _, c := range cases { + out, err := ToInternal("gregorian", c["in"][0], c["in"][1].(string)) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("GregorianToInternal(%#v, %#v) gave error %#v; want %#v", c["in"][0], c["in"][1].(string), err, c["want"][1]) + } + if out != c["want"][0].(TAI64NAXURTime) { + t.Errorf("GregorianToInternal(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0], c["in"][1].(string), out, c["want"][0].(TAI64NAXURTime)) + } + } +} + +func TestGregorianFromInternal(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, ""}, "want": []interface{}{"Thu, 01 Jan 1970 00:00:01 UTC", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "2006-01-02 15:04:05 MST"}, "want": []interface{}{"1970-01-01 00:00:01 UTC", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "%Y-%m-%d %H:%M:%S %Z"}, "want": []interface{}{"1970-01-01 00:00:01 UTC", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "%Y-%m-%d %H:%M:%S %z"}, "want": []interface{}{"1970-01-01 00:00:01 +0000", nil}}, + } + + for _, c := range cases { + out, err := FromInternal("gregorian", c["in"][0].(TAI64NAXURTime), c["in"][1].(string)) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("GregorianFromInternal(%#v, %#v) gave error %#v; want %#v", c["in"][0], c["in"][1].(string), err, c["want"][1]) + } + if out != c["want"][0].(string) { + t.Errorf("GregorianFromInternal(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0], c["in"][1].(string), out, c["want"][0].(string)) + } + } +} + +func TestGregorianOffset(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{TAI64NAXURTime{}, "in 1 second"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, []byte("in 1 second")}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, time.Second}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + + {"in": []interface{}{TAI64NAXURTime{}, "in 17 bloxnards"}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + {"in": []interface{}{TAI64NAXURTime{}, TAI64NAXURTime{Seconds: 1}}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + } + + for _, c := range cases { + out, err := Offset("gregorian", c["in"][0].(TAI64NAXURTime), c["in"][1]) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("GregorianOffset(%#v, %#v) gave error %#v; want %#v", c["in"][0].(TAI64NAXURTime), c["in"][1], err, c["want"][1]) + } + if out != c["want"][0].(TAI64NAXURTime) { + t.Errorf("GregorianOffset(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0].(TAI64NAXURTime), c["in"][1], out, c["want"][0].(TAI64NAXURTime)) + } + } +} diff --git a/calendars/tai64.go b/calendars/tai64.go index e2c115b..0413eb9 100644 --- a/calendars/tai64.go +++ b/calendars/tai64.go @@ -58,7 +58,7 @@ func init() { if len(tmp) < 2 { tmp = append(tmp, "0") } - _, err = fmt.Sscanf(fmt.Sprintf("%020s.%-045s", tmp[0], tmp[1]), "%020d.%09d%09d%09d%09d%09d", &stamp.Seconds, &stamp.Nano, &stamp.Atto, &stamp.Xicto, &stamp.Ucto, &stamp.Rocto) + _, err = fmt.Sscanf(fmt.Sprintf("%s %-045s", tmp[0], tmp[1]), "%d %09d%09d%09d%09d%09d", &stamp.Seconds, &stamp.Nano, &stamp.Atto, &stamp.Xicto, &stamp.Ucto, &stamp.Rocto) case "tai64naxur": _, err = fmt.Sscanf(dateString, "%016X%08X%08X%08X%08X%08X", &stamp.Seconds, &stamp.Nano, &stamp.Atto, &stamp.Xicto, &stamp.Ucto, &stamp.Rocto) case "tai64naxu": @@ -75,7 +75,11 @@ func init() { err = InvalidFormatError } - if format != "decimal" { + if err != nil && err.Error() == "EOF" { + err = nil + } + + if format != "decimal" && err == nil { stamp.Seconds -= 0x4000000000000000 } @@ -111,6 +115,8 @@ func init() { // TODO - other types case big.Float: adjust = TAI64NAXURTimeFromFloat(offset.(big.Float)) + case *big.Float: + adjust = TAI64NAXURTimeFromFloat(*offset.(*big.Float)) case []byte: adjust = TAI64NAXURTimeFromDecimalString(string(offset.([]byte))) case string: diff --git a/calendars/tai64_test.go b/calendars/tai64_test.go new file mode 100644 index 0000000..bf0f859 --- /dev/null +++ b/calendars/tai64_test.go @@ -0,0 +1,79 @@ +package calendars + +import ( + "math/big" + "testing" +) + +func TestTai64ToInternal(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{"1", "decimal"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"1.", "decimal"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"1.0", "decimal"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"40000000000000010000000000000000000000000000000000000000", "tai64naxur"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"400000000000000100000000000000000000000000000000", "tai64naxu"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"4000000000000001000000000000000000000000", "tai64nax"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"40000000000000010000000000000000", "tai64na"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"400000000000000100000000", "tai64n"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"4000000000000001", "tai64"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"4000000000000001", "invalid"}, "want": []interface{}{TAI64NAXURTime{}, InvalidFormatError}}, + + {"in": []interface{}{[]byte("40000000000000010000000000000000000000000000000000000000"), "tai64naxur"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "tai64naxur"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{1, "tai64naxur"}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + } + + for _, c := range cases { + out, err := ToInternal("tai64", c["in"][0], c["in"][1].(string)) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("Tai64ToInternal(%#v, %#v) gave error %#v; want %#v", c["in"][0], c["in"][1].(string), err, c["want"][1]) + } + if out != c["want"][0].(TAI64NAXURTime) { + t.Errorf("Tai64ToInternal(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0], c["in"][1].(string), out, c["want"][0].(TAI64NAXURTime)) + } + } +} + +func TestTai64FromInternal(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "decimal"}, "want": []interface{}{"1", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "tai64naxur"}, "want": []interface{}{"40000000000000010000000000000000000000000000000000000000", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "tai64naxu"}, "want": []interface{}{"400000000000000100000000000000000000000000000000", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "tai64nax"}, "want": []interface{}{"4000000000000001000000000000000000000000", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "tai64na"}, "want": []interface{}{"40000000000000010000000000000000", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "tai64n"}, "want": []interface{}{"400000000000000100000000", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "tai64"}, "want": []interface{}{"4000000000000001", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "invalid"}, "want": []interface{}{"", InvalidFormatError}}, + } + + for _, c := range cases { + out, err := FromInternal("tai64", c["in"][0].(TAI64NAXURTime), c["in"][1].(string)) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("Tai64FromInternal(%#v, %#v) gave error %#v; want %#v", c["in"][0], c["in"][1].(string), err, c["want"][1]) + } + if out != c["want"][0].(string) { + t.Errorf("Tai64FromInternal(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0], c["in"][1].(string), out, c["want"][0].(string)) + } + } +} + +func TestTai64Offset(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{TAI64NAXURTime{}, "1"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, []byte("1")}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, big.NewFloat(1)}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, *big.NewFloat(1)}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, TAI64NAXURTime{Seconds: 1}}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, 1}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + } + + for _, c := range cases { + out, err := Offset("tai64", c["in"][0].(TAI64NAXURTime), c["in"][1]) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("Tai64Offset(%#v, %#v) gave error %#v; want %#v", c["in"][0].(TAI64NAXURTime), c["in"][1], err, c["want"][1]) + } + if out != c["want"][0].(TAI64NAXURTime) { + t.Errorf("Tai64Offset(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0].(TAI64NAXURTime), c["in"][1], out, c["want"][0].(TAI64NAXURTime)) + } + } +} diff --git a/calendars/unix.go b/calendars/unix.go index 39966f1..bfdea3f 100644 --- a/calendars/unix.go +++ b/calendars/unix.go @@ -30,11 +30,13 @@ func init() { switch date.(type) { // TODO - other types case int: - stamp = TAI64NAXURTimeFromDecimalString(string(date.(int))) + stamp = TAI64NAXURTimeFromDecimalString(fmt.Sprintf("%d", date.(int))) case float64: stamp = TAI64NAXURTimeFromDecimalString(fmt.Sprintf("%f", date.(float64))) case big.Float: stamp = TAI64NAXURTimeFromFloat(date.(big.Float)) + case *big.Float: + stamp = TAI64NAXURTimeFromFloat(*date.(*big.Float)) case []byte: stamp = TAI64NAXURTimeFromDecimalString(string(date.([]byte))) case string: diff --git a/calendars/unix_test.go b/calendars/unix_test.go new file mode 100644 index 0000000..c63ae57 --- /dev/null +++ b/calendars/unix_test.go @@ -0,0 +1,73 @@ +package calendars + +import ( + "math/big" + "testing" +) + +func TestUnixToInternal(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{1, ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{1., ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{1.0, ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"1", ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"1.", ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{"1.0", ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{big.NewFloat(1), ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{*big.NewFloat(1), ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{[]byte("1"), ""}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, ""}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + } + + for _, c := range cases { + out, err := ToInternal("unix", c["in"][0], c["in"][1].(string)) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("UnixToInternal(%#v, %#v) gave error %#v; want %#v", c["in"][0], c["in"][1].(string), err, c["want"][1]) + } + if out != c["want"][0].(TAI64NAXURTime) { + t.Errorf("UnixToInternal(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0], c["in"][1].(string), out, c["want"][0].(TAI64NAXURTime)) + } + } +} + +func TestUnixFromInternal(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, ""}, "want": []interface{}{"1.000000000", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "%f"}, "want": []interface{}{"1.000000", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "%.45f"}, "want": []interface{}{"1.000000000000000000000000000000000000000000000", nil}}, + {"in": []interface{}{TAI64NAXURTime{Seconds: 1}, "%.0f"}, "want": []interface{}{"1", nil}}, + } + + for _, c := range cases { + out, err := FromInternal("unix", c["in"][0].(TAI64NAXURTime), c["in"][1].(string)) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("UnixFromInternal(%#v, %#v) gave error %#v; want %#v", c["in"][0], c["in"][1].(string), err, c["want"][1]) + } + if out != c["want"][0].(string) { + t.Errorf("UnixFromInternal(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0], c["in"][1].(string), out, c["want"][0].(string)) + } + } +} + +func TestUnixOffset(t *testing.T) { + cases := []map[string][]interface{}{ + {"in": []interface{}{TAI64NAXURTime{}, 1}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, 1.0}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, "1"}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, []byte("1")}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, big.NewFloat(1)}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, *big.NewFloat(1)}, "want": []interface{}{TAI64NAXURTime{Seconds: 1}, nil}}, + {"in": []interface{}{TAI64NAXURTime{}, TAI64NAXURTime{Seconds: 1}}, "want": []interface{}{TAI64NAXURTime{}, UnsupportedInputError}}, + } + + for _, c := range cases { + out, err := Offset("unix", c["in"][0].(TAI64NAXURTime), c["in"][1]) + if (err != nil && c["want"][1] != nil && err.Error() != c["want"][1].(error).Error()) || (err == nil && err != c["want"][1]) || (c["want"][1] == nil && err != c["want"][1]) { + t.Errorf("UnixOffset(%#v, %#v) gave error %#v; want %#v", c["in"][0].(TAI64NAXURTime), c["in"][1], err, c["want"][1]) + } + if out != c["want"][0].(TAI64NAXURTime) { + t.Errorf("UnixOffset(%#v, %#v)\nreturned %#v\nwanted %#v", c["in"][0].(TAI64NAXURTime), c["in"][1], out, c["want"][0].(TAI64NAXURTime)) + } + } +} diff --git a/calends.go b/calends.go index 058599c..dbdddac 100644 --- a/calends.go +++ b/calends.go @@ -27,6 +27,7 @@ import ( "fmt" "io" "math/big" + "strings" "github.com/danhunsaker/calends/calendars" ) @@ -39,7 +40,7 @@ type Calends struct { } // The version of the library -var Version string = "0.0.0" +var Version string = "0.0.1" // Create is the mechanism for constructing new Calends objects. /* @@ -67,6 +68,8 @@ is ignored, and recalculated from the 'start' and 'end' values exclusively. */ func Create(stamp interface{}, calendar, format string) (instance Calends, err error) { + instance.duration = big.NewFloat(0.) + if stamp == nil { return } @@ -104,13 +107,13 @@ func Create(stamp interface{}, calendar, format string) (instance Calends, err e if hasStart { start, err = calendars.ToInternal(calendar, rawStart, format) if err != nil { - return Calends{}, err + return } } if hasEnd { end, err = calendars.ToInternal(calendar, rawEnd, format) if err != nil { - return Calends{}, err + return } } if hasDuration { @@ -243,10 +246,11 @@ func (c *Calends) UnmarshalText(text []byte) error { return err } -// MarshalJSON implements the encoding.json.Marshaler interface. +// MarshalJSON implements the encoding/json.Marshaler interface. func (c Calends) MarshalJSON() ([]byte, error) { if tmp, _ := c.duration.Int64(); tmp == 0 { - return c.startTime.MarshalText() + tmp, err := c.startTime.MarshalText() + return append(append([]byte{'"'}, tmp...), '"'), err } start, _ := c.startTime.MarshalText() @@ -255,7 +259,7 @@ func (c Calends) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf(`{"start":"%s","end":"%s"}`, start, end)), nil } -// UnmarshalJSON implements the encoding.json.Unmarshaler interface. +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. func (c *Calends) UnmarshalJSON(text []byte) error { var startTime, endTime calendars.TAI64NAXURTime @@ -272,9 +276,9 @@ func (c *Calends) UnmarshalJSON(text []byte) error { return errors.New("(*Calends).UnmarshalJSON [convert end time " + parsed["end"] + "]: " + err.Error()) } } else { - err = startTime.UnmarshalText(text) + err = startTime.UnmarshalText([]byte(strings.Trim(string(text), `"`))) if err != nil { - return errors.New("(*Calends).UnmarshalJSON [convert time " + string(text) + "]: " + err.Error()) + return errors.New("(*Calends).UnmarshalJSON [convert time " + strings.Trim(string(text), `"`) + "]: " + err.Error()) } endTime = startTime @@ -285,7 +289,7 @@ func (c *Calends) UnmarshalJSON(text []byte) error { "end": endTime, }, "tai64", "") - c = &temp + *c = temp if err != nil { err = errors.New("(*Calends).UnmarshalJSON [set values]: " + err.Error()) diff --git a/calends_test.go b/calends_test.go new file mode 100644 index 0000000..32abdde --- /dev/null +++ b/calends_test.go @@ -0,0 +1,259 @@ +package calends + +import ( + "encoding/json" + "errors" + "math/big" + "testing" + + "github.com/danhunsaker/calends/calendars" +) + +func testValue(dur float64) Calends { + out, _ := Create(map[string]interface{}{"start": 0, "duration": dur}, "", "") + return out +} + +func TestCreate(t *testing.T) { + var got interface{} + var err error + + successCases := []struct { + in []interface{} + }{ + {[]interface{}{nil, "", ""}}, + + {[]interface{}{0, "", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "end": 0}, "", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "duration": 0}, "", ""}}, + {[]interface{}{map[string]interface{}{"duration": 0, "end": 0}, "", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "duration": 0, "end": 0}, "", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "duration": big.NewFloat(0), "end": 0}, "", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "duration": *big.NewFloat(0), "end": 0}, "", ""}}, + + {[]interface{}{0, "unix", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "end": 0}, "unix", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "duration": 0}, "unix", ""}}, + {[]interface{}{map[string]interface{}{"duration": 0, "end": 0}, "unix", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "duration": 0, "end": 0}, "unix", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "duration": big.NewFloat(0), "end": 0}, "unix", ""}}, + {[]interface{}{map[string]interface{}{"start": 0, "duration": *big.NewFloat(0), "end": 0}, "unix", ""}}, + } + + failureCases := []struct { + in []interface{} + want error + }{ + {[]interface{}{0, "invalid", ""}, calendars.UnknownCalendarError}, + + {[]interface{}{map[string]interface{}{"start": calendars.InvalidFormatError}, "", ""}, calendars.UnsupportedInputError}, + {[]interface{}{map[string]interface{}{"end": calendars.InvalidFormatError}, "", ""}, calendars.UnsupportedInputError}, + {[]interface{}{map[string]interface{}{"duration": 0}, "", ""}, calendars.UnsupportedInputError}, + + {[]interface{}{map[string]interface{}{"duration": calendars.InvalidFormatError}, "", ""}, errors.New("Invalid Duration Type")}, + } + + for _, c := range successCases { + got, err = Create(c.in[0], c.in[1].(string), c.in[2].(string)) + if err != nil { + t.Errorf("Create(%q) gave error %q", c.in, err) + } + switch got := got.(type) { + case Calends: + if got.startTime.String() != "0" { + t.Errorf("Create(%q) returned with startTime %#v", c.in, got.startTime) + } + if got.duration.String() != "0" { + t.Errorf("Create(%q) returned with duration %#v", c.in, got.duration) + } + if got.endTime.String() != "0" { + t.Errorf("Create(%q) returned with endTime %#v", c.in, got.endTime) + } + default: + t.Errorf("Create(%q)\n == %#v,\n want %#v", c.in, got, "Calends") + } + } + + for _, c := range failureCases { + _, err = Create(c.in[0], c.in[1].(string), c.in[2].(string)) + + if err == nil { + t.Errorf("Create(%q) didn't give error; expected %q", c.in, c.want) + } + if err.Error() != c.want.Error() { + t.Errorf("Create(%q) gave error %q; expected %q", c.in, err, c.want) + } + } +} + +func TestDate(t *testing.T) { + got, err := testValue(0).Date("unix", "") + if err != nil { + t.Errorf("Date(%q) gave error %q", []interface{}{"unix", ""}, err) + } + if got != "0.000000000" { + t.Errorf("Date(%q)\n == %q,\n want %q", []interface{}{"unix", ""}, got, "0.000000000") + } + + got, err = testValue(0).Date("", "") + if err != nil { + t.Errorf("Date(%q) gave error %q", []interface{}{"", ""}, err) + } + if got != "0.000000000" { + t.Errorf("Date(%q)\n == %q,\n want %q", []interface{}{"", ""}, got, "0.000000000") + } + + got, err = testValue(0).Date("invalid", "") + if err == nil { + t.Errorf("Date(%q) didn't give error; expected %q", []interface{}{"invalid", ""}, err) + } + if err != calendars.UnknownCalendarError { + t.Errorf("Date(%q) gave error %q; expected %q", []interface{}{"invalid", ""}, err, calendars.UnknownCalendarError) + } +} + +func TestDuration(t *testing.T) { + got := testValue(0).Duration() + if got.String() != "0" { + t.Errorf("Duration()\n == %q,\n want %q", got.String(), "0") + } +} + +func TestEndDate(t *testing.T) { + got, err := testValue(0).EndDate("unix", "") + if err != nil { + t.Errorf("EndDate(%q) gave error %q", []interface{}{"unix", ""}, err) + } + if got != "0.000000000" { + t.Errorf("EndDate(%q)\n == %q,\n want %q", []interface{}{"unix", ""}, got, "0.000000000") + } + + got, err = testValue(0).EndDate("", "") + if err != nil { + t.Errorf("EndDate(%q) gave error %q", []interface{}{"", ""}, err) + } + if got != "0.000000000" { + t.Errorf("EndDate(%q)\n == %q,\n want %q", []interface{}{"", ""}, got, "0.000000000") + } + + got, err = testValue(0).EndDate("invalid", "") + if err == nil { + t.Errorf("EndDate(%q) didn't give error; expected %q", []interface{}{"invalid", ""}, err) + } + if err != calendars.UnknownCalendarError { + t.Errorf("EndDate(%q) gave error %q; expected %q", []interface{}{"invalid", ""}, err, calendars.UnknownCalendarError) + } +} + +func TestMarshalText(t *testing.T) { + zero, err := testValue(0).MarshalText() + if err != nil { + t.Errorf("zero.MarshalText() gave error %q", err) + } + if string(zero) != "40000000000000000000000000000000000000000000000000000000" { + t.Errorf("zero.MarshalText()\n == %q,\n want %q", zero, "40000000000000000000000000000000000000000000000000000000") + } + + day, err := testValue(86400).MarshalText() + if err != nil { + t.Errorf("day.MarshalText() gave error %q", err) + } + if string(day) != "from 40000000000000000000000000000000000000000000000000000000 to 40000000000151800000000000000000000000000000000000000000" { + t.Errorf("day.MarshalText()\n == %q,\n want %q", day, "from 40000000000000000000000000000000000000000000000000000000 to 40000000000151800000000000000000000000000000000000000000") + } +} + +func TestUnmarshalText(t *testing.T) { + zero := testValue(0) + zeroOut, _ := zero.MarshalText() + err := zero.UnmarshalText(zeroOut) + if err != nil { + t.Errorf("zero.UnmarshalText(%q) gave error %q", zeroOut, err) + } + if zero.startTime != testValue(0).startTime || zero.endTime != testValue(0).endTime || zero.duration.String() != testValue(0).duration.String() { + t.Errorf("zero.UnmarshalText(%q)\n == %#v,\n want %#v", zeroOut, zero, testValue(0)) + } + + day := testValue(86400) + dayOut, _ := day.MarshalText() + err = day.UnmarshalText(dayOut) + if err != nil { + t.Errorf("day.UnmarshalText(%q) gave error %q", dayOut, err) + } + if day.startTime != testValue(86400).startTime || day.endTime != testValue(86400).endTime || day.duration.String() != testValue(86400).duration.String() { + t.Errorf("day.UnmarshalText(%q)\n == %#v,\n want %#v", dayOut, day, testValue(86400)) + } +} + +func TestMarshalJSON(t *testing.T) { + zero, err := testValue(0).MarshalJSON() + if err != nil { + t.Errorf("zero.MarshalJSON() gave error %q", err) + } + if string(zero) != `"40000000000000000000000000000000000000000000000000000000"` { + t.Errorf("zero.MarshalJSON()\n == %q,\n want %q", zero, `"40000000000000000000000000000000000000000000000000000000"`) + } + + day, err := testValue(86400).MarshalJSON() + if err != nil { + t.Errorf("day.MarshalJSON() gave error %q", err) + } + if string(day) != `{"start":"40000000000000000000000000000000000000000000000000000000","end":"40000000000151800000000000000000000000000000000000000000"}` { + t.Errorf("day.MarshalJSON()\n == %q,\n want %q", day, `{"start":"40000000000000000000000000000000000000000000000000000000","end":"40000000000151800000000000000000000000000000000000000000"}`) + } + + both, err := json.Marshal(map[string]interface{}{ + "day": testValue(86400), + "zero": testValue(0), + }) + if err != nil { + t.Errorf("both.MarshalJSON() gave error %q", err) + } + if string(both) != `{"day":{"start":"40000000000000000000000000000000000000000000000000000000","end":"40000000000151800000000000000000000000000000000000000000"},"zero":"40000000000000000000000000000000000000000000000000000000"}` { + t.Errorf("both.MarshalJSON()\n == %q,\n want %q", both, `{"day":{"start":"40000000000000000000000000000000000000000000000000000000","end":"40000000000151800000000000000000000000000000000000000000"},"zero":"40000000000000000000000000000000000000000000000000000000"}`) + } +} + +func TestUnmarshalJSON(t *testing.T) { + var both map[string]Calends + var zero, day Calends + + zeroIn := testValue(0) + zeroOut, _ := zeroIn.MarshalJSON() + err := zero.UnmarshalJSON(zeroOut) + if err != nil { + t.Errorf("zero.UnmarshalJSON(%q) gave error %q\n", zeroOut, err) + } + if zero.startTime != zeroIn.startTime || zero.endTime != zeroIn.endTime || zero.duration.String() != zeroIn.duration.String() { + t.Errorf("zero.UnmarshalJSON(%q)\n == %#v,\n want %#v\n", zeroOut, zero, zeroIn) + } + + dayIn := testValue(86400) + dayOut, _ := dayIn.MarshalJSON() + err = day.UnmarshalJSON(dayOut) + if err != nil { + t.Errorf("day.UnmarshalJSON(%q) gave error %q\n", dayOut, err) + } + if day.startTime != dayIn.startTime || day.endTime != dayIn.endTime || day.duration.String() != dayIn.duration.String() { + t.Errorf("day.UnmarshalJSON(%q)\n == %#v,\n want %#v\n", dayOut, day, dayIn) + } + + bothIn := map[string]Calends{ + "day": dayIn, + "zero": zeroIn, + } + bothOut, _ := json.Marshal(bothIn) + err = json.Unmarshal(bothOut, &both) + if err != nil { + t.Errorf("both.UnmarshalJSON(%q) gave error %q\n", bothOut, err) + } + if both["zero"].startTime != bothIn["zero"].startTime || + both["day"].startTime != bothIn["day"].startTime || + both["zero"].endTime != bothIn["zero"].endTime || + both["day"].endTime != bothIn["day"].endTime || + both["zero"].duration.String() != bothIn["zero"].duration.String() || + both["day"].duration.String() != bothIn["day"].duration.String() { + + t.Errorf("both.UnmarshalJSON(%q)\n == %#v,\n want %#v\n", bothOut, both, bothIn) + } +} diff --git a/comparisons_test.go b/comparisons_test.go new file mode 100644 index 0000000..3d524dc --- /dev/null +++ b/comparisons_test.go @@ -0,0 +1,508 @@ +package calends + +import ( + "strconv" + "testing" +) + +func TestGetTimesByMode(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + x, y := getTimesByMode(test1, test2, "start") + if x.String() != "0" && y.String() != "0" { + t.Errorf("start fail...\ngot " + x.String() + "," + y.String() + "\nwant 0,0") + } + x, y = getTimesByMode(test1, test2, "start-end") + if x.String() != "0" && y.String() != "86400" { + t.Errorf("start-end fail...\ngot " + x.String() + "," + y.String() + "\nwant 0,86400") + } + x, y = getTimesByMode(test1, test2, "end-start") + if x.String() != "0" && y.String() != "0" { + t.Errorf("end-start fail...\ngot " + x.String() + "," + y.String() + "\nwant 0,0") + } + x, y = getTimesByMode(test1, test2, "end") + if x.String() != "0" && y.String() != "86400" { + t.Errorf("end fail...\ngot " + x.String() + "," + y.String() + "\nwant 0,86400") + } +} + +func TestCompareTimesByMode(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + cmp := compareTimesByMode(test1, test2, "start") + if cmp != 0 { + t.Errorf("1,2 start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = compareTimesByMode(test2, test1, "start") + if cmp != 0 { + t.Errorf("2,1 start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = compareTimesByMode(test1, test1, "start") + if cmp != 0 { + t.Errorf("1,1 start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = compareTimesByMode(test2, test2, "start") + if cmp != 0 { + t.Errorf("2,2 start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + + cmp = compareTimesByMode(test1, test2, "start-end") + if cmp != -1 { + t.Errorf("1,2 start-end fail...\ngot " + strconv.Itoa(cmp) + "\nwant -1") + } + cmp = compareTimesByMode(test2, test1, "start-end") + if cmp != 0 { + t.Errorf("2,1 start-end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = compareTimesByMode(test1, test1, "start-end") + if cmp != 0 { + t.Errorf("1,1 start-end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = compareTimesByMode(test2, test2, "start-end") + if cmp != -1 { + t.Errorf("2,2 start-end fail...\ngot " + strconv.Itoa(cmp) + "\nwant -1") + } + + cmp = compareTimesByMode(test1, test2, "end-start") + if cmp != 0 { + t.Errorf("1,2 end-start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = compareTimesByMode(test2, test1, "end-start") + if cmp != 1 { + t.Errorf("2,1 end-start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 1") + } + cmp = compareTimesByMode(test1, test1, "end-start") + if cmp != 0 { + t.Errorf("1,1 end-start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = compareTimesByMode(test2, test2, "end-start") + if cmp != 1 { + t.Errorf("2,2 end-start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 1") + } + + cmp = compareTimesByMode(test1, test2, "end") + if cmp != -1 { + t.Errorf("1,2 end fail...\ngot " + strconv.Itoa(cmp) + "\nwant -1") + } + cmp = compareTimesByMode(test2, test1, "end") + if cmp != 1 { + t.Errorf("2,1 end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 1") + } + cmp = compareTimesByMode(test1, test1, "end") + if cmp != 0 { + t.Errorf("1,1 end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = compareTimesByMode(test2, test2, "end") + if cmp != 0 { + t.Errorf("2,2 end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } +} + +func TestDifference(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + got := test1.Difference(test2, "start") + if got.String() != "0" { + t.Errorf("1,2 start fail...\ngot " + got.String() + "\nwant 0") + } + got = test2.Difference(test1, "start") + if got.String() != "0" { + t.Errorf("2,1 start fail...\ngot " + got.String() + "\nwant 0") + } + got = test1.Difference(test2, "start-end") + if got.String() != "-86400" { + t.Errorf("1,2 start-end fail...\ngot " + got.String() + "\nwant -86400") + } + got = test2.Difference(test1, "start-end") + if got.String() != "0" { + t.Errorf("2,1 start-end fail...\ngot " + got.String() + "\nwant 0") + } + got = test1.Difference(test2, "end-start") + if got.String() != "0" { + t.Errorf("1,2 end-start fail...\ngot " + got.String() + "\nwant 0") + } + got = test2.Difference(test1, "end-start") + if got.String() != "86400" { + t.Errorf("2,1 end-start fail...\ngot " + got.String() + "\nwant 86400") + } + got = test1.Difference(test2, "end") + if got.String() != "-86400" { + t.Errorf("1,2 end fail...\ngot " + got.String() + "\nwant -86400") + } + got = test2.Difference(test1, "end") + if got.String() != "86400" { + t.Errorf("2,1 end fail...\ngot " + got.String() + "\nwant 86400") + } + got = test1.Difference(test2, "duration") + if got.String() != "-86400" { + t.Errorf("1,2 duration fail...\ngot " + got.String() + "\nwant -86400") + } + got = test2.Difference(test1, "duration") + if got.String() != "86400" { + t.Errorf("2,1 duration fail...\ngot " + got.String() + "\nwant 86400") + } +} + +func TestCompare(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + cmp := test1.Compare(test2, "start") + if cmp != 0 { + t.Errorf("1,2 start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = test2.Compare(test1, "start") + if cmp != 0 { + t.Errorf("2,1 start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = test1.Compare(test1, "start") + if cmp != 0 { + t.Errorf("1,1 start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = test2.Compare(test2, "start") + if cmp != 0 { + t.Errorf("2,2 start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + + cmp = test1.Compare(test2, "start-end") + if cmp != -1 { + t.Errorf("1,2 start-end fail...\ngot " + strconv.Itoa(cmp) + "\nwant -1") + } + cmp = test2.Compare(test1, "start-end") + if cmp != 0 { + t.Errorf("2,1 start-end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = test1.Compare(test1, "start-end") + if cmp != 0 { + t.Errorf("1,1 start-end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = test2.Compare(test2, "start-end") + if cmp != -1 { + t.Errorf("2,2 start-end fail...\ngot " + strconv.Itoa(cmp) + "\nwant -1") + } + + cmp = test1.Compare(test2, "end-start") + if cmp != 0 { + t.Errorf("1,2 end-start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = test2.Compare(test1, "end-start") + if cmp != 1 { + t.Errorf("2,1 end-start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 1") + } + cmp = test1.Compare(test1, "end-start") + if cmp != 0 { + t.Errorf("1,1 end-start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = test2.Compare(test2, "end-start") + if cmp != 1 { + t.Errorf("2,2 end-start fail...\ngot " + strconv.Itoa(cmp) + "\nwant 1") + } + + cmp = test1.Compare(test2, "end") + if cmp != -1 { + t.Errorf("1,2 end fail...\ngot " + strconv.Itoa(cmp) + "\nwant -1") + } + cmp = test2.Compare(test1, "end") + if cmp != 1 { + t.Errorf("2,1 end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 1") + } + cmp = test1.Compare(test1, "end") + if cmp != 0 { + t.Errorf("1,1 end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } + cmp = test2.Compare(test2, "end") + if cmp != 0 { + t.Errorf("2,2 end fail...\ngot " + strconv.Itoa(cmp) + "\nwant 0") + } +} + +func TestIsSame(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.IsSame(test2) { + t.Errorf("1,2 failed") + } + if test2.IsSame(test1) { + t.Errorf("2,1 failed") + } + if !test1.IsSame(test1) { + t.Errorf("1,1 failed") + } + if !test2.IsSame(test2) { + t.Errorf("2,2 failed") + } +} + +func TestIsDuring(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if !test1.IsDuring(test2) { + t.Errorf("1,2 failed") + } + if test2.IsDuring(test1) { + t.Errorf("2,1 failed") + } + if !test1.IsDuring(test1) { + t.Errorf("1,1 failed") + } + if !test2.IsDuring(test2) { + t.Errorf("2,2 failed") + } +} + +func TestStartsDuring(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if !test1.StartsDuring(test2) { + t.Errorf("1,2 failed") + } + if test2.StartsDuring(test1) { + t.Errorf("2,1 failed") + } + if !test1.StartsDuring(test1) { + t.Errorf("1,1 failed") + } + if !test2.StartsDuring(test2) { + t.Errorf("2,2 failed") + } +} + +func TestEndsDuring(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if !test1.EndsDuring(test2) { + t.Errorf("1,2 failed") + } + if test2.EndsDuring(test1) { + t.Errorf("2,1 failed") + } + if !test1.EndsDuring(test1) { + t.Errorf("1,1 failed") + } + if !test2.EndsDuring(test2) { + t.Errorf("2,2 failed") + } +} + +func TestContains(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.Contains(test2) { + t.Errorf("1,2 failed") + } + if !test2.Contains(test1) { + t.Errorf("2,1 failed") + } + if !test1.Contains(test1) { + t.Errorf("1,1 failed") + } + if !test2.Contains(test2) { + t.Errorf("2,2 failed") + } +} + +func TestOverlaps(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if !test1.Overlaps(test2) { + t.Errorf("1,2 failed") + } + if !test2.Overlaps(test1) { + t.Errorf("2,1 failed") + } + if !test1.Overlaps(test1) { + t.Errorf("1,1 failed") + } + if !test2.Overlaps(test2) { + t.Errorf("2,2 failed") + } +} + +func TestAbuts(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.Abuts(test2) { + t.Errorf("1,2 failed") + } + if test2.Abuts(test1) { + t.Errorf("2,1 failed") + } + if test1.Abuts(test1) { + t.Errorf("1,1 failed") + } + if test2.Abuts(test2) { + t.Errorf("2,2 failed") + } +} + +func TestIsBefore(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.IsBefore(test2) { + t.Errorf("1,2 failed") + } + if test2.IsBefore(test1) { + t.Errorf("2,1 failed") + } + if test1.IsBefore(test1) { + t.Errorf("1,1 failed") + } + if test2.IsBefore(test2) { + t.Errorf("2,2 failed") + } +} + +func TestStartsBefore(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.StartsBefore(test2) { + t.Errorf("1,2 failed") + } + if test2.StartsBefore(test1) { + t.Errorf("2,1 failed") + } + if test1.StartsBefore(test1) { + t.Errorf("1,1 failed") + } + if test2.StartsBefore(test2) { + t.Errorf("2,2 failed") + } +} + +func TestEndsBefore(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if !test1.EndsBefore(test2) { + t.Errorf("1,2 failed") + } + if test2.EndsBefore(test1) { + t.Errorf("2,1 failed") + } + if test1.EndsBefore(test1) { + t.Errorf("1,1 failed") + } + if test2.EndsBefore(test2) { + t.Errorf("2,2 failed") + } +} + +func TestIsAfter(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.IsAfter(test2) { + t.Errorf("1,2 failed") + } + if !test2.IsAfter(test1) { + t.Errorf("2,1 failed") + } + if test1.IsAfter(test1) { + t.Errorf("1,1 failed") + } + if test2.IsAfter(test2) { + t.Errorf("2,2 failed") + } +} + +func TestStartsAfter(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.StartsAfter(test2) { + t.Errorf("1,2 failed") + } + if test2.StartsAfter(test1) { + t.Errorf("2,1 failed") + } + if test1.StartsAfter(test1) { + t.Errorf("1,1 failed") + } + if test2.StartsAfter(test2) { + t.Errorf("2,2 failed") + } +} + +func TestEndsAfter(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.EndsAfter(test2) { + t.Errorf("1,2 failed") + } + if !test2.EndsAfter(test1) { + t.Errorf("2,1 failed") + } + if test1.EndsAfter(test1) { + t.Errorf("1,1 failed") + } + if test2.EndsAfter(test2) { + t.Errorf("2,2 failed") + } +} + +func TestIsShorter(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if !test1.IsShorter(test2) { + t.Errorf("1,2 failed") + } + if test2.IsShorter(test1) { + t.Errorf("2,1 failed") + } + if test1.IsShorter(test1) { + t.Errorf("1,1 failed") + } + if test2.IsShorter(test2) { + t.Errorf("2,2 failed") + } +} + +func TestIsSameDuration(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.IsSameDuration(test2) { + t.Errorf("1,2 failed") + } + if test2.IsSameDuration(test1) { + t.Errorf("2,1 failed") + } + if !test1.IsSameDuration(test1) { + t.Errorf("1,1 failed") + } + if !test2.IsSameDuration(test2) { + t.Errorf("2,2 failed") + } +} + +func TestIsLonger(t *testing.T) { + test1 := testValue(0) + test2 := testValue(86400) + + if test1.IsLonger(test2) { + t.Errorf("1,2 failed") + } + if !test2.IsLonger(test1) { + t.Errorf("2,1 failed") + } + if test1.IsLonger(test1) { + t.Errorf("1,1 failed") + } + if test2.IsLonger(test2) { + t.Errorf("2,2 failed") + } +} diff --git a/go.test.sh b/go.test.sh new file mode 100755 index 0000000..06f5e82 --- /dev/null +++ b/go.test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e +echo "" > coverage.txt + +for d in $(go list ./... | grep -v vendor); do + go test -v -race -coverprofile=profile.out -covermode=atomic "$d" + if [ -f profile.out ]; then + cat profile.out >> coverage.txt + rm profile.out + fi +done diff --git a/offsets_test.go b/offsets_test.go new file mode 100644 index 0000000..96ac9de --- /dev/null +++ b/offsets_test.go @@ -0,0 +1,462 @@ +package calends + +import ( + "errors" + "testing" + + "github.com/danhunsaker/calends/calendars" +) + +func TestAdd(t *testing.T) { + test, err := testValue(0).Add("86400", "unix") + if err != nil { + t.Errorf("Add(%#v, %#v) gives error %q", "86400", "unix", err) + } + if test.startTime.String() != "86400" { + t.Errorf("Add(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", test.startTime.String(), "86400") + } + if test.duration.String() != "-86400" { + t.Errorf("Add(%#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", test.duration.String(), "-86400") + } + if test.endTime.String() != "0" { + t.Errorf("Add(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", test.endTime.String(), "0") + } + + test, err = testValue(0).Add("86400", "invalid") + if err != calendars.UnknownCalendarError { + t.Errorf("Add(%#v, %#v) gives error %#v; wanted %#v", "86400", "invalid", err, calendars.UnknownCalendarError) + } + + test, err = testValue(0).Add("invalid", "gregorian") + if err != calendars.UnsupportedInputError { + t.Errorf("Add(%#v, %#v) gives error %#v; wanted %#v", "invalid", "gregorian", err, calendars.UnsupportedInputError) + } +} + +func TestSubtract(t *testing.T) { + test, err := testValue(0).Subtract("86400", "unix") + if err != nil { + t.Errorf("Subtract(%#v, %#v) gives error %q", "86400", "unix", err) + } + if test.startTime.String() != "-86400" { + t.Errorf("Subtract(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", test.startTime.String(), "-86400") + } + if test.duration.String() != "86400" { + t.Errorf("Subtract(%#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", test.duration.String(), "86400") + } + if test.endTime.String() != "0" { + t.Errorf("Subtract(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", test.endTime.String(), "0") + } + + test, err = testValue(0).Subtract("86400", "invalid") + if err != calendars.UnknownCalendarError { + t.Errorf("Subtract(%#v, %#v) gives error %#v; wanted %#v", "86400", "invalid", err, calendars.UnknownCalendarError) + } + + test, err = testValue(0).Subtract("invalid", "gregorian") + if err != calendars.UnsupportedInputError { + t.Errorf("Subtract(%#v, %#v) gives error %#v; wanted %#v", "invalid", "gregorian", err, calendars.UnsupportedInputError) + } +} + +func TestAddFromEnd(t *testing.T) { + test, err := testValue(0).AddFromEnd("86400", "unix") + if err != nil { + t.Errorf("AddFromEnd(%#v, %#v) gives error %q", "86400", "unix", err) + } + if test.startTime.String() != "0" { + t.Errorf("AddFromEnd(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", test.startTime.String(), "0") + } + if test.duration.String() != "86400" { + t.Errorf("AddFromEnd(%#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", test.duration.String(), "86400") + } + if test.endTime.String() != "86400" { + t.Errorf("AddFromEnd(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", test.endTime.String(), "86400") + } + + test, err = testValue(0).AddFromEnd("86400", "invalid") + if err != calendars.UnknownCalendarError { + t.Errorf("AddFromEnd(%#v, %#v) gives error %#v; wanted %#v", "86400", "invalid", err, calendars.UnknownCalendarError) + } + + test, err = testValue(0).AddFromEnd("invalid", "gregorian") + if err != calendars.UnsupportedInputError { + t.Errorf("AddFromEnd(%#v, %#v) gives error %#v; wanted %#v", "invalid", "gregorian", err, calendars.UnsupportedInputError) + } +} + +func TestSubtractFromEnd(t *testing.T) { + test, err := testValue(0).SubtractFromEnd("86400", "unix") + if err != nil { + t.Errorf("SubtractFromEnd(%#v, %#v) gives error %q", "86400", "unix", err) + } + if test.startTime.String() != "0" { + t.Errorf("SubtractFromEnd(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", test.startTime.String(), "0") + } + if test.duration.String() != "-86400" { + t.Errorf("SubtractFromEnd(%#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", test.duration.String(), "-86400") + } + if test.endTime.String() != "-86400" { + t.Errorf("SubtractFromEnd(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", test.endTime.String(), "-86400") + } + + test, err = testValue(0).SubtractFromEnd("86400", "invalid") + if err != calendars.UnknownCalendarError { + t.Errorf("SubtractFromEnd(%#v, %#v) gives error %#v; wanted %#v", "86400", "invalid", err, calendars.UnknownCalendarError) + } + + test, err = testValue(0).SubtractFromEnd("invalid", "gregorian") + if err != calendars.UnsupportedInputError { + t.Errorf("SubtractFromEnd(%#v, %#v) gives error %#v; wanted %#v", "invalid", "gregorian", err, calendars.UnsupportedInputError) + } +} + +func TestNext(t *testing.T) { + test1, err1 := testValue(0).Next("86400", "unix") + test2, err2 := test1.Next("", "") + test3, err3 := testValue(86400).Next("", "") + test4, err4 := testValue(0).Next("86400", "") + _, err5 := testValue(0).Next("86400", "invalid") + _, err6 := testValue(0).Next("invalid", "gregorian") + + if err1 != nil { + t.Errorf("1:Next(%#v, %#v) gives error %q", "86400", "unix", err1) + } + if test1.startTime.String() != "0" { + t.Errorf("1:Next(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", test1.startTime.String(), "0") + } + if test1.duration.String() != "86400" { + t.Errorf("1:Next(%#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", test1.duration.String(), "86400") + } + if test1.endTime.String() != "86400" { + t.Errorf("1:Next(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", test1.endTime.String(), "86400") + } + + if err2 != nil { + t.Errorf("2:Next(%#v, %#v) gives error %q", "", "", err2) + } + if test2.startTime.String() != "86400" { + t.Errorf("2:Next(%#v, %#v) has startTime of %#v\nwant %#v", "", "", test2.startTime.String(), "86400") + } + if test2.duration.String() != "86400" { + t.Errorf("2:Next(%#v, %#v) has duration of %#v\nwant %#v", "", "", test2.duration.String(), "86400") + } + if test2.endTime.String() != "172800" { + t.Errorf("2:Next(%#v, %#v) has endTime of %#v\nwant %#v", "", "", test2.endTime.String(), "172800") + } + + if err3 != nil { + t.Errorf("3:Next(%#v, %#v) gives error %q", "", "", err3) + } + if test3.startTime.String() != "86400" { + t.Errorf("3:Next(%#v, %#v) has startTime of %#v\nwant %#v", "", "", test3.startTime.String(), "86400") + } + if test3.duration.String() != "86400" { + t.Errorf("3:Next(%#v, %#v) has duration of %#v\nwant %#v", "", "", test3.duration.String(), "86400") + } + if test3.endTime.String() != "172800" { + t.Errorf("3:Next(%#v, %#v) has endTime of %#v\nwant %#v", "", "", test3.endTime.String(), "172800") + } + + if err4 != nil { + t.Errorf("4:Next(%#v, %#v) gives error %q", "86400", "", err4) + } + if test4.startTime.String() != "0" { + t.Errorf("4:Next(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "", test4.startTime.String(), "0") + } + if test4.duration.String() != "86400" { + t.Errorf("4:Next(%#v, %#v) has duration of %#v\nwant %#v", "86400", "", test4.duration.String(), "86400") + } + if test4.endTime.String() != "86400" { + t.Errorf("4:Next(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "", test4.endTime.String(), "86400") + } + + if err5 != calendars.UnknownCalendarError { + t.Errorf("5:Next(%#v, %#v) gives error %q; want %q", "86400", "invalid", err5, calendars.UnknownCalendarError) + } + + if err6 != calendars.UnsupportedInputError { + t.Errorf("6:Next(%#v, %#v) gives error %q; want %q", "invalid", "gregorian", err6, calendars.UnsupportedInputError) + } +} + +func TestPrevious(t *testing.T) { + test1, err1 := testValue(0).Previous("86400", "unix") + test2, err2 := test1.Previous("", "") + test3, err3 := testValue(86400).Previous("", "") + test4, err4 := testValue(0).Previous("86400", "") + _, err5 := testValue(0).Previous("86400", "invalid") + _, err6 := testValue(0).Previous("invalid", "gregorian") + + if err1 != nil { + t.Errorf("1:Previous(%#v, %#v) gives error %q", "86400", "unix", err1) + } + if test1.startTime.String() != "-86400" { + t.Errorf("1:Previous(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", test1.startTime.String(), "-86400") + } + if test1.duration.String() != "86400" { + t.Errorf("1:Previous(%#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", test1.duration.String(), "86400") + } + if test1.endTime.String() != "0" { + t.Errorf("1:Previous(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", test1.endTime.String(), "0") + } + + if err2 != nil { + t.Errorf("2:Previous(%#v, %#v) gives error %q", "", "", err2) + } + if test2.startTime.String() != "-172800" { + t.Errorf("2:Previous(%#v, %#v) has startTime of %#v\nwant %#v", "", "", test2.startTime.String(), "-172800") + } + if test2.duration.String() != "86400" { + t.Errorf("2:Previous(%#v, %#v) has duration of %#v\nwant %#v", "", "", test2.duration.String(), "86400") + } + if test2.endTime.String() != "-86400" { + t.Errorf("2:Previous(%#v, %#v) has endTime of %#v\nwant %#v", "", "", test2.endTime.String(), "-86400") + } + + if err3 != nil { + t.Errorf("3:Previous(%#v, %#v) gives error %q", "", "", err3) + } + if test3.startTime.String() != "-86400" { + t.Errorf("3:Previous(%#v, %#v) has startTime of %#v\nwant %#v", "", "", test3.startTime.String(), "-86400") + } + if test3.duration.String() != "86400" { + t.Errorf("3:Previous(%#v, %#v) has duration of %#v\nwant %#v", "", "", test3.duration.String(), "86400") + } + if test3.endTime.String() != "0" { + t.Errorf("3:Previous(%#v, %#v) has endTime of %#v\nwant %#v", "", "", test3.endTime.String(), "0") + } + + if err4 != nil { + t.Errorf("4:Previous(%#v, %#v) gives error %q", "86400", "", err4) + } + if test4.startTime.String() != "-86400" { + t.Errorf("4:Previous(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "", test4.startTime.String(), "-86400") + } + if test4.duration.String() != "86400" { + t.Errorf("4:Previous(%#v, %#v) has duration of %#v\nwant %#v", "86400", "", test4.duration.String(), "86400") + } + if test4.endTime.String() != "0" { + t.Errorf("4:Previous(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "", test4.endTime.String(), "0") + } + + if err5 != calendars.UnknownCalendarError { + t.Errorf("5:Previous(%#v, %#v) gives error %q; want %q", "86400", "invalid", err5, calendars.UnknownCalendarError) + } + + if err6 != calendars.UnsupportedInputError { + t.Errorf("6:Previous(%#v, %#v) gives error %q; want %q", "invalid", "gregorian", err6, calendars.UnsupportedInputError) + } +} + +func TestSetDate(t *testing.T) { + test, err := testValue(0).SetDate("86400", "unix", "") + if err != nil { + t.Errorf("SetDate(%#v, %#v, %#v) gives error %q", "86400", "unix", "", err) + } + if test.startTime.String() != "86400" { + t.Errorf("SetDate(%#v, %#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", "", test.startTime.String(), "86400") + } + if test.duration.String() != "-86400" { + t.Errorf("SetDate(%#v, %#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", "", test.duration.String(), "-86400") + } + if test.endTime.String() != "0" { + t.Errorf("SetDate(%#v, %#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", "", test.endTime.String(), "0") + } + + test, err = testValue(0).SetDate("86400", "", "") + if err != nil { + t.Errorf("SetDate(%#v, %#v, %#v) gives error %q", "86400", "", "", err) + } + if test.startTime.String() != "86400" { + t.Errorf("SetDate(%#v, %#v, %#v) has startTime of %#v\nwant %#v", "86400", "", "", test.startTime.String(), "86400") + } + if test.duration.String() != "-86400" { + t.Errorf("SetDate(%#v, %#v, %#v) has duration of %#v\nwant %#v", "86400", "", "", test.duration.String(), "-86400") + } + if test.endTime.String() != "0" { + t.Errorf("SetDate(%#v, %#v, %#v) has endTime of %#v\nwant %#v", "86400", "", "", test.endTime.String(), "0") + } + + _, err = testValue(0).SetDate("86400", "invalid", "") + if err != calendars.UnknownCalendarError { + t.Errorf("SetDate(%#v, %#v, %#v) gives error %q; want %q", "86400", "invalid", "", err, calendars.UnknownCalendarError) + } + + _, err = testValue(0).SetDate("invalid", "gregorian", "") + if err.Error() != `parsing time "invalid" as "Mon, 02 Jan 2006 15:04:05 MST": cannot parse "invalid" as "Mon"` { + t.Errorf("SetDate(%#v, %#v, %#v) gives error %q; want %q", "invalid", "gregorian", "", err, errors.New(`parsing time "invalid" as "Mon, 02 Jan 2006 15:04:05 MST": cannot parse "invalid" as "Mon"`)) + } +} + +func TestSetEndDate(t *testing.T) { + test, err := testValue(0).SetEndDate("86400", "unix", "") + if err != nil { + t.Errorf("SetEndDate(%#v, %#v, %#v) gives error %q", "86400", "unix", "", err) + } + if test.startTime.String() != "0" { + t.Errorf("SetEndDate(%#v, %#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", "", test.startTime.String(), "0") + } + if test.duration.String() != "86400" { + t.Errorf("SetEndDate(%#v, %#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", "", test.duration.String(), "86400") + } + if test.endTime.String() != "86400" { + t.Errorf("SetEndDate(%#v, %#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", "", test.endTime.String(), "86400") + } + + test, err = testValue(0).SetEndDate("86400", "", "") + if err != nil { + t.Errorf("SetEndDate(%#v, %#v, %#v) gives error %q", "86400", "", "", err) + } + if test.startTime.String() != "0" { + t.Errorf("SetEndDate(%#v, %#v, %#v) has startTime of %#v\nwant %#v", "86400", "", "", test.startTime.String(), "0") + } + if test.duration.String() != "86400" { + t.Errorf("SetEndDate(%#v, %#v, %#v) has duration of %#v\nwant %#v", "86400", "", "", test.duration.String(), "86400") + } + if test.endTime.String() != "86400" { + t.Errorf("SetEndDate(%#v, %#v, %#v) has endTime of %#v\nwant %#v", "86400", "", "", test.endTime.String(), "86400") + } + + _, err = testValue(0).SetEndDate("86400", "invalid", "") + if err != calendars.UnknownCalendarError { + t.Errorf("SetEndDate(%#v, %#v, %#v) gives error %q; want %q", "86400", "invalid", "", err, calendars.UnknownCalendarError) + } + + _, err = testValue(0).SetEndDate("invalid", "gregorian", "") + if err.Error() != `parsing time "invalid" as "Mon, 02 Jan 2006 15:04:05 MST": cannot parse "invalid" as "Mon"` { + t.Errorf("SetEndDate(%#v, %#v, %#v) gives error %q; want %q", "invalid", "gregorian", "", err, errors.New(`parsing time "invalid" as "Mon, 02 Jan 2006 15:04:05 MST": cannot parse "invalid" as "Mon"`)) + } +} + +func TestSetDuration(t *testing.T) { + test, err := testValue(0).SetDuration("86400", "unix") + if err != nil { + t.Errorf("SetDuration(%#v, %#v) gives error %q", "86400", "unix", err) + } + if test.startTime.String() != "0" { + t.Errorf("SetDuration(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", test.startTime.String(), "0") + } + if test.duration.String() != "86400" { + t.Errorf("SetDuration(%#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", test.duration.String(), "86400") + } + if test.endTime.String() != "86400" { + t.Errorf("SetDuration(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", test.endTime.String(), "86400") + } + + _, err = testValue(0).SetDuration("86400", "invalid") + if err != calendars.UnknownCalendarError { + t.Errorf("SetDuration(%#v, %#v) gives error %q; want %q", "86400", "invalid", err, calendars.UnknownCalendarError) + } + + _, err = testValue(0).SetDuration("invalid", "gregorian") + if err != calendars.UnsupportedInputError { + t.Errorf("SetDuration(%#v, %#v) gives error %q; want %q", "invalid", "gregorian", err, calendars.UnsupportedInputError) + } +} + +func TestSetDurationFromEnd(t *testing.T) { + test, err := testValue(0).SetDurationFromEnd("86400", "unix") + + if err != nil { + t.Errorf("SetDurationFromEnd(%#v, %#v) gives error %q", "86400", "unix", err) + } + if test.startTime.String() != "-86400" { + t.Errorf("SetDurationFromEnd(%#v, %#v) has startTime of %#v\nwant %#v", "86400", "unix", test.startTime.String(), "-86400") + } + if test.duration.String() != "86400" { + t.Errorf("SetDurationFromEnd(%#v, %#v) has duration of %#v\nwant %#v", "86400", "unix", test.duration.String(), "86400") + } + if test.endTime.String() != "0" { + t.Errorf("SetDurationFromEnd(%#v, %#v) has endTime of %#v\nwant %#v", "86400", "unix", test.endTime.String(), "0") + } + + _, err = testValue(0).SetDurationFromEnd("86400", "invalid") + if err != calendars.UnknownCalendarError { + t.Errorf("SetDurationFromEnd(%#v, %#v) gives error %q; want %q", "86400", "invalid", err, calendars.UnknownCalendarError) + } + + _, err = testValue(0).SetDurationFromEnd("invalid", "gregorian") + if err != calendars.UnsupportedInputError { + t.Errorf("SetDurationFromEnd(%#v, %#v) gives error %q; want %q", "invalid", "gregorian", err, calendars.UnsupportedInputError) + } +} + +func TestMerge(t *testing.T) { + temp, _ := testValue(-86400).SetDate("-172800", "unix", "") + want, _ := testValue(86400).SetDate("-172800", "unix", "") + + test, err := temp.Merge(testValue(86400)) + if err != nil { + t.Errorf("Merge(%#v)\ngives error %q", testValue(86400), err) + } + if !test.IsSame(want) { + t.Errorf("Merge(%#v)\nreturns %#v\nwant %#v", testValue(86400), test, want) + } + + test, err = testValue(86400).Merge(temp) + if err != nil { + t.Errorf("Merge(%#v)\ngives error %q", temp, err) + } + if !test.IsSame(want) { + t.Errorf("Merge(%#v)\nreturns %#v\nwant %#v", temp, test, want) + } +} + +func TestIntersect(t *testing.T) { + test1, err1 := testValue(0).Intersect(testValue(86400)) + temp, _ := testValue(-86400).SetEndDate("172800", "unix", "") + test2, err2 := temp.Intersect(testValue(86400)) + temp, _ = testValue(-86400).SetDate("-172800", "unix", "") + test3, err3 := temp.Intersect(testValue(86400)) + + if err1 != nil { + t.Errorf("1:Intersect(%#v)\ngives error %q", testValue(86400), err1) + } + if err2 != nil { + t.Errorf("2:Intersect(%#v)\ngives error %q", testValue(86400), err2) + } + if err3 == nil { + t.Errorf("3:Intersect(%#v)\nshould give error; got nil", testValue(86400)) + } + + if !test1.IsSame(testValue(0)) { + t.Errorf("1:Intersect(%#v)\nreturns %#v\nwant %#v", testValue(86400), test1, testValue(0)) + } + if !test2.IsSame(testValue(86400)) { + t.Errorf("2:Intersect(%#v)\nreturns %#v\nwant %#v", testValue(86400), test2, testValue(86400)) + } + if !test3.IsSame(temp) { + t.Errorf("3:Intersect(%#v)\nreturns %#v\nwant %#v", testValue(86400), test3, temp) + } +} + +func TestGap(t *testing.T) { + test1, err1 := testValue(0).Gap(testValue(86400)) + temp2, _ := testValue(-86400).SetDate("-172800", "unix", "") + test2, err2 := temp2.Gap(testValue(86400)) + temp3, _ := testValue(-86400).SetDate("-172800", "unix", "") + test3, err3 := testValue(86400).Gap(temp3) + + if err1 == nil { + t.Errorf("1:Gap(%#v)\nshould give error; got nil", testValue(86400)) + } + if err2 != nil { + t.Errorf("2:Gap(%#v)\ngives error %q", testValue(86400), err2) + } + if err3 != nil { + t.Errorf("3:Gap(%#v)\ngives error %q", testValue(86400), err3) + } + + if !test1.IsSame(testValue(0)) { + t.Errorf("1:Gap(%#v)\nreturns %#v\nwant %#v", testValue(86400), test1, testValue(0)) + } + temp, _ := testValue(0).SetDate("-86400", "unix", "") + if !test2.IsSame(temp) { + t.Errorf("2:Gap(%#v)\nreturns %#v\nwant %#v", testValue(86400), test2, temp) + } + temp, _ = testValue(0).SetDate("-86400", "unix", "") + if !test3.IsSame(temp) { + t.Errorf("3:Gap(%#v)\nreturns %#v\nwant %#v", temp3, test3, temp) + } +}