Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ terminal graphical visualization time.

| Encoder | Configuration | ns/op | B/op | allocs/op |
|---------------------|--------------------|-------|------|-----------|
| **Default Encoder** | All Properties OFF | 484.0 | 80 | 1 |
| | All Properties ON | 540.1 | 104 | 2 |
| **JSON Encoder** | All Properties OFF | 507.3 | 80 | 1 |
| | All Properties ON | 553.6 | 104 | 2 |
| **YAML Encoder** | All Properties OFF | 531.3 | 80 | 1 |
| | All Properties ON | 588.2 | 104 | 2 |
| **Default Encoder** | All Properties OFF | 490.3 | 80 | 1 |
| | All Properties ON | 511.2 | 104 | 1 |
| **JSON Encoder** | All Properties OFF | 513.3 | 80 | 1 |
| | All Properties ON | 536.5 | 104 | 1 |
| **YAML Encoder** | All Properties OFF | 535.3 | 80 | 1 |
| | All Properties ON | 557.1 | 104 | 1 |


## 🤝 Contributing
Expand Down
34 changes: 0 additions & 34 deletions internal/services/colors_printer.go

This file was deleted.

140 changes: 68 additions & 72 deletions internal/services/datetime_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,112 +21,108 @@ var timeFormat = map[s.DateTimeFormat]string{
s.JP: "03:04:05 PM",
}

var (
dateTimePrinterInstance *DateTimePrinter
dateTimePrinterOnce sync.Once
)

type DateTimePrinter struct {
timeNow func() time.Time // Function to get current time, allows mocking for tests
currentFormat atomic.Value
currentDate atomic.Value
currentTime atomic.Value
currentUnix atomic.Value
dateOnce sync.Once
timeOnce sync.Once
unixOnce sync.Once
timeNow func() time.Time
cachedDates [3]atomic.Value
cachedTimes [3]atomic.Value
currentUnix atomic.Value
}

