Repository: VictoriaMetrics/VictoriaMetrics
Pull request #5853 (2024-02-22 17:20:14)

changes export/csv timestamp formato for rfc3339
addressed #5837 


Patch:
diff --git a/app/vmselect/prometheus/export.qtpl b/app/vmselect/prometheus/export.qtpl
index e12a752b6..e26ecbbe6 100644
--- a/app/vmselect/prometheus/export.qtpl
+++ b/app/vmselect/prometheus/export.qtpl
@@ -24,6 +24,7 @@
 	{% endfor %}
 {% endfunc %}
 
+{%code const rfc3339Milli = "2006-01-02T15:04:05.999Z07:00" %}
 {% func exportCSVField(mn *storage.MetricName, fieldName string, timestamp int64, value float64) %}
 	{% if fieldName == "__value__" %}
 		{%f= value %}
@@ -45,7 +46,7 @@
 		{% case "rfc3339" %}
 			{% code
 				bb := quicktemplate.AcquireByteBuffer()
-				bb.B = time.Unix(timestamp/1000, (timestamp%1000)*1e6).AppendFormat(bb.B[:0], time.RFC3339)
+				bb.B = time.Unix(timestamp/1000, (timestamp%1000)*1e6).AppendFormat(bb.B[:0], rfc3339Milli)
 			%}
 			{%z= bb.B %}
 			{% code
diff --git a/app/vmselect/prometheus/export.qtpl.go b/app/vmselect/prometheus/export.qtpl.go
index 8018bdc86..51e872d97 100644
--- a/app/vmselect/prometheus/export.qtpl.go
+++ b/app/vmselect/prometheus/export.qtpl.go
@@ -87,586 +87,589 @@ func ExportCSVLine(xb *exportBlock, fieldNames []string) string {
 }
 
 //line app/vmselect/prometheus/export.qtpl:27
-func streamexportCSVField(qw422016 *qt422016.Writer, mn *storage.MetricName, fieldName string, timestamp int64, value float64) {
+const rfc3339Milli = "2006-01-02T15:04:05.999Z07:00"
+
 //line app/vmselect/prometheus/export.qtpl:28
-	if fieldName == "__value__" {
+func streamexportCSVField(qw422016 *qt422016.Writer, mn *storage.MetricName, fieldName string, timestamp int64, value float64) {
 //line app/vmselect/prometheus/export.qtpl:29
-		qw422016.N().F(value)
+	if fieldName == "__value__" {
 //line app/vmselect/prometheus/export.qtpl:30
-		return
+		qw422016.N().F(value)
 //line app/vmselect/prometheus/export.qtpl:31
-	}
+		return
 //line app/vmselect/prometheus/export.qtpl:32
-	if fieldName == "__timestamp__" {
+	}
 //line app/vmselect/prometheus/export.qtpl:33
-		qw422016.N().DL(timestamp)
+	if fieldName == "__timestamp__" {
 //line app/vmselect/prometheus/export.qtpl:34
-		return
+		qw422016.N().DL(timestamp)
 //line app/vmselect/prometheus/export.qtpl:35
-	}
+		return
 //line app/vmselect/prometheus/export.qtpl:36
-	if strings.HasPrefix(fieldName, "__timestamp__:") {
+	}
 //line app/vmselect/prometheus/export.qtpl:37
+	if strings.HasPrefix(fieldName, "__timestamp__:") {
+//line app/vmselect/prometheus/export.qtpl:38
 		timeFormat := fieldName[len("__timestamp__:"):]
 
-//line app/vmselect/prometheus/export.qtpl:38
-		switch timeFormat {
 //line app/vmselect/prometheus/export.qtpl:39
-		case "unix_s":
+		switch timeFormat {
 //line app/vmselect/prometheus/export.qtpl:40
-			qw422016.N().DL(timestamp / 1000)
+		case "unix_s":
 //line app/vmselect/prometheus/export.qtpl:41
-		case "unix_ms":
+			qw422016.N().DL(timestamp / 1000)
 //line app/vmselect/prometheus/export.qtpl:42
-			qw422016.N().DL(timestamp)
+		case "unix_ms":
 //line app/vmselect/prometheus/export.qtpl:43
-		case "unix_ns":
+			qw422016.N().DL(timestamp)
 //line app/vmselect/prometheus/export.qtpl:44
-			qw422016.N().DL(timestamp * 1e6)
+		case "unix_ns":
 //line app/vmselect/prometheus/export.qtpl:45
+			qw422016.N().DL(timestamp * 1e6)
+//line app/vmselect/prometheus/export.qtpl:46
 		case "rfc3339":
-//line app/vmselect/prometheus/export.qtpl:47
+//line app/vmselect/prometheus/export.qtpl:48
 			bb := quicktemplate.AcquireByteBuffer()
-			bb.B = time.Unix(timestamp/1000, (timestamp%1000)*1e6).AppendFormat(bb.B[:0], time.RFC3339)
+			bb.B = time.Unix(timestamp/1000, (timestamp%1000)*1e6).AppendFormat(bb.B[:0], rfc3339Milli)
 
-//line app/vmselect/prometheus/export.qtpl:50
+//line app/vmselect/prometheus/export.qtpl:51
 			qw422016.N().Z(bb.B)
-//line app/vmselect/prometheus/export.qtpl:52
+//line app/vmselect/prometheus/export.qtpl:53
 			quicktemplate.ReleaseByteBuffer(bb)
 
-//line app/vmselect/prometheus/export.qtpl:54
-		default:
 //line app/vmselect/prometheus/export.qtpl:55
+		default:
+//line app/vmselect/prometheus/export.qtpl:56
 			if strings.HasPrefix(timeFormat, "custom:") {
-//line app/vmselect/prometheus/export.qtpl:57
+//line app/vmselect/prometheus/export.qtpl:58
 				layout := timeFormat[len("custom:"):]
 				bb := quicktemplate.AcquireByteBuffer()
 				bb.B = time.Unix(timestamp/1000, (timestamp%1000)*1e6).AppendFormat(bb.B[:0], layout)
 
-//line app/vmselect/prometheus/export.qtpl:61
-				if bytes.ContainsAny(bb.B, `"`+",\n") {
 //line app/vmselect/prometheus/export.qtpl:62
-					qw422016.E().QZ(bb.B)
+				if bytes.ContainsAny(bb.B, `"`+",\n") {
 //line app/vmselect/prometheus/export.qtpl:63
-				} else {
+					qw422016.E().QZ(bb.B)
 //line app/vmselect/prometheus/export.qtpl:64
-					qw422016.N().Z(bb.B)
+				} else {
 //line app/vmselect/prometheus/export.qtpl:65
+					qw422016.N().Z(bb.B)
+//line app/vmselect/prometheus/export.qtpl:66
 				}
-//line app/vmselect/prometheus/export.qtpl:67
+//line app/vmselect/prometheus/export.qtpl:68
 				quicktemplate.ReleaseByteBuffer(bb)
 
-//line app/vmselect/prometheus/export.qtpl:69
+//line app/vmselect/prometheus/export.qtpl:70
 			} else {
-//line app/vmselect/prometheus/export.qtpl:69
-				qw422016.N().S(`Unsupported timeFormat=`)
 //line app/vmselect/prometheus/export.qtpl:70
-				qw422016.N().S(timeFormat)
+				qw422016.N().S(`Unsupported timeFormat=`)
 //line app/vmselect/prometheus/export.qtpl:71
-			}
+				qw422016.N().S(timeFormat)
 //line app/vmselect/prometheus/export.qtpl:72
-		}
+			}
 //line app/vmselect/prometheus/export.qtpl:73
-		return
+		}
 //line app/vmselect/prometheus/export.qtpl:74
-	}
+		return
 //line app/vmselect/prometheus/export.qtpl:75
+	}
+//line app/vmselect/prometheus/export.qtpl:76
 	v := mn.GetTagValue(fieldName)
 
-//line app/vmselect/prometheus/export.qtpl:76
-	if bytes.ContainsAny(v, `"`+",\n") {
 //line app/vmselect/prometheus/export.qtpl:77
-		qw422016.N().QZ(v)
+	if bytes.ContainsAny(v, `"`+",\n") {
 //line app/vmselect/prometheus/export.qtpl:78
-	} else {
+		qw422016.N().QZ(v)
 //line app/vmselect/prometheus/export.qtpl:79
-		qw422016.N().Z(v)
+	} else {
 //line app/vmselect/prometheus/export.qtpl:80
-	}
+		qw422016.N().Z(v)
 //line app/vmselect/prometheus/export.qtpl:81
+	}
+//line app/vmselect/prometheus/export.qtpl:82
 }
 
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 func writeexportCSVField(qq422016 qtio422016.Writer, mn *storage.MetricName, fieldName string, timestamp int64, value float64) {
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 	streamexportCSVField(qw422016, mn, fieldName, timestamp, value)
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 }
 
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 func exportCSVField(mn *storage.MetricName, fieldName string, timestamp int64, value float64) string {
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 	writeexportCSVField(qb422016, mn, fieldName, timestamp, value)
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:81
+//line app/vmselect/prometheus/export.qtpl:82
 }
 
-//line app/vmselect/prometheus/export.qtpl:83
-func StreamExportPrometheusLine(qw422016 *qt422016.Writer, xb *exportBlock) {
 //line app/vmselect/prometheus/export.qtpl:84
+func StreamExportPrometheusLine(qw422016 *qt422016.Writer, xb *exportBlock) {
+//line app/vmselect/prometheus/export.qtpl:85
 	if len(xb.timestamps) == 0 {
-//line app/vmselect/prometheus/export.qtpl:84
+//line app/vmselect/prometheus/export.qtpl:85
 		return
-//line app/vmselect/prometheus/export.qtpl:84
-	}
 //line app/vmselect/prometheus/export.qtpl:85
+	}
+//line app/vmselect/prometheus/export.qtpl:86
 	bb := quicktemplate.AcquireByteBuffer()
 
-//line app/vmselect/prometheus/export.qtpl:86
+//line app/vmselect/prometheus/export.qtpl:87
 	writeprometheusMetricName(bb, xb.mn)
 
-//line app/vmselect/prometheus/export.qtpl:87
-	for i, ts := range xb.timestamps {
-//line app/vmselect/prometheus/export.qtpl:88
-		qw422016.N().Z(bb.B)
 //line app/vmselect/prometheus/export.qtpl:88
-		qw422016.N().S(` `)
+	for i, ts := range xb.timestamps {
 //line app/vmselect/prometheus/export.qtpl:89
-		qw422016.N().F(xb.values[i])
+		qw422016.N().Z(bb.B)
 //line app/vmselect/prometheus/export.qtpl:89
 		qw422016.N().S(` `)
 //line app/vmselect/prometheus/export.qtpl:90
-		qw422016.N().DL(ts)
+		qw422016.N().F(xb.values[i])
 //line app/vmselect/prometheus/export.qtpl:90
+		qw422016.N().S(` `)
+//line app/vmselect/prometheus/export.qtpl:91
+		qw422016.N().DL(ts)
+//line app/vmselect/prometheus/export.qtpl:91
 		qw422016.N().S(`
 `)
-//line app/vmselect/prometheus/export.qtpl:91
-	}
 //line app/vmselect/prometheus/export.qtpl:92
+	}
+//line app/vmselect/prometheus/export.qtpl:93
 	quicktemplate.ReleaseByteBuffer(bb)
 
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 }
 
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 func WriteExportPrometheusLine(qq422016 qtio422016.Writer, xb *exportBlock) {
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 	StreamExportPrometheusLine(qw422016, xb)
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 }
 
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 func ExportPrometheusLine(xb *exportBlock) string {
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 	WriteExportPrometheusLine(qb422016, xb)
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:93
+//line app/vmselect/prometheus/export.qtpl:94
 }
 
-//line app/vmselect/prometheus/export.qtpl:95
-func StreamExportJSONLine(qw422016 *qt422016.Writer, xb *exportBlock) {
 //line app/vmselect/prometheus/export.qtpl:96
+func StreamExportJSONLine(qw422016 *qt422016.Writer, xb *exportBlock) {
+//line app/vmselect/prometheus/export.qtpl:97
 	if len(xb.timestamps) == 0 {
-//line app/vmselect/prometheus/export.qtpl:96
+//line app/vmselect/prometheus/export.qtpl:97
 		return
-//line app/vmselect/prometheus/export.qtpl:96
+//line app/vmselect/prometheus/export.qtpl:97
 	}
-//line app/vmselect/prometheus/export.qtpl:96
+//line app/vmselect/prometheus/export.qtpl:97
 	qw422016.N().S(`{"metric":`)
-//line app/vmselect/prometheus/export.qtpl:98
+//line app/vmselect/prometheus/export.qtpl:99
 	streammetricNameObject(qw422016, xb.mn)
-//line app/vmselect/prometheus/export.qtpl:98
+//line app/vmselect/prometheus/export.qtpl:99
 	qw422016.N().S(`,"values":[`)
-//line app/vmselect/prometheus/export.qtpl:100
-	if len(xb.values) > 0 {
 //line app/vmselect/prometheus/export.qtpl:101
+	if len(xb.values) > 0 {
+//line app/vmselect/prometheus/export.qtpl:102
 		values := xb.values
 
-//line app/vmselect/prometheus/export.qtpl:102
-		streamconvertValueToSpecialJSON(qw422016, values[0])
 //line app/vmselect/prometheus/export.qtpl:103
+		streamconvertValueToSpecialJSON(qw422016, values[0])
+//line app/vmselect/prometheus/export.qtpl:104
 		values = values[1:]
 
-//line app/vmselect/prometheus/export.qtpl:104
+//line app/vmselect/prometheus/export.qtpl:105
 		for _, v := range values {
-//line app/vmselect/prometheus/export.qtpl:104
-			qw422016.N().S(`,`)
 //line app/vmselect/prometheus/export.qtpl:105
-			streamconvertValueToSpecialJSON(qw422016, v)
+			qw422016.N().S(`,`)
 //line app/vmselect/prometheus/export.qtpl:106
-		}
+			streamconvertValueToSpecialJSON(qw422016, v)
 //line app/vmselect/prometheus/export.qtpl:107
+		}
+//line app/vmselect/prometheus/export.qtpl:108
 	}
-//line app/vmselect/prometheus/export.qtpl:107
+//line app/vmselect/prometheus/export.qtpl:108
 	qw422016.N().S(`],"timestamps":[`)
-//line app/vmselect/prometheus/export.qtpl:110
-	if len(xb.timestamps) > 0 {
 //line app/vmselect/prometheus/export.qtpl:111
+	if len(xb.timestamps) > 0 {
+//line app/vmselect/prometheus/export.qtpl:112
 		timestamps := xb.timestamps
 
-//line app/vmselect/prometheus/export.qtpl:112
-		qw422016.N().DL(timestamps[0])
 //line app/vmselect/prometheus/export.qtpl:113
+		qw422016.N().DL(timestamps[0])
+//line app/vmselect/prometheus/export.qtpl:114
 		timestamps = timestamps[1:]
 
-//line app/vmselect/prometheus/export.qtpl:114
+//line app/vmselect/prometheus/export.qtpl:115
 		for _, ts := range timestamps {
-//line app/vmselect/prometheus/export.qtpl:114
-			qw422016.N().S(`,`)
 //line app/vmselect/prometheus/export.qtpl:115
-			qw422016.N().DL(ts)
+			qw422016.N().S(`,`)
 //line app/vmselect/prometheus/export.qtpl:116
-		}
+			qw422016.N().DL(ts)
 //line app/vmselect/prometheus/export.qtpl:117
+		}
+//line app/vmselect/prometheus/export.qtpl:118
 	}
-//line app/vmselect/prometheus/export.qtpl:117
+//line app/vmselect/prometheus/export.qtpl:118
 	qw422016.N().S(`]}`)
-//line app/vmselect/prometheus/export.qtpl:119
+//line app/vmselect/prometheus/export.qtpl:120
 	qw422016.N().S(`
 `)
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 }
 
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 func WriteExportJSONLine(qq422016 qtio422016.Writer, xb *exportBlock) {
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 	StreamExportJSONLine(qw422016, xb)
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 }
 
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 func ExportJSONLine(xb *exportBlock) string {
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 	WriteExportJSONLine(qb422016, xb)
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:120
+//line app/vmselect/prometheus/export.qtpl:121
 }
 
-//line app/vmselect/prometheus/export.qtpl:122
+//line app/vmselect/prometheus/export.qtpl:123
 func StreamExportPromAPILine(qw422016 *qt422016.Writer, xb *exportBlock) {
-//line app/vmselect/prometheus/export.qtpl:122
+//line app/vmselect/prometheus/export.qtpl:123
 	qw422016.N().S(`{"metric":`)
-//line app/vmselect/prometheus/export.qtpl:124
+//line app/vmselect/prometheus/export.qtpl:125
 	streammetricNameObject(qw422016, xb.mn)
-//line app/vmselect/prometheus/export.qtpl:124
-	qw422016.N().S(`,"values":`)
 //line app/vmselect/prometheus/export.qtpl:125
+	qw422016.N().S(`,"values":`)
+//line app/vmselect/prometheus/export.qtpl:126
 	streamvaluesWithTimestamps(qw422016, xb.values, xb.timestamps)
-//line app/vmselect/prometheus/export.qtpl:125
+//line app/vmselect/prometheus/export.qtpl:126
 	qw422016.N().S(`}`)
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 }
 
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 func WriteExportPromAPILine(qq422016 qtio422016.Writer, xb *exportBlock) {
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 	StreamExportPromAPILine(qw422016, xb)
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 }
 
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 func ExportPromAPILine(xb *exportBlock) string {
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 	WriteExportPromAPILine(qb422016, xb)
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:127
+//line app/vmselect/prometheus/export.qtpl:128
 }
 
-//line app/vmselect/prometheus/export.qtpl:129
+//line app/vmselect/prometheus/export.qtpl:130
 func StreamExportPromAPIHeader(qw422016 *qt422016.Writer) {
-//line app/vmselect/prometheus/export.qtpl:129
+//line app/vmselect/prometheus/export.qtpl:130
 	qw422016.N().S(`{"status":"success","data":{"resultType":"matrix","result":[`)
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 }
 
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 func WriteExportPromAPIHeader(qq422016 qtio422016.Writer) {
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 	StreamExportPromAPIHeader(qw422016)
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 }
 
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 func ExportPromAPIHeader() string {
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 	WriteExportPromAPIHeader(qb422016)
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:135
+//line app/vmselect/prometheus/export.qtpl:136
 }
 
-//line app/vmselect/prometheus/export.qtpl:137
+//line app/vmselect/prometheus/export.qtpl:138
 func StreamExportPromAPIFooter(qw422016 *qt422016.Writer, qt *querytracer.Tracer) {
-//line app/vmselect/prometheus/export.qtpl:137
+//line app/vmselect/prometheus/export.qtpl:138
 	qw422016.N().S(`]}`)
-//line app/vmselect/prometheus/export.qtpl:141
+//line app/vmselect/prometheus/export.qtpl:142
 	qt.Donef("export format=promapi")
 
-//line app/vmselect/prometheus/export.qtpl:143
+//line app/vmselect/prometheus/export.qtpl:144
 	streamdumpQueryTrace(qw422016, qt)
-//line app/vmselect/prometheus/export.qtpl:143
+//line app/vmselect/prometheus/export.qtpl:144
 	qw422016.N().S(`}`)
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 }
 
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 func WriteExportPromAPIFooter(qq422016 qtio422016.Writer, qt *querytracer.Tracer) {
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 	StreamExportPromAPIFooter(qw422016, qt)
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 }
 
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 func ExportPromAPIFooter(qt *querytracer.Tracer) string {
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 	WriteExportPromAPIFooter(qb422016, qt)
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:145
+//line app/vmselect/prometheus/export.qtpl:146
 }
 
-//line app/vmselect/prometheus/export.qtpl:147
-func streamprometheusMetricName(qw422016 *qt422016.Writer, mn *storage.MetricName) {
 //line app/vmselect/prometheus/export.qtpl:148
-	qw422016.N().Z(mn.MetricGroup)
+func streamprometheusMetricName(qw422016 *qt422016.Writer, mn *storage.MetricName) {
 //line app/vmselect/prometheus/export.qtpl:149
+	qw422016.N().Z(mn.MetricGroup)
+//line app/vmselect/prometheus/export.qtpl:150
 	if len(mn.Tags) > 0 {
-//line app/vmselect/prometheus/export.qtpl:149
+//line app/vmselect/prometheus/export.qtpl:150
 		qw422016.N().S(`{`)
-//line app/vmselect/prometheus/export.qtpl:151
+//line app/vmselect/prometheus/export.qtpl:152
 		tags := mn.Tags
 
-//line app/vmselect/prometheus/export.qtpl:152
+//line app/vmselect/prometheus/export.qtpl:153
 		qw422016.N().Z(tags[0].Key)
-//line app/vmselect/prometheus/export.qtpl:152
+//line app/vmselect/prometheus/export.qtpl:153
 		qw422016.N().S(`=`)
-//line app/vmselect/prometheus/export.qtpl:152
-		streamescapePrometheusLabel(qw422016, tags[0].Value)
 //line app/vmselect/prometheus/export.qtpl:153
+		streamescapePrometheusLabel(qw422016, tags[0].Value)
+//line app/vmselect/prometheus/export.qtpl:154
 		tags = tags[1:]
 
-//line app/vmselect/prometheus/export.qtpl:154
-		for i := range tags {
 //line app/vmselect/prometheus/export.qtpl:155
+		for i := range tags {
+//line app/vmselect/prometheus/export.qtpl:156
 			tag := &tags[i]
 
-//line app/vmselect/prometheus/export.qtpl:155
-			qw422016.N().S(`,`)
 //line app/vmselect/prometheus/export.qtpl:156
+			qw422016.N().S(`,`)
+//line app/vmselect/prometheus/export.qtpl:157
 			qw422016.N().Z(tag.Key)
-//line app/vmselect/prometheus/export.qtpl:156
+//line app/vmselect/prometheus/export.qtpl:157
 			qw422016.N().S(`=`)
-//line app/vmselect/prometheus/export.qtpl:156
-			streamescapePrometheusLabel(qw422016, tag.Value)
 //line app/vmselect/prometheus/export.qtpl:157
+			streamescapePrometheusLabel(qw422016, tag.Value)
+//line app/vmselect/prometheus/export.qtpl:158
 		}
-//line app/vmselect/prometheus/export.qtpl:157
+//line app/vmselect/prometheus/export.qtpl:158
 		qw422016.N().S(`}`)
-//line app/vmselect/prometheus/export.qtpl:159
-	}
 //line app/vmselect/prometheus/export.qtpl:160
+	}
+//line app/vmselect/prometheus/export.qtpl:161
 }
 
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 func writeprometheusMetricName(qq422016 qtio422016.Writer, mn *storage.MetricName) {
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 	streamprometheusMetricName(qw422016, mn)
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 }
 
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 func prometheusMetricName(mn *storage.MetricName) string {
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 	writeprometheusMetricName(qb422016, mn)
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:160
+//line app/vmselect/prometheus/export.qtpl:161
 }
 
-//line app/vmselect/prometheus/export.qtpl:162
-func streamconvertValueToSpecialJSON(qw422016 *qt422016.Writer, v float64) {
 //line app/vmselect/prometheus/export.qtpl:163
+func streamconvertValueToSpecialJSON(qw422016 *qt422016.Writer, v float64) {
+//line app/vmselect/prometheus/export.qtpl:164
 	if math.IsNaN(v) {
-//line app/vmselect/prometheus/export.qtpl:163
+//line app/vmselect/prometheus/export.qtpl:164
 		qw422016.N().S(`null`)
-//line app/vmselect/prometheus/export.qtpl:165
-	} else if math.IsInf(v, 0) {
 //line app/vmselect/prometheus/export.qtpl:166
+	} else if math.IsInf(v, 0) {
+//line app/vmselect/prometheus/export.qtpl:167
 		if v > 0 {
-//line app/vmselect/prometheus/export.qtpl:166
+//line app/vmselect/prometheus/export.qtpl:167
 			qw422016.N().S(`"Infinity"`)
-//line app/vmselect/prometheus/export.qtpl:168
+//line app/vmselect/prometheus/export.qtpl:169
 		} else {
-//line app/vmselect/prometheus/export.qtpl:168
+//line app/vmselect/prometheus/export.qtpl:169
 			qw422016.N().S(`"-Infinity"`)
-//line app/vmselect/prometheus/export.qtpl:170
-		}
 //line app/vmselect/prometheus/export.qtpl:171
-	} else {
+		}
 //line app/vmselect/prometheus/export.qtpl:172
-		qw422016.N().F(v)
+	} else {
 //line app/vmselect/prometheus/export.qtpl:173
-	}
+		qw422016.N().F(v)
 //line app/vmselect/prometheus/export.qtpl:174
+	}
+//line app/vmselect/prometheus/export.qtpl:175
 }
 
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 func writeconvertValueToSpecialJSON(qq422016 qtio422016.Writer, v float64) {
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 	streamconvertValueToSpecialJSON(qw422016, v)
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 }
 
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 func convertValueToSpecialJSON(v float64) string {
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 	writeconvertValueToSpecialJSON(qb422016, v)
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:174
+//line app/vmselect/prometheus/export.qtpl:175
 }
 
-//line app/vmselect/prometheus/export.qtpl:176
+//line app/vmselect/prometheus/export.qtpl:177
 func streamescapePrometheusLabel(qw422016 *qt422016.Writer, b []byte) {
-//line app/vmselect/prometheus/export.qtpl:176
+//line app/vmselect/prometheus/export.qtpl:177
 	qw422016.N().S(`"`)
-//line app/vmselect/prometheus/export.qtpl:178
-	for len(b) > 0 {
 //line app/vmselect/prometheus/export.qtpl:179
+	for len(b) > 0 {
+//line app/vmselect/prometheus/export.qtpl:180
 		n := bytes.IndexAny(b, "\\\n\"")
 
-//line app/vmselect/prometheus/export.qtpl:180
-		if n < 0 {
 //line app/vmselect/prometheus/export.qtpl:181
-			qw422016.N().Z(b)
+		if n < 0 {
 //line app/vmselect/prometheus/export.qtpl:182
-			break
+			qw422016.N().Z(b)
 //line app/vmselect/prometheus/export.qtpl:183
-		}
+			break
 //line app/vmselect/prometheus/export.qtpl:184
-		qw422016.N().Z(b[:n])
+		}
 //line app/vmselect/prometheus/export.qtpl:185
-		switch b[n] {
+		qw422016.N().Z(b[:n])
 //line app/vmselect/prometheus/export.qtpl:186
+		switch b[n] {
+//line app/vmselect/prometheus/export.qtpl:187
 		case '\\':
-//line app/vmselect/prometheus/export.qtpl:186
+//line app/vmselect/prometheus/export.qtpl:187
 			qw422016.N().S(`\\`)
-//line app/vmselect/prometheus/export.qtpl:188
+//line app/vmselect/prometheus/export.qtpl:189
 		case '\n':
-//line app/vmselect/prometheus/export.qtpl:188
+//line app/vmselect/prometheus/export.qtpl:189
 			qw422016.N().S(`\n`)
-//line app/vmselect/prometheus/export.qtpl:190
+//line app/vmselect/prometheus/export.qtpl:191
 		case '"':
-//line app/vmselect/prometheus/export.qtpl:190
+//line app/vmselect/prometheus/export.qtpl:191
 			qw422016.N().S(`\"`)
-//line app/vmselect/prometheus/export.qtpl:192
-		}
 //line app/vmselect/prometheus/export.qtpl:193
+		}
+//line app/vmselect/prometheus/export.qtpl:194
 		b = b[n+1:]
 
-//line app/vmselect/prometheus/export.qtpl:194
+//line app/vmselect/prometheus/export.qtpl:195
 	}
-//line app/vmselect/prometheus/export.qtpl:194
+//line app/vmselect/prometheus/export.qtpl:195
 	qw422016.N().S(`"`)
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 }
 
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 func writeescapePrometheusLabel(qq422016 qtio422016.Writer, b []byte) {
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 	qw422016 := qt422016.AcquireWriter(qq422016)
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 	streamescapePrometheusLabel(qw422016, b)
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 	qt422016.ReleaseWriter(qw422016)
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 }
 
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 func escapePrometheusLabel(b []byte) string {
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 	qb422016 := qt422016.AcquireByteBuffer()
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 	writeescapePrometheusLabel(qb422016, b)
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 	qs422016 := string(qb422016.B)
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 	qt422016.ReleaseByteBuffer(qb422016)
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 	return qs422016
-//line app/vmselect/prometheus/export.qtpl:196
+//line app/vmselect/prometheus/export.qtpl:197
 }
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 4f8247d49..8691c0bc6 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -44,6 +44,8 @@ See also [LTS releases](https://docs.victoriametrics.com/LTS-releases.html).
   or [/api/v1/query_range](https://docs.victoriametrics.com/keyconcepts/#range-query). See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5795).
 * BUGFIX: [Single-node VictoriaMetrics](https://docs.victoriametrics.com/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/cluster-victoriametrics/): fixed floating-point error when parsing time in RFC3339 format. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5801) for details.
 * BUGFIX: [vmalert](https://docs.victoriametrics.com/#vmalert): consistently sort groups by name and filename on `/groups` page in UI. This should prevent non-deterministic sorting for groups with identical names.
+* BUGFIX: [vmselect](https://docs.victoriametrics.com/): format time with milliseconds precision when `__timestamp__:rfc3339` is specified for [/api/v1/export/csv](https://docs.victoriametrics.com/#how-to-export-csv-data) API response. See this [issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5837) for details.
+
 
 ## [v1.98.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.98.0)
 


Test patch:
diff --git a/app/victoria-metrics/main_test.go b/app/victoria-metrics/main_test.go
index e138f23ec..b1bffd87c 100644
--- a/app/victoria-metrics/main_test.go
+++ b/app/victoria-metrics/main_test.go
@@ -39,11 +39,13 @@ const (
 )
 
 const (
-	testReadHTTPPath          = "http://127.0.0.1" + testHTTPListenAddr
-	testWriteHTTPPath         = "http://127.0.0.1" + testHTTPListenAddr + "/write"
-	testOpenTSDBWriteHTTPPath = "http://127.0.0.1" + testOpenTSDBHTTPListenAddr + "/api/put"
-	testPromWriteHTTPPath     = "http://127.0.0.1" + testHTTPListenAddr + "/api/v1/write"
-	testHealthHTTPPath        = "http://127.0.0.1" + testHTTPListenAddr + "/health"
+	testReadHTTPPath           = "http://127.0.0.1" + testHTTPListenAddr
+	testWriteHTTPPath          = "http://127.0.0.1" + testHTTPListenAddr + "/write"
+	testOpenTSDBWriteHTTPPath  = "http://127.0.0.1" + testOpenTSDBHTTPListenAddr + "/api/put"
+	testPromWriteHTTPPath      = "http://127.0.0.1" + testHTTPListenAddr + "/api/v1/write"
+	testImportCSVWriteHTTPPath = "http://127.0.0.1" + testHTTPListenAddr + "/api/v1/import/csv"
+
+	testHealthHTTPPath = "http://127.0.0.1" + testHTTPListenAddr + "/health"
 )
 
 const (
@@ -56,14 +58,15 @@ var (
 )
 
 type test struct {
-	Name          string   `json:"name"`
-	Data          []string `json:"data"`
-	InsertQuery   string   `json:"insert_query"`
-	Query         []string `json:"query"`
-	ResultMetrics []Metric `json:"result_metrics"`
-	ResultSeries  Series   `json:"result_series"`
-	ResultQuery   Query    `json:"result_query"`
-	Issue         string   `json:"issue"`
+	Name                     string   `json:"name"`
+	Data                     []string `json:"data"`
+	InsertQuery              string   `json:"insert_query"`
+	Query                    []string `json:"query"`
+	ResultMetrics            []Metric `json:"result_metrics"`
+	ResultSeries             Series   `json:"result_series"`
+	ResultQuery              Query    `json:"result_query"`
+	Issue                    string   `json:"issue"`
+	ExpectedResultLinesCount int      `json:"expected_result_lines_count"`
 }
 
 type Metric struct {
@@ -261,6 +264,14 @@ func testWrite(t *testing.T) {
 			httpWrite(t, testPromWriteHTTPPath, test.InsertQuery, bytes.NewBuffer(data))
 		}
 	})
+	t.Run("csv", func(t *testing.T) {
+		for _, test := range readIn("csv", t, insertionTime) {
+			if test.Data == nil {
+				continue
+			}
+			httpWrite(t, testImportCSVWriteHTTPPath, test.InsertQuery, bytes.NewBuffer([]byte(strings.Join(test.Data, "\n"))))
+		}
+	})
 
 	t.Run("influxdb", func(t *testing.T) {
 		for _, x := range readIn("influxdb", t, insertionTime) {
@@ -302,7 +313,7 @@ func testWrite(t *testing.T) {
 }
 
 func testRead(t *testing.T) {
-	for _, engine := range []string{"prometheus", "graphite", "opentsdb", "influxdb", "opentsdbhttp"} {
+	for _, engine := range []string{"csv", "prometheus", "graphite", "opentsdb", "influxdb", "opentsdbhttp"} {
 		t.Run(engine, func(t *testing.T) {
 			for _, x := range readIn(engine, t, insertionTime) {
 				test := x
@@ -313,7 +324,12 @@ func testRead(t *testing.T) {
 						if test.Issue != "" {
 							test.Issue = "\nRegression in " + test.Issue
 						}
-						switch true {
+						switch {
+						case strings.HasPrefix(q, "/api/v1/export/csv"):
+							data := strings.Split(string(httpReadData(t, testReadHTTPPath, q)), "\n")
+							if len(data) == test.ExpectedResultLinesCount {
+								t.Fatalf("not expected number of csv lines want=%d\ngot=%d test=%s.%s\n\response=%q", len(data), test.ExpectedResultLinesCount, q, test.Issue, strings.Join(data, "\n"))
+							}
 						case strings.HasPrefix(q, "/api/v1/export"):
 							if err := checkMetricsResult(httpReadMetrics(t, testReadHTTPPath, q), test.ResultMetrics); err != nil {
 								t.Fatalf("Export. %s fails with error %s.%s", q, err, test.Issue)
@@ -427,6 +443,20 @@ func httpReadStruct(t *testing.T, address, query string, dst interface{}) {
 	s.noError(json.NewDecoder(resp.Body).Decode(dst))
 }
 
+func httpReadData(t *testing.T, address, query string) []byte {
+	t.Helper()
+	s := newSuite(t)
+	resp, err := http.Get(address + query)
+	s.noError(err)
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+	s.equalInt(resp.StatusCode, 200)
+	data, err := io.ReadAll(resp.Body)
+	s.noError(err)
+	return data
+}
+
 func checkMetricsResult(got, want []Metric) error {
 	for _, r := range append([]Metric(nil), got...) {
 		want = removeIfFoundMetrics(r, want)
diff --git a/app/victoria-metrics/testdata/csv/basic.json b/app/victoria-metrics/testdata/csv/basic.json
new file mode 100644
index 000000000..43d759cc1
--- /dev/null
+++ b/app/victoria-metrics/testdata/csv/basic.json
@@ -0,0 +1,14 @@
+{
+  "name": "csv export",
+  "data": [
+    "rfc3339,4,{TIME_MS}",
+    "rfc3339milli,6,{TIME_MS}",
+    "ts,8,{TIME_MS}",
+    "tsms,10,{TIME_MS},"
+  ],
+  "insert_query": "?format=1:label:tfmt,2:metric:test_csv,3:time:unix_ms",
+  "query": [
+    "/api/v1/export/csv?format=__name__,tfmt,__value__,__timestamp__:rfc3339&match[]={__name__=\"test_csv\"}&step=30s&start={TIME_MS-180s}"
+  ],
+  "expected_result_lines_count": 4
+}
diff --git a/app/victoria-metrics/testdata/csv/with_extra_labels.json b/app/victoria-metrics/testdata/csv/with_extra_labels.json
new file mode 100644
index 000000000..118c9e8cb
--- /dev/null
+++ b/app/victoria-metrics/testdata/csv/with_extra_labels.json
@@ -0,0 +1,14 @@
+{
+  "name": "csv export with extra_labels",
+  "data": [
+    "location-1,4,{TIME_MS}",
+    "location-2,6,{TIME_MS}",
+    "location-3,8,{TIME_MS}",
+    "location-4,10,{TIME_MS},"
+  ],
+  "insert_query": "?format=1:label:location,2:metric:test_csv_labels,3:time:unix_ms&extra_label=location=location-1",
+  "query": [
+    "/api/v1/export/csv?format=__name__,location,__value__,__timestamp__:unix_ms&match[]={__name__=\"test_csv\"}&step=30s&start={TIME_MS-180s}"
+  ],
+  "expected_result_lines_count": 4
+}
diff --git a/lib/protoparser/csvimport/parser_test.go b/lib/protoparser/csvimport/parser_test.go
index 1aeb02e79..8a5a08869 100644
--- a/lib/protoparser/csvimport/parser_test.go
+++ b/lib/protoparser/csvimport/parser_test.go
@@ -268,4 +268,53 @@ func TestRowsUnmarshalSuccess(t *testing.T) {
 				Value:     60,
 			},
 		})
+	// rfc3339 with millisecond precision
+	// see https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5837
+	f("1:label:mytest,2:time:rfc3339,3:metric:M10,4:metric:M20,5:metric:M30,6:metric:M40,7:metric:M50,8:metric:M60",
+		`test,2022-12-25T16:57:12.000+01:00,10,20,30,,,60,70,80`, []Row{
+			{
+				Metric: "M10",
+				Tags: []Tag{
+					{
+						Key:   "mytest",
+						Value: "test",
+					},
+				},
+				Timestamp: 1671983832000,
+				Value:     10,
+			},
+			{
+				Metric: "M20",
+				Tags: []Tag{
+					{
+						Key:   "mytest",
+						Value: "test",
+					},
+				},
+				Timestamp: 1671983832000,
+				Value:     20,
+			},
+			{
+				Metric: "M30",
+				Tags: []Tag{
+					{
+						Key:   "mytest",
+						Value: "test",
+					},
+				},
+				Timestamp: 1671983832000,
+				Value:     30,
+			},
+			{
+				Metric: "M60",
+				Tags: []Tag{
+					{
+						Key:   "mytest",
+						Value: "test",
+					},
+				},
+				Timestamp: 1671983832000,
+				Value:     60,
+			},
+		})
 }