scrobblerlog: consider timezone from parsed file

This commit is contained in:
Philipp Wolfer 2025-04-28 08:54:17 +02:00
parent 9184d2c3cf
commit 69665bc286
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B

View file

@ -1,5 +1,5 @@
/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
Copyright © 2023-2025 Philipp Wolfer <phw@uploadedlobster.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -38,6 +38,7 @@ type ScrobblerLog struct {
Timezone string
Client string
Listens models.ListensList
location *time.Location
}
func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) {
@ -79,8 +80,7 @@ func Parse(data io.Reader, includeSkipped bool) (ScrobblerLog, error) {
continue
}
client := strings.Split(result.Client, " ")[0]
listen, err := rowToListen(row, client)
listen, err := result.rowToListen(row)
if err != nil {
return result, err
}
@ -138,14 +138,19 @@ func ReadHeader(reader *bufio.Reader, log *ScrobblerLog) error {
err = fmt.Errorf("not a scrobbler log file")
}
// The timezone can be set to "UTC" or "UNKNOWN", if the device writing
// the log knows the time, but not the timezone.
timezone, found := strings.CutPrefix(text, "#TZ/")
if found {
log.Timezone = timezone
log.location = locationFromTimezone(log.Timezone)
continue
}
client, found := strings.CutPrefix(text, "#CLIENT/")
if found {
log.Client = client
continue
}
}
@ -171,7 +176,7 @@ func WriteHeader(writer io.Writer, log *ScrobblerLog) error {
return nil
}
func rowToListen(row []string, client string) (models.Listen, error) {
func (l ScrobblerLog) rowToListen(row []string) (models.Listen, error) {
var listen models.Listen
trackNumber, err := strconv.Atoi(row[3])
if err != nil {
@ -183,11 +188,12 @@ func rowToListen(row []string, client string) (models.Listen, error) {
return listen, err
}
timestamp, err := strconv.Atoi(row[6])
timestamp, err := strconv.ParseInt(row[6], 10, 64)
if err != nil {
return listen, err
}
client := strings.Split(l.Client, " ")[0]
listen = models.Listen{
Track: models.Track{
ArtistNames: []string{row[0]},
@ -200,7 +206,7 @@ func rowToListen(row []string, client string) (models.Listen, error) {
"media_player": client,
},
},
ListenedAt: time.Unix(int64(timestamp), 0),
ListenedAt: timeFromLocalTimestamp(timestamp, l.location),
}
if len(row) > 7 {
@ -209,3 +215,27 @@ func rowToListen(row []string, client string) (models.Listen, error) {
return listen, nil
}
// Convert the timezone string from the header to a time.Location.
// Often this is set to "UNKNOWN" in the log file, in which case it defaults
// to UTC.
func locationFromTimezone(timezone string) *time.Location {
location, err := time.LoadLocation(timezone)
if err != nil {
return time.UTC
}
return location
}
// Convert a Unix timestamp to a time.Time object, but treat the timestamp
// as being in the given location's timezone instead of UTC.
func timeFromLocalTimestamp(timestamp int64, location *time.Location) time.Time {
t := time.Unix(timestamp, 0)
// The time is now in UTC. Get the offset to the requested timezone.
_, offset := t.In(location).Zone()
if offset != 0 {
t = t.Add(time.Duration(offset) * time.Second)
}
return t
}