package app import ( "database/sql" "fmt" "reflect" "time" ) const dateFormat = "2006-01-02" const dateTimeFormat = "2006-01-02T15:04:05-0800" // FieldsAndValues is a struct that holds field names and corresponding values for an object. type FieldsAndValues struct { fieldNames []string fieldValues []interface{} } // GetFieldsAndValues takes an object and returns a pointer to a FieldsAndValues struct // containing the field names and corresponding values for each non-zero field, // excluding the "ID" field. func GetFieldsAndValues(obj interface{}) *FieldsAndValues { result := &FieldsAndValues{ fieldNames: []string{}, fieldValues: []interface{}{}, } v := reflect.ValueOf(obj).Elem() t := v.Type() for i := 0; i < t.NumField(); i++ { field := t.Field(i) fieldValue := v.Field(i) if field.Name != "ID" { if !IsZero(fieldValue) { fmt.Printf("DEBUG: Adding field %s with value %v\n", field.Name, fieldValue.Interface()) result.fieldNames = append(result.fieldNames, field.Name) result.fieldValues = append(result.fieldValues, fieldValue.Interface()) } } } return result } // IsZero checks if a value is zero or considered zero-like (e.g., empty arrays or slices, nil pointers). // Note that strings, including empty strings, are treated as non-zero values. func IsZero(v reflect.Value) bool { switch v.Kind() { case reflect.String: return false // Treat strings, including empty strings, as non-zero values case reflect.Struct: for i := 0; i < v.NumField(); i++ { if !IsZero(v.Field(i)) { return false } } return true case reflect.Ptr, reflect.Interface: if v.IsNil() { return true } return IsZero(v.Elem()) case reflect.Array, reflect.Slice: return v.Len() == 0 default: z := reflect.Zero(v.Type()) return v.Interface() == z.Interface() } } // SqlDateToString takes a pointer to a sql.NullTime object and returns a pointer to a string // representing the date in dateTimeFormat if the sql.NullTime object is valid, otherwise returns nil. func SqlDateToString(d *sql.NullTime) *string { if d == nil || !d.Valid { return nil } dateStr := d.Time.Format(dateTimeFormat) return &dateStr } // StringToSqlDate takes a pointer to a string representing a date and returns a pointer to // a sql.NullTime object containing the parsed date if the string is not nil, otherwise returns nil. func StringToSqlDate(s *string) *sql.NullTime { if s == nil { return nil } parsedTime, err := ParseDateTime(s) if err != nil { return nil } return &sql.NullTime{ Time: *parsedTime, Valid: true, } } // ParseDateTime takes a pointer to a string representing a date and returns a pointer to // a time.Time object containing the parsed date if the string is in one of the supported formats. // If the string is nil or not in a supported format, returns an error. func ParseDateTime(dateStr *string) (*time.Time, error) { if dateStr == nil { return nil, fmt.Errorf("✋members.parseDateTime: dateStr is null") } loc, _ := time.LoadLocation("Local") // formats := []string{dateFormat, dateTimeFormat, time.RFC1123, time.RFC3339} var parsedTime *time.Time for _, format := range formats { tm, err := time.ParseInLocation(format, *dateStr, loc) if err == nil { parsedTime = &tm break } } if parsedTime == nil { return nil, fmt.Errorf("✋parseDateTime: input string doesn't match any of the expected formats") } return parsedTime, nil }