// RetrieveDateTime returns the current date, time, and combined/unix string based on the configuration.
// Returns: (dateString, timeString, combinedOrUnixString).
// If the format is UnixTimestamp, the timestamp is returned as the third value, ignoring the boolean flags.
// Otherwise, 'addDate' and 'addTime' control which components are generated.
func (d *DateTimePrinter) RetrieveDateTime(addDate, addTime bool) (string, string, string) {
var dateRes, timeRes string
currentFmt := d.currentFormat.Load().(s.DateTimeFormat)

if currentFmt == s.UnixTimestamp && (addDate || addTime) {
d.unixOnce.Do(func() {
d.currentUnix.Store(strconv.FormatInt(d.timeNow().Unix(), 10))
go d.updateCurrentUnixEverySecond()
})

// RetrieveDateTime now accepts the desired format
func (d *DateTimePrinter) RetrieveDateTime(fmt s.DateTimeFormat, addDate, addTime bool) (string, string, string) {
if fmt == s.UnixTimestamp {
return "", "", d.currentUnix.Load().(string)
}

if addDate {
d.dateOnce.Do(func() {
d.currentDate.Store(d.timeNow().Format(dateFormat[currentFmt]))
go d.updateCurrentDateEveryDay()
})
var dateRes, timeRes string

dateRes = d.currentDate.Load().(string)
if addDate {
dateRes = d.cachedDates[fmt].Load().(string)
}

if addTime {
d.timeOnce.Do(func() {
d.currentTime.Store(d.timeNow().Format(timeFormat[currentFmt]))
go d.updateCurrentTimeEverySecond()
})

timeRes = d.currentTime.Load().(string)
}

if addDate && addTime {
return "", "", dateRes + " " + timeRes
timeRes = d.cachedTimes[fmt].Load().(string)
}

return dateRes, timeRes, ""
}

// UpdateDateTimeFormat updates the DateTimePrinter's currentFormat property and updates the currentDate and
// currentTime properties accordingly.
func (d *DateTimePrinter) UpdateDateTimeFormat(format s.DateTimeFormat) {
// init initializes the current timestamp and cached formatted strings,
// then starts background goroutines to keep them updated.
func (d *DateTimePrinter) init() {
now := d.timeNow()
d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10))

for i := 0; i < 3; i++ {
fmt := s.DateTimeFormat(i)
d.cachedDates[i].Store(now.Format(dateFormat[fmt]))
d.cachedTimes[i].Store(now.Format(timeFormat[fmt]))
}

d.currentFormat.Store(format)
d.currentDate.Store(now.Format(dateFormat[format]))
d.currentTime.Store(now.Format(timeFormat[format]))
go d.loopUpdateDate()
go d.loopUpdateTime()
}

// updateCurrentDateEveryDay synchronizes with the system clock and updates the DateTimePrinter's
// currentDate property every midnight.
func (d *DateTimePrinter) updateCurrentDateEveryDay() {
// loopUpdateTime updates all time formats, the unix timestamp every second,
// and refreshes the date format if the day has changed.
func (d *DateTimePrinter) loopUpdateTime() {
// Initialize lastDay with a value that forces an update on the first iteration
lastDay := -1

for {
now := d.timeNow()
currentFmt := d.currentFormat.Load().(s.DateTimeFormat)
d.currentDate.Store(now.Format(dateFormat[currentFmt]))

// computing next midnight in local time zone
nextMidnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
time.Sleep(time.Until(nextMidnight))
}
}
// 1. Update Time Formats (Always)
for i := 0; i < 3; i++ {
fmt := s.DateTimeFormat(i)
d.cachedTimes[i].Store(now.Format(timeFormat[fmt]))
}

// updateCurrentTimeEverySecond synchronizes with the system clock and updates the DateTimePrinter's
// currentTime property every full second.
func (d *DateTimePrinter) updateCurrentTimeEverySecond() {
for {
now := d.timeNow()
currentFmt := d.currentFormat.Load().(s.DateTimeFormat)
d.currentTime.Store(now.Format(timeFormat[currentFmt]))
// 2. Update Unix Timestamp (Always)
d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10))

// 3. Update Date Formats (Only if day changed)
if currentDay := now.Day(); currentDay != lastDay {
for i := 0; i < 3; i++ {
fmt := s.DateTimeFormat(i)
d.cachedDates[i].Store(now.Format(dateFormat[fmt]))
}

lastDay = currentDay
}

nextSecond := now.Truncate(time.Second).Add(time.Second)
time.Sleep(time.Until(nextSecond))
}
}

// updateCurrentUnixEverySecond synchronizes with the system clock and updates the DateTimePrinter's
// currentTime property every full second.
func (d *DateTimePrinter) updateCurrentUnixEverySecond() {
// loopUpdateDate updates all date formats every 10 mins
func (d *DateTimePrinter) loopUpdateDate() {
for {
now := d.timeNow()
d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10))

nextSecond := now.Truncate(time.Second).Add(time.Second)
time.Sleep(time.Until(nextSecond))
for i := 0; i < 3; i++ {
fmt := s.DateTimeFormat(i)
d.cachedDates[i].Store(now.Format(dateFormat[fmt]))
}

time.Sleep(time.Minute * 10)
}
}

// NewDateTimePrinter initializes DateTimePrinter with the default timeNow function.
func NewDateTimePrinter() *DateTimePrinter {
dateTimePrinter := &DateTimePrinter{timeNow: time.Now}
dateTimePrinter.currentFormat.Store(s.IT)
// GetDateTimePrinter returns the singleton instance.
func GetDateTimePrinter() *DateTimePrinter {
dateTimePrinterOnce.Do(
func() {
dateTimePrinterInstance = &DateTimePrinter{timeNow: time.Now}
dateTimePrinterInstance.init()
},
)

return dateTimePrinter
return dateTimePrinterInstance
}
65 changes: 31 additions & 34 deletions internal/services/datetime_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,32 @@ func TestDateTimePrinter_PrintDateTime(t *testing.T) {
return time.Date(2023, time.November, 1, 15, 30, 45, 0, time.UTC)
},
}
dateTimePrinter.init()

t.Run("Return both date and time", func(t *testing.T) {
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true)
assert.Empty(t, dateRes)
assert.Empty(t, timeRes)
assert.Equal(t, "01/11/2023 15:30:45", dateTimeRes)
dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, true, true)
assert.Empty(t, unixTs)
assert.NotEmpty(t, dateRes)
assert.NotEmpty(t, timeRes)
assert.Equal(t, "01/11/2023 15:30:45", dateRes+" "+timeRes)
})

t.Run("Return date only", func(t *testing.T) {
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, false)
dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, true, false)
assert.Equal(t, "01/11/2023", dateRes)
assert.Equal(t, "", timeRes)
assert.Equal(t, "", dateTimeRes)
assert.Equal(t, "", unixTs)
})

t.Run("Return time only", func(t *testing.T) {
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, true)
dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, false, true)
assert.Equal(t, "", dateRes)
assert.Equal(t, "15:30:45", timeRes)
assert.Equal(t, "", dateTimeRes)
assert.Equal(t, "", unixTs)
})

t.Run("Return empty string when both flags are false", func(t *testing.T) {
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, false)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.IT, false, false)
assert.Equal(t, "", dateRes)
assert.Equal(t, "", timeRes)
assert.Equal(t, "", dateTimeRes)
Expand All @@ -60,6 +58,7 @@ func TestDateTimePrinter_Formats(t *testing.T) {
return fixedFutureTime
},
}
dateTimePrinter.init()

tests := []struct {
name string
Expand Down Expand Up @@ -93,18 +92,18 @@ func TestDateTimePrinter_Formats(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dateTimePrinter.UpdateDateTimeFormat(tt.format)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true)
assert.Empty(t, dateRes)
assert.Empty(t, timeRes)
assert.Equal(t, tt.wantCombined, dateTimeRes)
dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(tt.format, true, true)
assert.NotEmpty(t, dateRes)
assert.NotEmpty(t, timeRes)
assert.Empty(t, unixTs)
assert.Equal(t, tt.wantCombined, dateRes+" "+timeRes)

// Also test individual components
d, tRes, _ := dateTimePrinter.RetrieveDateTime(true, false)
d, tRes, _ := dateTimePrinter.RetrieveDateTime(tt.format, true, false)
assert.Equal(t, tt.wantDate, d)
assert.Empty(t, tRes)

d, tRes, _ = dateTimePrinter.RetrieveDateTime(false, true)
d, tRes, _ = dateTimePrinter.RetrieveDateTime(tt.format, false, true)
assert.Empty(t, d)
assert.Equal(t, tt.wantTime, tRes)
})
Expand All @@ -121,48 +120,46 @@ func TestDateTimePrinter_UnixTimestamp(t *testing.T) {
return fixedFutureTime
},
}

dateTimePrinter.UpdateDateTimeFormat(shared.UnixTimestamp)
dateTimePrinter.init()

t.Run("Return unix timestamp combined", func(t *testing.T) {
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, true, true)
assert.Empty(t, dateRes)
assert.Empty(t, timeRes)
assert.Equal(t, expectedUnix, dateTimeRes)
})

t.Run("Return unix timestamp with date flag only", func(t *testing.T) {
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, false)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, true, false)
assert.Empty(t, dateRes)
assert.Empty(t, timeRes)
assert.Equal(t, expectedUnix, dateTimeRes)
})

t.Run("Return unix timestamp with time flag only", func(t *testing.T) {
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, true)
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, false, true)
assert.Empty(t, dateRes)
assert.Empty(t, timeRes)
assert.Equal(t, expectedUnix, dateTimeRes)
})
}

func TestNewDateTimePrinter(t *testing.T) {
assert.NotNil(t, NewDateTimePrinter())
assert.IsType(t, &DateTimePrinter{}, NewDateTimePrinter())
assert.NotNil(t, GetDateTimePrinter())
assert.IsType(t, &DateTimePrinter{}, GetDateTimePrinter())
}

func TestDateTimePrinter_FullSecondUpdate(t *testing.T) {
dateTimePrinter := NewDateTimePrinter()
dateTimePrinter := GetDateTimePrinter()

t.Run("Return both date and time", func(t *testing.T) {
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
dateRes, timeRes, _ := dateTimePrinter.RetrieveDateTime(true, true)
assert.Empty(t, dateRes)
assert.Empty(t, timeRes)
dateRes, timeRes, _ := dateTimePrinter.RetrieveDateTime(shared.IT, true, true)
assert.NotEmpty(t, dateRes)
assert.NotEmpty(t, timeRes)

prevTime := dateTimePrinter.currentTime.Load()
prevTime := dateTimePrinter.cachedTimes[shared.IT]
time.Sleep(2 * time.Second)
currTime := dateTimePrinter.currentTime.Load()
currTime := dateTimePrinter.cachedTimes[shared.IT]

assert.NotEqual(t, prevTime, currTime,
"The current %s time should have changed from previous time", currTime, prevTime)
Expand Down
Loading