Skip to content

Commit

Permalink
Add average monthly and yearly messages
Browse files Browse the repository at this point in the history
  • Loading branch information
f-ewald committed May 28, 2022
1 parent eb8c491 commit 7208064
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 18 deletions.
63 changes: 47 additions & 16 deletions pkg/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ func (db *Database) Close() (err error) {

// Statistics contains all metrics that are shown to the user.
type Statistics struct {
TotalMessages int `json:"total_messages" yaml:"total-messages"`
ReceivedMessages int `json:"received_messages" yaml:"received-messages"`
SentMessages int `json:"sent_messages" yaml:"sent-messages"`
AvgDailyMessages float64 `json:"avg_daily_messages" yaml:"avg-daily-messages"`
Chats int `json:"chats" yaml:"chats"`
FirstMessage time.Time `json:"first_message" yaml:"first-message"`
LastMessage time.Time `json:"last_message" yaml:"last-message"`
TotalMessages int `json:"total_messages" yaml:"total-messages"`
ReceivedMessages int `json:"received_messages" yaml:"received-messages"`
SentMessages int `json:"sent_messages" yaml:"sent-messages"`
AvgDailyMessages float64 `json:"avg_daily_messages" yaml:"avg-daily-messages"`
AvgMonthlyMessages float64 `json:"avg_monthly_messages" yaml:"avg-monthly-messages"`
AvgYearlyMessages float64 `json:"avg_yearly_messages" yaml:"avg-yearly-messages"`
Chats int `json:"chats" yaml:"chats"`
FirstMessage time.Time `json:"first_message" yaml:"first-message"`
LastMessage time.Time `json:"last_message" yaml:"last-message"`
}

func (db *Database) Statistics(ctx context.Context) (stats *Statistics, err error) {
Expand All @@ -68,13 +70,6 @@ func (db *Database) Statistics(ctx context.Context) (stats *Statistics, err erro
return nil, err
}

// Average daily messages
row = db.db.QueryRowContext(ctx, "SELECT AVG(m.c) FROM (select count(*) AS c from message GROUP BY strftime('%Y-%m-%d', datetime(date/1000000000 + strftime('%s','2001-01-01'), 'unixepoch'))) AS m")
err = row.Scan(&stats.AvgDailyMessages)
if err != nil {
return nil, err
}

row = db.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM chat")
err = row.Scan(&stats.Chats)
if err != nil {
Expand All @@ -89,12 +84,48 @@ func (db *Database) Statistics(ctx context.Context) (stats *Statistics, err erro
return nil, err
}
// Epoch in iMessage starts at 2001-01-01 as opposed to 1970-01-01 in Go.
stats.FirstMessage = time.UnixMicro(firstMessage/1e3).AddDate(31, 0, 0)
stats.LastMessage = time.UnixMicro(lastMessage/1e3).AddDate(31, 0, 0)
stats.FirstMessage = time.UnixMicro(firstMessage/1e3).
AddDate(31, 0, 0).
Round(time.Second)
stats.LastMessage = time.UnixMicro(lastMessage/1e3).
AddDate(31, 0, 0).
Round(time.Second)

// Total days are defined as full 24-hour blocks
totalDays := float64(int(stats.LastMessage.Sub(stats.FirstMessage).Hours() / 24))
stats.AvgDailyMessages = float64(stats.TotalMessages) / totalDays

totalMonths := monthsSince(stats.FirstMessage, stats.LastMessage)
stats.AvgMonthlyMessages = float64(stats.TotalMessages) / float64(totalMonths)

totalYears := yearsSince(stats.FirstMessage, stats.LastMessage)
stats.AvgYearlyMessages = float64(stats.TotalMessages) / float64(totalYears)

return stats, nil
}

// monthsSince counts the months between two points in time, rounding up to the next full month.
func monthsSince(t1 time.Time, t2 time.Time) int {
if t2.Before(t1) {
return 0
}
count := 1
month := t1.UTC().Month()
for t1.Before(t2) {
if t1.Month() != month {
count += 1
month = t1.UTC().Month()
}
t1 = t1.AddDate(0, 0, 1)
}
return count
}

// yearsSince returns the number of full years between two timestamps.
func yearsSince(t1 time.Time, t2 time.Time) int {
return monthsSince(t1, t2) / 12
}

// Participant is represented by a handle in the database
type Participant struct {
ID int `json:"id" yaml:"id"`
Expand Down
126 changes: 126 additions & 0 deletions pkg/db_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package hermes

import (
"github.com/stretchr/testify/assert"
"testing"
"time"
)

func TestMonthsSince(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
t1 time.Time
t2 time.Time
expected int
}{
{
name: "365 days",
t1: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2011, 1, 1, 0, 0, 0, 0, time.UTC),
expected: 12,
},
{
name: "366 days",
t1: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2011, 1, 2, 0, 0, 0, 0, time.UTC),
expected: 13,
},
{
name: "negative time",
t1: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC),
expected: 0,
},
{
name: "one month",
t1: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2010, 2, 1, 0, 0, 0, 0, time.UTC),
expected: 1,
},
{
name: "round up",
t1: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2010, 5, 2, 23, 0, 0, 0, time.UTC),
expected: 5,
},
{
name: "15 days",
t1: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2010, 1, 15, 0, 0, 0, 0, time.UTC),
expected: 1,
},
{
name: "31 days",
t1: time.Date(2010, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2010, 1, 31, 23, 0, 0, 0, time.UTC),
expected: 1,
},
{
name: "last day to first day",
t1: time.Date(2010, 1, 31, 0, 0, 0, 0, time.UTC),
t2: time.Date(2010, 2, 1, 0, 0, 0, 0, time.UTC),
expected: 1,
},
{
name: "last day to second day",
t1: time.Date(2010, 1, 31, 0, 0, 0, 0, time.UTC),
t2: time.Date(2010, 2, 2, 0, 0, 0, 0, time.UTC),
expected: 2,
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.expected, monthsSince(testCase.t1, testCase.t2))
})
}
}

func TestYearsSince(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
t1 time.Time
t2 time.Time
expected int
}{
{
name: "same date",
t1: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
expected: 0,
},
{
name: "next day",
t1: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC),
expected: 0,
},
{
name: "next month",
t1: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
expected: 0,
},
{
name: "next year",
t1: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
expected: 1,
},
{
name: "previous year",
t1: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
t2: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC),
expected: 0,
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.expected, yearsSince(testCase.t1, testCase.t2))
})
}
}
4 changes: 2 additions & 2 deletions templates/statistics.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ Sent messages: {{ .SentMessages }}
First message: {{ .FirstMessage }}
Last message: {{ .LastMessage }}
Daily Average: {{ printf "%.2f" .AvgDailyMessages }}
Monthly Average: <Not available>
Yearly Average: <Not available>
Monthly Average: {{ printf "%.2f" .AvgMonthlyMessages }}
Yearly Average: {{ printf "%.2f" .AvgYearlyMessages }}
Chats: {{ .Chats }}

0 comments on commit 7208064

Please sign in to comment.