bun icon indicating copy to clipboard operation
bun copied to clipboard

Location Data is Ignored if UTC() was ever called on the time struct

Open comsma opened this issue 8 months ago • 4 comments

I had to build a query hook for bun that allows me to apply the database time zone loc to all time structs before inserting them into the database. For instance i am creating an order and when i call the .UTC() method on the time it causes the .In() called in the time zone hook. When printing the requested date into the console it shows the time with the database offset but bun inserts the time as utc still. This issue goes away if UTC() was not called and no location was set but i do not have control over the fact the UTC() was called because my protobuf api has a helper method called AsTime() that returns the time struct after calling the UTC() function.

I am using the mssql dialect v1.2.11

// Order Repository
requestedDate := time.Unix(1745335353,0).UTC()
orderDate := time.Unix(1745428361,0)

prophet.OeHdr{
			
			OrderNo:        strconv.Itoa(int(orderNo)),
			OrderDate:        &orderDate,
			RequestedDate:    &requestedDate,
			
}
// timeZoneHook
type TimeZoneHook struct {
	dbLocation *time.Location
}

func (h TimeZoneHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context {
	// Process different query types
	switch event.IQuery.(type) {
	case *bun.InsertQuery, *bun.UpdateQuery:
		// Skip processing if there is no model
		if event.Model == nil {
			return ctx
		}

		// Get the model value
		val := reflect.ValueOf(event.Model.Value())

		// Handle pointer types
		if val.Kind() == reflect.Ptr {
			if val.IsNil() {
				return ctx
			}
			val = val.Elem()
		}

		// Check if the value is valid for processing
		if !val.IsValid() {
			return ctx
		}

		// Process based on the kind of value
		switch val.Kind() {
		case reflect.Struct:
			// Process struct directly
			h.inDBTimezone(val)

		case reflect.Slice:
			// Process each element in the slice
			for i := 0; i < val.Len(); i++ {
				elem := val.Index(i)
				// If element is a struct or pointer to struct
				if elem.Kind() == reflect.Struct {
					h.inDBTimezone(val)
				} else if elem.Kind() == reflect.Ptr && !elem.IsNil() {
					elemVal := elem.Elem()
					if elemVal.Kind() == reflect.Struct {
						h.inDBTimezone(val)
					}
				}
			}
		}
	}
	return ctx
}

// inDBTimezone converts time fields to the database time zone
func (h TimeZoneHook) inDBTimezone(val reflect.Value) {
	h.processTimeFields(val, func(t *time.Time) time.Time {
		return t.In(h.dbLocation)
	})
}

comsma avatar Apr 23 '25 17:04 comsma

This issue has been automatically marked as stale because it has not had activity in the last 30 days. If there is no update within the next 7 days, this issue will be closed.

github-actions[bot] avatar May 24 '25 03:05 github-actions[bot]

I assume you are using PostgreSQL, which indeed converts timestamps to UTC when using the Append function. https://github.com/uptrace/bun/blob/master/driver/pgdriver/format.go#L129

If the corresponding database field includes time zone information, like timestamptz, using 2025-04-22 03:22:00+00 or 2025-04-22 11:22:00+08 will produce the same result.

j2gg0s avatar May 25 '25 13:05 j2gg0s

This is mssql(Microsoft sql server)

comsma avatar May 25 '25 13:05 comsma

@comsma

If you’re using MSSQL, try setting time.Local to your target time zone.

j2gg0s avatar May 26 '25 03:05 j2gg0s

This issue has been automatically marked as stale because it has not had activity in the last 30 days. If there is no update within the next 7 days, this issue will be closed.

github-actions[bot] avatar Jun 26 '25 03:06 github-actions[bot]