diff --git a/README.md b/README.md index 211fd40..a71b804 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,10 @@ If you have a feature request, please open an issue. It would be great if you co - [IsBetween](https://pkg.go.dev/github.com/Code-Hex/synchro#Time.IsBetween) - [IsLeapYear](https://pkg.go.dev/github.com/Code-Hex/synchro#Time.IsLeapYear) - [DiffInCalendarDays](https://pkg.go.dev/github.com/Code-Hex/synchro#Time.DiffInCalendarDays) +- [Change](https://pkg.go.dev/github.com/Code-Hex/synchro#Time.Change) + - `Change` allows you to specify the date and time components you want to change and make modifications. +- [Advance](https://pkg.go.dev/github.com/Code-Hex/synchro#Time.Advance) + - `Advance` allows you to specify the date and time components you want to increment and make modifications. ## TODO diff --git a/builder.go b/builder.go new file mode 100644 index 0000000..d7a976c --- /dev/null +++ b/builder.go @@ -0,0 +1,221 @@ +package synchro + +import ( + "time" + + "github.com/Code-Hex/synchro/tz" +) + +func toPtr[T any](x T) *T { + return &x +} + +// TimeBuilder defines an interface for constructing a Time[T] value +// with specific date and time components using method chaining. +// +// Implementations should allow for setting each component step by step, +// and then finalizing the construction with the Do() method. +type TimeBuilder[T TimeZone] interface { + // Year sets the year component of the time being built. + Year(int) TimeBuilder[T] + + // Month sets the month component of the time being built. + Month(time.Month) TimeBuilder[T] + + // Day sets the day component of the time being built. + Day(int) TimeBuilder[T] + + // Hour sets the hour component of the time being built. + Hour(int) TimeBuilder[T] + + // Minute sets the minute component of the time being built. + Minute(int) TimeBuilder[T] + + // Second sets the second component of the time being built. + Second(int) TimeBuilder[T] + + // Nanosecond sets the nanosecond component of the time being built. + Nanosecond(int) TimeBuilder[T] + + // Do finalizes and returns the constructed Time value + // based on the components set in the chain. + Do() Time[T] +} + +type changeBuilder[T TimeZone] struct { + year *int + month *time.Month + day *int + hour *int + minute *int + second *int + nsec *int + + t Time[T] +} + +var _ TimeBuilder[tz.UTC] = changeBuilder[tz.UTC]{} + +// Year implements the TimeBuilder interface to change years. +func (c changeBuilder[T]) Year(year int) TimeBuilder[T] { + c.year = toPtr(year) + return c +} + +// Month implements the TimeBuilder interface to change months. +func (c changeBuilder[T]) Month(month time.Month) TimeBuilder[T] { + c.month = toPtr(month) + return c +} + +// Day implements the TimeBuilder interface to change days. +func (c changeBuilder[T]) Day(day int) TimeBuilder[T] { + c.day = toPtr(day) + return c +} + +// Hour implements the TimeBuilder interface to change hours. +func (c changeBuilder[T]) Hour(hour int) TimeBuilder[T] { + c.hour = toPtr(hour) + return c +} + +// Minute implements the TimeBuilder interface to change minutes. +func (c changeBuilder[T]) Minute(minute int) TimeBuilder[T] { + c.minute = toPtr(minute) + return c +} + +// Second implements the TimeBuilder interface to change seconds. +func (c changeBuilder[T]) Second(second int) TimeBuilder[T] { + c.second = toPtr(second) + return c +} + +// Nanosecond implements the TimeBuilder interface to change nanoseconds. +func (c changeBuilder[T]) Nanosecond(nsec int) TimeBuilder[T] { + c.nsec = toPtr(nsec) + return c +} + +// Do implements the TimeBuilder interface to change any date and time components. +func (c changeBuilder[T]) Do() Time[T] { + year, month, day := c.t.Date() + hour, min, sec := c.t.Clock() + nsec := c.t.Nanosecond() + if c.year != nil { + year = *c.year + } + if c.month != nil { + month = *c.month + } + if c.day != nil { + day = *c.day + } + if c.hour != nil { + hour = *c.hour + } + if c.minute != nil { + min = *c.minute + } + if c.second != nil { + sec = *c.second + } + if c.nsec != nil { + nsec = *c.nsec + } + return New[T](year, month, day, hour, min, sec, nsec) +} + +// Change initializes and returns a TimeBuilder for the current Time value. +// +// This provides a method chain approach for specifying which parts of the time +// you want to change, allowing for the creation of a new Time[T] instance +// with the specified modifications. +func (t Time[T]) Change() TimeBuilder[T] { + return changeBuilder[T]{t: t} +} + +type advanceBuilder[T TimeZone] struct { + year int + month time.Month + day int + hour int + minute int + second int + nsec int + + t Time[T] +} + +var _ TimeBuilder[tz.UTC] = advanceBuilder[tz.UTC]{} + +// Advance initializes and returns a TimeBuilder for the current Time value. +// +// This provides a method chain approach for specifying which parts of the time +// you want to increment, allowing for the creation of a new Time[T] instance +// with the specified modifications. +func (t Time[T]) Advance() TimeBuilder[T] { + return advanceBuilder[T]{t: t} +} + +// Year implements the TimeBuilder interface to increment years. +func (a advanceBuilder[T]) Year(year int) TimeBuilder[T] { + a.year += year + return a +} + +// Month implements the TimeBuilder interface to increment months. +func (a advanceBuilder[T]) Month(month time.Month) TimeBuilder[T] { + a.month += month + return a +} + +// Day implements the TimeBuilder interface to increment days. +func (a advanceBuilder[T]) Day(day int) TimeBuilder[T] { + a.day += day + return a +} + +// Hour implements the TimeBuilder interface to increment hours. +func (a advanceBuilder[T]) Hour(hour int) TimeBuilder[T] { + a.hour += hour + return a +} + +// Minute implements the TimeBuilder interface to increment minutes. +func (a advanceBuilder[T]) Minute(minute int) TimeBuilder[T] { + a.minute += minute + return a +} + +// Second implements the TimeBuilder interface to increment seconds. +func (a advanceBuilder[T]) Second(second int) TimeBuilder[T] { + a.second += second + return a +} + +// Nanosecond implements the TimeBuilder interface to increment nanoseconds. +func (a advanceBuilder[T]) Nanosecond(nsec int) TimeBuilder[T] { + a.nsec += nsec + return a +} + +// Do implements the TimeBuilder interface to increment any date and time components. +func (a advanceBuilder[T]) Do() Time[T] { + t := a.t.AddDate(a.year, int(a.month), a.day) + + if a.hour != 0 { + t = t.Add(time.Hour * time.Duration(a.hour)) + } + if a.minute != 0 { + t = t.Add(time.Minute * time.Duration(a.minute)) + } + if a.second != 0 { + t = t.Add(time.Second * time.Duration(a.second)) + } + if a.nsec != 0 { + t = t.Add(time.Duration(a.nsec)) + } + return t +} diff --git a/builder_test.go b/builder_test.go new file mode 100644 index 0000000..68e2f64 --- /dev/null +++ b/builder_test.go @@ -0,0 +1,120 @@ +package synchro_test + +import ( + "fmt" + "testing" + "time" + + "github.com/Code-Hex/synchro" + "github.com/Code-Hex/synchro/tz" +) + +func ExampleTime_Change() { + utc := synchro.New[tz.UTC](2009, time.November, 10, 23, 0, 0, 0) + c1 := utc.Change().Year(2010).Do() + c2 := utc.Change().Year(2010).Month(time.December).Do() + c3 := utc.Change().Year(2010).Month(time.December).Day(1).Do() + c4 := c3.Change().Hour(1).Do() + c5 := c3.Change().Hour(1).Minute(1).Do() + c6 := c3.Change().Hour(1).Minute(1).Second(1).Do() + c7 := c3.Change().Hour(1).Minute(1).Second(1).Nanosecond(123456789).Do() + fmt.Printf("Go launched at %s\n", utc) + fmt.Println(c1) + fmt.Println(c2) + fmt.Println(c3) + fmt.Println(c4) + fmt.Println(c5) + fmt.Println(c6) + fmt.Println(c7) + // Output: + // Go launched at 2009-11-10 23:00:00 +0000 UTC + // 2010-11-10 23:00:00 +0000 UTC + // 2010-12-10 23:00:00 +0000 UTC + // 2010-12-01 23:00:00 +0000 UTC + // 2010-12-01 01:00:00 +0000 UTC + // 2010-12-01 01:01:00 +0000 UTC + // 2010-12-01 01:01:01 +0000 UTC + // 2010-12-01 01:01:01.123456789 +0000 UTC +} + +func ExampleTime_Advance() { + utc := synchro.New[tz.UTC](2009, time.November, 10, 23, 0, 0, 0) + c1 := utc.Advance().Year(1).Do() + c11 := utc.Advance().Year(1).Year(1).Do() // +2 years + + c2 := utc.Advance().Year(1).Month(1).Do() + c3 := utc.Advance().Year(1).Month(1).Day(1).Do() + c4 := c3.Advance().Hour(1).Do() + c5 := c3.Advance().Hour(1).Minute(1).Do() + c6 := c3.Advance().Hour(1).Minute(1).Second(1).Do() + c7 := c3.Advance().Hour(1).Minute(1).Second(1).Nanosecond(123456789).Do() + + fmt.Printf("Go launched at %s\n", utc) + fmt.Println(c1) + fmt.Println(c11) + fmt.Println() + fmt.Println(c2) + fmt.Println(c3) + fmt.Println(c4) + fmt.Println(c5) + fmt.Println(c6) + fmt.Println(c7) + // Output: + // Go launched at 2009-11-10 23:00:00 +0000 UTC + // 2010-11-10 23:00:00 +0000 UTC + // 2011-11-10 23:00:00 +0000 UTC + // + // 2010-12-10 23:00:00 +0000 UTC + // 2010-12-11 23:00:00 +0000 UTC + // 2010-12-12 00:00:00 +0000 UTC + // 2010-12-12 00:01:00 +0000 UTC + // 2010-12-12 00:01:01 +0000 UTC + // 2010-12-12 00:01:01.123456789 +0000 UTC +} + +func TestAdvance(t *testing.T) { + utc := synchro.New[tz.UTC](2009, time.November, 10, 23, 0, 0, 0) + + t.Run("month twice", func(t *testing.T) { + got := utc.Advance().Month(1).Month(2).Do() // +3 months + want := synchro.New[tz.UTC](2010, time.February, 10, 23, 0, 0, 0) + if want != got { + t.Fatalf("- %s\n+ %s", want, got) + } + }) + t.Run("day twice", func(t *testing.T) { + got := utc.Advance().Day(1).Day(2).Do() // +3 days + want := synchro.New[tz.UTC](2009, time.November, 13, 23, 0, 0, 0) + if want != got { + t.Fatalf("- %s\n+ %s", want, got) + } + }) + t.Run("hour twice", func(t *testing.T) { + got := utc.Advance().Hour(1).Hour(2).Do() // +3 hours + want := synchro.New[tz.UTC](2009, time.November, 11, 2, 0, 0, 0) + if want != got { + t.Fatalf("- %s\n+ %s", want, got) + } + }) + t.Run("minute twice", func(t *testing.T) { + got := utc.Advance().Minute(5).Minute(60).Do() // +65 minutes + want := synchro.New[tz.UTC](2009, time.November, 11, 0, 5, 0, 0) + if want != got { + t.Fatalf("- %s\n+ %s", want, got) + } + }) + t.Run("second twice", func(t *testing.T) { + got := utc.Advance().Second(5).Second(60).Do() // +65 seconds + want := synchro.New[tz.UTC](2009, time.November, 10, 23, 1, 5, 0) + if want != got { + t.Fatalf("- %s\n+ %s", want, got) + } + }) + t.Run("nanosec twice", func(t *testing.T) { + got := utc.Advance().Nanosecond(5).Nanosecond(60).Do() // +65 nanosec + want := synchro.New[tz.UTC](2009, time.November, 10, 23, 0, 0, 65) + if want != got { + t.Fatalf("- %s\n+ %s", want, got) + } + }) +} diff --git a/example_test.go b/example_test.go index fc866a3..81be8a8 100644 --- a/example_test.go +++ b/example_test.go @@ -60,7 +60,7 @@ func ExampleNowContext() { // 0001-01-01 00:00:00 +0000 UTC } -func ExampleDate() { +func ExampleNew() { utc := synchro.New[tz.UTC](2009, time.November, 10, 23, 0, 0, 0) fmt.Printf("Go launched at %s\n", utc) // Output: