Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

# Change Log

## 2025-06-23 - Runtime 0.18.0-alpha.13

- fix: time zone retrieval and logging [#906](https://github.com/hypermodeinc/modus/pull/906)

## 2025-06-23 - Runtime 0.18.0-alpha.12

- fix: ensure valid UTF8 byte sequence in HTTP response [#904](https://github.com/hypermodeinc/modus/pull/904)
Expand Down
28 changes: 21 additions & 7 deletions runtime/hostfunctions/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,39 @@ func LogMessage(ctx context.Context, level, message string) {
func GetTimeInZone(ctx context.Context, tz *string) *string {
now := time.Now()

var loc *time.Location
var zoneId string
if tz != nil && *tz != "" {
loc = timezones.GetLocation(*tz)
zoneId = *tz
} else if tz, ok := ctx.Value(utils.TimeZoneContextKey).(string); ok {
loc = timezones.GetLocation(tz)
zoneId = tz
} else {
logger.Error(ctx).Msg("Time zone not specified.")
return nil
}

if loc == nil {
loc, err := timezones.GetLocation(ctx, zoneId)
if err != nil {
logger.Err(ctx, err).Str("tz", zoneId).Msg("Failed to get time zone location.")
return nil
}

s := now.In(loc).Format(time.RFC3339Nano)
return &s
}

func GetTimeZoneData(tz, format *string) []byte {
func GetTimeZoneData(ctx context.Context, tz, format *string) []byte {
if tz == nil {
logger.Error(ctx).Msg("Time zone not specified.")
return nil
}

return timezones.GetTimeZoneData(*tz, *format)
if format == nil {
logger.Error(ctx).Msg("Time zone format not specified.")
return nil
}
data, err := timezones.GetTimeZoneData(ctx, *tz, *format)
if err != nil {
logger.Error(ctx).Err(err).Str("tz", *tz).Msg("Failed to get time zone data.")
return nil
}
return data
}
59 changes: 36 additions & 23 deletions runtime/timezones/timezones.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,18 @@
package timezones

import (
"context"
"errors"
"fmt"
"os"
"time"

"github.com/puzpuzpuz/xsync/v4"
)

type tzInfo struct {
location *time.Location
data []byte
}

var systemTimeZone string
var tzCache = *xsync.NewMap[string, *tzInfo]()
var tzDataCache = *xsync.NewMap[string, []byte]()
var tzLocationCache = *xsync.NewMap[string, *time.Location]()

func init() {
if tz, err := getSystemLocalTimeZone(); err == nil {
Expand All @@ -42,48 +41,62 @@ func GetLocalTimeZone() string {
return systemTimeZone
}

func GetLocation(tz string) *time.Location {
func GetLocation(ctx context.Context, tz string) (*time.Location, error) {
if tz == "" {
return nil
return nil, errors.New("time zone cannot be empty")
}

info, err := getTimeZoneInfo(tz)
loc, err := getTimeZoneLocation(tz)
if err != nil {
return nil
return nil, fmt.Errorf("failed to load time zone location for %s: %w", tz, err)
}

return info.location
return loc, nil
}

func GetTimeZoneData(tz, format string) []byte {
func GetTimeZoneData(ctx context.Context, tz, format string) ([]byte, error) {
if tz == "" {
return nil
return nil, errors.New("time zone cannot be empty")
}

// only support tzif format for now
// we can expand this to support other formats as needed
if format != "tzif" {
return nil
return nil, fmt.Errorf("unsupported time zone format: %s", format)
}

data, err := getTimeZoneData(tz)
if err != nil {
return nil, fmt.Errorf("failed to load time zone data for %s: %w", tz, err)
}

info, err := getTimeZoneInfo(tz)
return data, nil
}

func getTimeZoneLocation(tz string) (*time.Location, error) {
if loc, ok := tzLocationCache.Load(tz); ok {
return loc, nil
}

loc, err := time.LoadLocation(tz)
if err != nil {
return nil
return nil, err
}

return info.data
tzLocationCache.Store(tz, loc)
return loc, nil
}

func getTimeZoneInfo(tz string) (*tzInfo, error) {
if info, ok := tzCache.Load(tz); ok {
return info, nil
func getTimeZoneData(tz string) ([]byte, error) {
if data, ok := tzDataCache.Load(tz); ok {
return data, nil
}

info, err := loadTimeZoneInfo(tz)
data, err := loadTimeZoneData(tz)
if err != nil {
return nil, err
}

tzCache.Store(tz, info)
return info, nil
tzDataCache.Store(tz, data)
return data, nil
}
18 changes: 7 additions & 11 deletions runtime/timezones/tzdata_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,23 @@ import (
"os"
"path"
"strings"
"time"
)

func loadTimeZoneInfo(tz string) (*tzInfo, error) {
func loadTimeZoneData(tz string) ([]byte, error) {
tzFile := "/usr/share/zoneinfo/" + tz
if _, err := os.Stat(tzFile); err != nil {
return nil, fmt.Errorf("could not find timezone file: %v", err)
return nil, fmt.Errorf("could not find time zone file: %v", err)
}

bytes, err := os.ReadFile(tzFile)
data, err := os.ReadFile(tzFile)
if err != nil {
return nil, fmt.Errorf("could not read timezone file: %v", err)
return nil, fmt.Errorf("could not read time zone file: %v", err)
}

loc, err := time.LoadLocationFromTZData(tz, bytes)
if err != nil {
return nil, fmt.Errorf("could not load timezone data: %v", err)
if len(data) == 0 {
return nil, errors.New("time zone data is empty")
}

info := &tzInfo{loc, bytes}
return info, nil
return data, nil
}

func getSystemLocalTimeZone() (string, error) {
Expand Down
16 changes: 6 additions & 10 deletions runtime/timezones/tzdata_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
package timezones

import (
"errors"
"fmt"
"time"
"unsafe"

_ "time/tzdata"
Expand All @@ -27,22 +27,18 @@ import (
//go:linkname loadFromEmbeddedTZData time/tzdata.loadFromEmbeddedTZData
func loadFromEmbeddedTZData(string) (string, error)

func loadTimeZoneInfo(tz string) (*tzInfo, error) {

func loadTimeZoneData(tz string) ([]byte, error) {
var data []byte
if s, err := loadFromEmbeddedTZData(tz); err != nil {
return nil, fmt.Errorf("could not load timezone data: %v", err)
return nil, fmt.Errorf("could not load time zone data: %v", err)
} else {
data = []byte(s)
}

loc, err := time.LoadLocationFromTZData(tz, data)
if err != nil {
return nil, fmt.Errorf("could not load timezone data: %v", err)
if len(data) == 0 {
return nil, errors.New("time zone data is empty")
}

info := &tzInfo{loc, data}
return info, nil
return data, nil
}

func getSystemLocalTimeZone() (string, error) {
Expand Down
Loading