diff --git a/csv_utils/csv_utils.go b/csv_utils/csv_utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..c3c728d04338ac6e0be1f3a62acd61ed76cd5b12
--- /dev/null
+++ b/csv_utils/csv_utils.go
@@ -0,0 +1,109 @@
+package csv_utils
+
+import (
+	"bytes"
+	"database/sql"
+	"encoding/csv"
+	"fmt"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/date_utils"
+	"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
+)
+
+// ToCsvBytes takes a list of header lines and data and returns the data in CSV format, as a byte slice.
+func ToCSVBytes(generatedInfo []string, headers []string, data [][]string) ([]byte, error) {
+	b, err := getCSVData(generatedInfo, headers, data)
+
+	return b.Bytes(), err
+}
+
+func getCSVData(generatedInfo []string, headers []string, data [][]string) (*bytes.Buffer, error) {
+	buffer := bytes.NewBuffer([]byte{})
+	csvWriter := csv.NewWriter(buffer)
+
+	if len(generatedInfo) > 0 {
+		if err := csvWriter.Write(generatedInfo); err != nil {
+			return nil, err
+		}
+	}
+
+	if err := csvWriter.Write(headers); err != nil {
+		return nil, err
+	}
+
+	for _, v := range data {
+		if err := csvWriter.Write(v); err != nil {
+			return nil, err
+		}
+	}
+
+	csvWriter.Flush()
+
+	return buffer, nil
+}
+
+// GetGenerationInfoForReport takes the csv event and returns the username and time of the request to generate the report.
+func GetGenerationInfoForReport(username string) []string {
+	if username == "" {
+		username = "unknown user"
+	}
+	generatedAt := date_utils.CurrentDate().Format("02 Jan 2006 15:04")
+
+	return []string{
+		fmt.Sprint("Generated by ", username, " on ", generatedAt),
+	}
+}
+
+// KeepLeadingZeroesInCsvField prepends a tab character to a string if it is numeric, so that leading zeroes will not be removed by the CSV
+func KeepLeadingZeroesInCsvField(fieldString string) string {
+	if string_utils.IsNumericString(fieldString) {
+		if fieldString[0:1] == "0" {
+			return fmt.Sprintf("\t%s", fieldString)
+		}
+	}
+	return fieldString
+}
+
+func SQLToCSVData(rows *sql.Rows) ([]byte, error) {
+	// Get the headers
+	columns, err := rows.Columns()
+	if err != nil {
+		return nil, err
+	}
+
+	// Get the data
+	values := make([]interface{}, len(columns))
+	scan_args := make([]interface{}, len(columns))
+	for i := range values {
+		scan_args[i] = &values[i]
+	}
+
+	var data [][]string
+	for rows.Next() {
+		row := make([]string, len(columns))
+
+		err = rows.Scan(scan_args...)
+		if err != nil {
+			return nil, err
+		}
+
+		for i, _ := range columns {
+			var value interface{}
+			rawValue := values[i]
+			byteArray, ok := rawValue.([]byte)
+			if ok {
+				value = string(byteArray)
+			} else {
+				value = rawValue
+			}
+			if value == nil {
+				row[i] = ""
+			} else {
+				row[i] = fmt.Sprintf("%v", value)
+			}
+		}
+		data = append(data, row)
+	}
+
+	b, err := getCSVData(nil, columns, data)
+	return b.Bytes(), err
+}