diff --git a/config.go.example b/config.go.example index c08f40b..729b411 100644 --- a/config.go.example +++ b/config.go.example @@ -8,4 +8,7 @@ const ( // Network settings Hostname = "pico2w-clock" EnableICMP = true + + // Time settings + ActiveTimezone = "UTC" ) diff --git a/main.go b/main.go index 42e1320..3903237 100644 --- a/main.go +++ b/main.go @@ -128,30 +128,31 @@ func main() { lastSecond := -1 + // Remove static call before the loop, then in the loop: for { currentTime := time.Now().Add(timeOffset).UTC() currentSecond := currentTime.Second() - // Skip display refreshes if the second hasn't changed to save the i2c bus if currentSecond != lastSecond { lastSecond = currentSecond - // Alternate colon (:) with space ( ) every second timeSeperator := ":" - if currentSecond % 2 == 1 { + if currentSecond%2 == 1 { timeSeperator = " " } - // Create the awful time string before displaying it - timeStr := fmt.Sprintf("%02d%s%02d%s%02d UTC", - currentTime.Hour(), timeSeperator, - currentTime.Minute(), timeSeperator, - currentTime.Second()) + // Dynamically determine DST vs Standard time for this second + loc, zoneName := ResolveTimezone(ActiveTimezone, currentTime) + localTime := currentTime.In(loc) + + timeStr := fmt.Sprintf("%02d%s%02d%s%02d %s", + localTime.Hour(), timeSeperator, + localTime.Minute(), timeSeperator, + localTime.Second(), + zoneName) oled.ClearBuffer() - // Time tinyfont.WriteLine(oled, defaultFont, 8, 32, timeStr, white) - // IP Address tinyfont.WriteLine(oled, tinyFont, 0, 60, fmt.Sprintf("IP: %s", ipStr), white) oled.Display() } diff --git a/time.go b/time.go new file mode 100644 index 0000000..86a9e24 --- /dev/null +++ b/time.go @@ -0,0 +1,91 @@ +package main + +import ( + "time" +) + +type TimezoneConfig struct { + StandardName string + StandardOffset int + DaylightName string + DaylightOffset int + ObservesUSDST bool +} + +// Map timezone aliases to standard/daylight offsets +var timezones = map[string]TimezoneConfig{ + // Dynamic US zones (switch automatically between Standard and Daylight) + "US/Eastern": {"EST", -5 * 3600, "EDT", -4 * 3600, true}, + "ET": {"EST", -5 * 3600, "EDT", -4 * 3600, true}, + "US/Central": {"CST", -6 * 3600, "CDT", -5 * 3600, true}, + "CT": {"CST", -6 * 3600, "CDT", -5 * 3600, true}, + "US/Mountain": {"MST", -7 * 3600, "MDT", -6 * 3600, true}, + "MT": {"MST", -7 * 3600, "MDT", -6 * 3600, true}, + "US/Pacific": {"PST", -8 * 3600, "PDT", -7 * 3600, true}, + "PT": {"PST", -8 * 3600, "PDT", -7 * 3600, true}, + + // Fixed non-DST zones + "UTC": {"UTC", 0, "UTC", 0, false}, + "EST": {"EST", -5 * 3600, "EST", -5 * 3600, false}, + "EDT": {"EDT", -4 * 3600, "EDT", -4 * 3600, false}, + "CST": {"CST", -6 * 3600, "CST", -6 * 3600, false}, + "CDT": {"CDT", -5 * 3600, "CDT", -5 * 3600, false}, + "MST": {"MST", -7 * 3600, "MST", -7 * 3600, false}, + "MDT": {"MDT", -6 * 3600, "MDT", -6 * 3600, false}, + "PST": {"PST", -8 * 3600, "PST", -8 * 3600, false}, + "PDT": {"PDT", -7 * 3600, "PDT", -7 * 3600, false}, +} + +// IsUSDST checks if the given UTC time falls in US Daylight Saving Time +// (Second Sunday in March at 02:00 local to first Sunday in November at 02:00 local). +func IsUSDST(t time.Time, stdOffsetSec int) bool { + month := t.Month() + if month < time.March || month > time.November { + return false // Dec, Jan, Feb + } + if month > time.March && month < time.November { + return true // Apr through Oct + } + + day := t.Day() + weekday := int(t.Weekday()) + + if month == time.March { + // Second Sunday in March is between 8th and 14th + prevSunday := day - weekday + if prevSunday < 8 { + return false + } + if prevSunday >= 15 { + return true + } + // Transition occurs at 2:00 AM standard local time -> (2 - stdOffsetHours) in UTC + stdOffsetHours := stdOffsetSec / 3600 + switchHourUTC := 2 - stdOffsetHours + return weekday != int(time.Sunday) || t.Hour() >= switchHourUTC + } + + // November: first Sunday is between 1st and 7th + prevSunday := day - weekday + if prevSunday < 1 { + return true // Before first Sunday + } + // Fall-back occurs at 2:00 AM daylight local time -> (2 - (stdOffsetHours + 1)) in UTC + stdOffsetHours := stdOffsetSec / 3600 + switchHourUTC := 2 - (stdOffsetHours + 1) + return weekday == int(time.Sunday) && t.Hour() < switchHourUTC +} + +// ResolveTimezone dynamically determines the *time.Location and label +// based on whether the zone observes DST at the given UTC time. +func ResolveTimezone(name string, utcNow time.Time) (*time.Location, string) { + cfg, ok := timezones[name] + if !ok { + return time.FixedZone("UTC", 0), "UTC" + } + + if cfg.ObservesUSDST && IsUSDST(utcNow, cfg.StandardOffset) { + return time.FixedZone(cfg.DaylightName, cfg.DaylightOffset), cfg.DaylightName + } + return time.FixedZone(cfg.StandardName, cfg.StandardOffset), cfg.StandardName +}