scrobblerlog: Allow configuring fallback time zone

Fixes #6
This commit is contained in:
Philipp Wolfer 2025-04-29 10:05:40 +02:00
parent 0f4b04c641
commit ed191d2f15
No known key found for this signature in database
GPG key ID: 8FDF744D4919943B
5 changed files with 68 additions and 22 deletions

View file

@ -71,10 +71,12 @@ type Record struct {
// Represents a scrobbler log file.
type ScrobblerLog struct {
TZ TZInfo
Client string
Records []Record
location *time.Location
TZ TZInfo
Client string
Records []Record
// Timezone to be used for timestamps in the log file,
// if TZ is set to [TZ_UNKNOWN].
FallbackTimezone *time.Location
}
func (l *ScrobblerLog) Parse(data io.Reader, includeSkipped bool) error {
@ -173,7 +175,6 @@ func (l *ScrobblerLog) ReadHeader(reader *bufio.Reader) error {
timezone, found := strings.CutPrefix(text, "#TZ/")
if found {
l.TZ = TZInfo(timezone)
l.location = locationFromTimezone(l.TZ)
continue
}
@ -223,6 +224,11 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) {
return record, err
}
var timezone *time.Location = nil
if l.TZ == TZ_UNKNOWN {
timezone = l.FallbackTimezone
}
record = Record{
ArtistName: row[0],
AlbumName: row[1],
@ -230,7 +236,7 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) {
TrackNumber: trackNumber,
Duration: time.Duration(duration) * time.Second,
Rating: Rating(row[5]),
Timestamp: timeFromLocalTimestamp(timestamp, l.location),
Timestamp: timeFromLocalTimestamp(timestamp, timezone),
}
if len(row) > 7 {
@ -240,26 +246,20 @@ func (l ScrobblerLog) rowToRecord(row []string) (Record, error) {
return record, 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 TZInfo) *time.Location {
location, err := time.LoadLocation(string(timezone))
if err != nil {
return time.UTC
}
return location
}
// Convert a Unix timestamp to a time.Time object, but treat the timestamp
// Convert a Unix timestamp to a [time.Time] object, but treat the timestamp
// as being in the given location's timezone instead of UTC.
// If location is nil, the timestamp is returned as 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)
// The time is now in UTC. Get the offset to the requested timezone
// and shift the time accordingly.
if location != nil {
_, offset := t.In(location).Zone()
if offset != 0 {
t = t.Add(time.Duration(offset) * time.Second)
}
}
return t
}

View file

@ -81,6 +81,21 @@ func TestParserExcludeSkipped(t *testing.T) {
record4.MusicBrainzRecordingID)
}
func TestParserFallbackTimezone(t *testing.T) {
assert := assert.New(t)
data := bytes.NewBufferString(testScrobblerLog)
result := scrobblerlog.ScrobblerLog{
FallbackTimezone: time.FixedZone("UTC+2", 7200),
}
err := result.Parse(data, false)
require.NoError(t, err)
record1 := result.Records[0]
assert.Equal(
time.Unix(1260342084, 0).Add(2*time.Hour),
record1.Timestamp,
)
}
func TestAppend(t *testing.T) {
assert := assert.New(t)
data := make([]byte, 0, 10)