rfc3339ToTimestamp function
Parses an RFC 3339 string into a google.protobuf.Timestamp.
Accepts strings like "1972-01-01T10:00:20.021Z" or without fractional
seconds like "2000-01-01T00:00:00Z".
Returns {"seconds": int, "nanos": int}.
Implementation
Map<String, Object?> rfc3339ToTimestamp(String rfc3339) {
// Strict RFC 3339 (proto3 JSON): a 4-digit year, the uppercase 'T'
// separator, and either an uppercase 'Z' or a numeric offset with a colon
// (±HH:MM). Rejects lenient variants (missing T/Z, lowercase z, offset
// without a colon).
if (!RegExp(
r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$',
).hasMatch(rfc3339)) {
throw FormatException('Invalid RFC 3339 timestamp: "$rfc3339"');
}
// Extract fractional seconds from the string to preserve full nanosecond
// precision (DateTime only holds microseconds). protobuf nanos are ALWAYS in
// [0, 999999999] — even for negative timestamps — so the fraction is parsed
// separately and the whole-second part is floored, not truncated.
int nanos = 0;
String forParse = rfc3339;
final dotIndex = rfc3339.indexOf('.');
if (dotIndex != -1) {
// Find the end of the fractional part (before 'Z' or timezone offset).
int endIndex = rfc3339.length;
for (int i = dotIndex + 1; i < rfc3339.length; i++) {
if (rfc3339[i] == 'Z' ||
rfc3339[i] == 'z' ||
rfc3339[i] == '+' ||
rfc3339[i] == '-') {
endIndex = i;
break;
}
}
final fracStr = rfc3339.substring(dotIndex + 1, endIndex).padRight(9, '0');
nanos = int.parse(fracStr.substring(0, 9));
// Strip the fraction so the parsed instant lands on a whole second; this
// makes `~/ 1000` exact (a negative fractional time would otherwise
// truncate toward zero, e.g. -1.5s -> -1s instead of the correct -2s+0.5).
forParse = rfc3339.substring(0, dotIndex) + rfc3339.substring(endIndex);
}
final dt = DateTime.parse(forParse);
final seconds = dt.millisecondsSinceEpoch ~/ 1000;
return {'seconds': seconds, 'nanos': nanos};
}