diff --git a/README.markdown b/README.markdown
index c9a1727f5..f3327f1ef 100644
--- a/README.markdown
+++ b/README.markdown
@@ -899,12 +899,14 @@ ExampleMessage.encode({ anything: true });
The representation of `google.protobuf.Timestamp` is configurable by the `useDate` flag.
The `useJsonTimestamp` flag controls precision when `useDate` is `false`.
-| Protobuf well-known type | Default/`useDate=true` | `useDate=false` | `useDate=string` |
-| --------------------------- | ---------------------- | ------------------------------------ | ---------------- |
-| `google.protobuf.Timestamp` | `Date` | `{ seconds: number, nanos: number }` | `string` |
+| Protobuf well-known type | Default/`useDate=true` | `useDate=false` | `useDate=string` | `useDate=string-nano` |
+| --------------------------- | ---------------------- | ------------------------------------ | ---------------- | --------------------- |
+| `google.protobuf.Timestamp` | `Date` | `{ seconds: number, nanos: number }` | `string` | `string` |
When using `useDate=false` and `useJsonTimestamp=raw` timestamp is represented as `{ seconds: number, nanos: number }`, but has nanosecond precision.
+When using `useDate=string-nano` timestamp is represented as an ISO string with nanosecond precision `1970-01-01T14:27:59.987654321Z` and relies on [nano-date](https://www.npmjs.com/package/nano-date) library for conversion. You'll need to install it in your project.
+
# Number Types
Numbers are by default assumed to be plain JavaScript `number`s.
diff --git a/integration/emit-default-values-json/test.ts b/integration/emit-default-values-json/test.ts
index a2aef5f94..9f1eddf25 100644
--- a/integration/emit-default-values-json/test.ts
+++ b/integration/emit-default-values-json/test.ts
@@ -726,7 +726,7 @@ export type Exact
= P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/file-suffix/parent.pb.ts b/integration/file-suffix/parent.pb.ts
index d7a0ebb8c..e7502489d 100644
--- a/integration/file-suffix/parent.pb.ts
+++ b/integration/file-suffix/parent.pb.ts
@@ -113,7 +113,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/grpc-js-use-date-false/grpc-js-use-date-false.ts b/integration/grpc-js-use-date-false/grpc-js-use-date-false.ts
index 577ebe509..304c723d3 100644
--- a/integration/grpc-js-use-date-false/grpc-js-use-date-false.ts
+++ b/integration/grpc-js-use-date-false/grpc-js-use-date-false.ts
@@ -152,7 +152,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/grpc-js-use-date-string-nano/google/protobuf/timestamp.ts b/integration/grpc-js-use-date-string-nano/google/protobuf/timestamp.ts
new file mode 100644
index 000000000..0cf501b86
--- /dev/null
+++ b/integration/grpc-js-use-date-string-nano/google/protobuf/timestamp.ts
@@ -0,0 +1,214 @@
+/* eslint-disable */
+import * as _m0 from "protobufjs/minimal";
+import Long = require("long");
+
+export const protobufPackage = "google.protobuf";
+
+/**
+ * A Timestamp represents a point in time independent of any time zone or local
+ * calendar, encoded as a count of seconds and fractions of seconds at
+ * nanosecond resolution. The count is relative to an epoch at UTC midnight on
+ * January 1, 1970, in the proleptic Gregorian calendar which extends the
+ * Gregorian calendar backwards to year one.
+ *
+ * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
+ * second table is needed for interpretation, using a [24-hour linear
+ * smear](https://developers.google.com/time/smear).
+ *
+ * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
+ * restricting to that range, we ensure that we can convert to and from [RFC
+ * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
+ *
+ * # Examples
+ *
+ * Example 1: Compute Timestamp from POSIX `time()`.
+ *
+ * Timestamp timestamp;
+ * timestamp.set_seconds(time(NULL));
+ * timestamp.set_nanos(0);
+ *
+ * Example 2: Compute Timestamp from POSIX `gettimeofday()`.
+ *
+ * struct timeval tv;
+ * gettimeofday(&tv, NULL);
+ *
+ * Timestamp timestamp;
+ * timestamp.set_seconds(tv.tv_sec);
+ * timestamp.set_nanos(tv.tv_usec * 1000);
+ *
+ * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
+ *
+ * FILETIME ft;
+ * GetSystemTimeAsFileTime(&ft);
+ * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+ *
+ * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
+ * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
+ * Timestamp timestamp;
+ * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
+ * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
+ *
+ * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
+ *
+ * long millis = System.currentTimeMillis();
+ *
+ * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
+ * .setNanos((int) ((millis % 1000) * 1000000)).build();
+ *
+ * Example 5: Compute Timestamp from Java `Instant.now()`.
+ *
+ * Instant now = Instant.now();
+ *
+ * Timestamp timestamp =
+ * Timestamp.newBuilder().setSeconds(now.getEpochSecond())
+ * .setNanos(now.getNano()).build();
+ *
+ * Example 6: Compute Timestamp from current time in Python.
+ *
+ * timestamp = Timestamp()
+ * timestamp.GetCurrentTime()
+ *
+ * # JSON Mapping
+ *
+ * In JSON format, the Timestamp type is encoded as a string in the
+ * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+ * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+ * where {year} is always expressed using four digits while {month}, {day},
+ * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+ * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+ * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+ * is required. A proto3 JSON serializer should always use UTC (as indicated by
+ * "Z") when printing the Timestamp type and a proto3 JSON parser should be
+ * able to accept both UTC and other timezones (as indicated by an offset).
+ *
+ * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+ * 01:30 UTC on January 15, 2017.
+ *
+ * In JavaScript, one can convert a Date object to this format using the
+ * standard
+ * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
+ * method. In Python, a standard `datetime.datetime` object can be converted
+ * to this format using
+ * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
+ * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
+ * the Joda Time's [`ISODateTimeFormat.dateTime()`](
+ * http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
+ * ) to obtain a formatter capable of generating timestamps in this format.
+ */
+export interface Timestamp {
+ /**
+ * Represents seconds of UTC time since Unix epoch
+ * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
+ * 9999-12-31T23:59:59Z inclusive.
+ */
+ seconds: number;
+ /**
+ * Non-negative fractions of a second at nanosecond resolution. Negative
+ * second values with fractions must still have non-negative nanos values
+ * that count forward in time. Must be from 0 to 999,999,999
+ * inclusive.
+ */
+ nanos: number;
+}
+
+function createBaseTimestamp(): Timestamp {
+ return { seconds: 0, nanos: 0 };
+}
+
+export const Timestamp = {
+ encode(message: Timestamp, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ if (message.seconds !== 0) {
+ writer.uint32(8).int64(message.seconds);
+ }
+ if (message.nanos !== 0) {
+ writer.uint32(16).int32(message.nanos);
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): Timestamp {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseTimestamp();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 8) {
+ break;
+ }
+
+ message.seconds = longToNumber(reader.int64() as Long);
+ continue;
+ case 2:
+ if (tag !== 16) {
+ break;
+ }
+
+ message.nanos = reader.int32();
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): Timestamp {
+ return {
+ seconds: isSet(object.seconds) ? globalThis.Number(object.seconds) : 0,
+ nanos: isSet(object.nanos) ? globalThis.Number(object.nanos) : 0,
+ };
+ },
+
+ toJSON(message: Timestamp): unknown {
+ const obj: any = {};
+ if (message.seconds !== 0) {
+ obj.seconds = Math.round(message.seconds);
+ }
+ if (message.nanos !== 0) {
+ obj.nanos = Math.round(message.nanos);
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): Timestamp {
+ return Timestamp.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): Timestamp {
+ const message = createBaseTimestamp();
+ message.seconds = object.seconds ?? 0;
+ message.nanos = object.nanos ?? 0;
+ return message;
+ },
+};
+
+type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
+
+export type DeepPartial = T extends Builtin ? T
+ : T extends globalThis.Array ? globalThis.Array>
+ : T extends ReadonlyArray ? ReadonlyArray>
+ : T extends {} ? { [K in keyof T]?: DeepPartial }
+ : Partial;
+
+type KeysOfUnion = T extends T ? keyof T : never;
+export type Exact = P extends Builtin ? P
+ : P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
+
+function longToNumber(long: Long): number {
+ if (long.gt(globalThis.Number.MAX_SAFE_INTEGER)) {
+ throw new globalThis.Error("Value is larger than Number.MAX_SAFE_INTEGER");
+ }
+ return long.toNumber();
+}
+
+if (_m0.util.Long !== Long) {
+ _m0.util.Long = Long as any;
+ _m0.configure();
+}
+
+function isSet(value: any): boolean {
+ return value !== null && value !== undefined;
+}
diff --git a/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano-test.ts b/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano-test.ts
new file mode 100644
index 000000000..192cdbf20
--- /dev/null
+++ b/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano-test.ts
@@ -0,0 +1,25 @@
+/**
+ * @jest-environment node
+ */
+import { TestService, TimestampMessage } from "./grpc-js-use-date-string-nano";
+
+const jan1 = "1970-01-01T14:27:59.987654321Z";
+
+describe("grpc-js-use-date-nano", () => {
+ it("compiles", () => {
+ expect(TestService).not.toBeUndefined();
+ });
+
+ it("returns simple date string", async () => {
+ const encoded = TestService.simpleNow.requestSerialize(jan1);
+ const decoded = TestService.simpleNow.responseDeserialize(encoded);
+ expect(decoded).toStrictEqual(jan1);
+ });
+
+ it("returns wrapped date string", async () => {
+ const data: TimestampMessage = { timestamp: jan1 };
+ const encoded = TestService.wrappedNow.requestSerialize(data);
+ const decoded = TestService.wrappedNow.responseDeserialize(encoded);
+ expect(decoded).toStrictEqual(data);
+ });
+});
diff --git a/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.bin b/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.bin
new file mode 100644
index 000000000..0c2facb63
Binary files /dev/null and b/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.bin differ
diff --git a/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.proto b/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.proto
new file mode 100644
index 000000000..d6fab88e6
--- /dev/null
+++ b/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.proto
@@ -0,0 +1,12 @@
+syntax = "proto3";
+
+import "google/protobuf/timestamp.proto";
+
+package simple;
+
+service Test {
+ rpc SimpleNow(google.protobuf.Timestamp) returns (google.protobuf.Timestamp);
+ rpc WrappedNow(TimestampMessage) returns (TimestampMessage);
+}
+
+message TimestampMessage { google.protobuf.Timestamp timestamp = 1; }
diff --git a/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.ts b/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.ts
new file mode 100644
index 000000000..f89033f9c
--- /dev/null
+++ b/integration/grpc-js-use-date-string-nano/grpc-js-use-date-string-nano.ts
@@ -0,0 +1,182 @@
+/* eslint-disable */
+import { ChannelCredentials, Client, makeGenericClientConstructor, Metadata } from "@grpc/grpc-js";
+import type {
+ CallOptions,
+ ClientOptions,
+ ClientUnaryCall,
+ handleUnaryCall,
+ ServiceError,
+ UntypedServiceImplementation,
+} from "@grpc/grpc-js";
+import NanoDate from "nano-date";
+import * as _m0 from "protobufjs/minimal";
+import { Timestamp } from "./google/protobuf/timestamp";
+
+export const protobufPackage = "simple";
+
+export interface TimestampMessage {
+ timestamp: string | undefined;
+}
+
+function createBaseTimestampMessage(): TimestampMessage {
+ return { timestamp: undefined };
+}
+
+export const TimestampMessage = {
+ encode(message: TimestampMessage, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer {
+ if (message.timestamp !== undefined) {
+ Timestamp.encode(toTimestamp(message.timestamp), writer.uint32(10).fork()).ldelim();
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): TimestampMessage {
+ const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseTimestampMessage();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ if (tag !== 10) {
+ break;
+ }
+
+ message.timestamp = fromTimestamp(Timestamp.decode(reader, reader.uint32()));
+ continue;
+ }
+ if ((tag & 7) === 4 || tag === 0) {
+ break;
+ }
+ reader.skipType(tag & 7);
+ }
+ return message;
+ },
+
+ fromJSON(object: any): TimestampMessage {
+ return { timestamp: isSet(object.timestamp) ? globalThis.String(object.timestamp) : undefined };
+ },
+
+ toJSON(message: TimestampMessage): unknown {
+ const obj: any = {};
+ if (message.timestamp !== undefined) {
+ obj.timestamp = message.timestamp;
+ }
+ return obj;
+ },
+
+ create, I>>(base?: I): TimestampMessage {
+ return TimestampMessage.fromPartial(base ?? ({} as any));
+ },
+ fromPartial, I>>(object: I): TimestampMessage {
+ const message = createBaseTimestampMessage();
+ message.timestamp = object.timestamp ?? undefined;
+ return message;
+ },
+};
+
+export type TestService = typeof TestService;
+export const TestService = {
+ simpleNow: {
+ path: "/simple.Test/SimpleNow",
+ requestStream: false,
+ responseStream: false,
+ requestSerialize: (value: string) => Buffer.from(Timestamp.encode(toTimestamp(value)).finish()),
+ requestDeserialize: (value: Buffer) => fromTimestamp(Timestamp.decode(value)),
+ responseSerialize: (value: string) => Buffer.from(Timestamp.encode(toTimestamp(value)).finish()),
+ responseDeserialize: (value: Buffer) => fromTimestamp(Timestamp.decode(value)),
+ },
+ wrappedNow: {
+ path: "/simple.Test/WrappedNow",
+ requestStream: false,
+ responseStream: false,
+ requestSerialize: (value: TimestampMessage) => Buffer.from(TimestampMessage.encode(value).finish()),
+ requestDeserialize: (value: Buffer) => TimestampMessage.decode(value),
+ responseSerialize: (value: TimestampMessage) => Buffer.from(TimestampMessage.encode(value).finish()),
+ responseDeserialize: (value: Buffer) => TimestampMessage.decode(value),
+ },
+} as const;
+
+export interface TestServer extends UntypedServiceImplementation {
+ simpleNow: handleUnaryCall;
+ wrappedNow: handleUnaryCall;
+}
+
+export interface TestClient extends Client {
+ simpleNow(request: string, callback: (error: ServiceError | null, response: string) => void): ClientUnaryCall;
+ simpleNow(
+ request: string,
+ metadata: Metadata,
+ callback: (error: ServiceError | null, response: string) => void,
+ ): ClientUnaryCall;
+ simpleNow(
+ request: string,
+ metadata: Metadata,
+ options: Partial,
+ callback: (error: ServiceError | null, response: string) => void,
+ ): ClientUnaryCall;
+ wrappedNow(
+ request: TimestampMessage,
+ callback: (error: ServiceError | null, response: TimestampMessage) => void,
+ ): ClientUnaryCall;
+ wrappedNow(
+ request: TimestampMessage,
+ metadata: Metadata,
+ callback: (error: ServiceError | null, response: TimestampMessage) => void,
+ ): ClientUnaryCall;
+ wrappedNow(
+ request: TimestampMessage,
+ metadata: Metadata,
+ options: Partial,
+ callback: (error: ServiceError | null, response: TimestampMessage) => void,
+ ): ClientUnaryCall;
+}
+
+export const TestClient = makeGenericClientConstructor(TestService, "simple.Test") as unknown as {
+ new (address: string, credentials: ChannelCredentials, options?: Partial): TestClient;
+ service: typeof TestService;
+ serviceName: string;
+};
+
+type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
+
+export type DeepPartial = T extends Builtin ? T
+ : T extends globalThis.Array ? globalThis.Array>
+ : T extends ReadonlyArray ? ReadonlyArray>
+ : T extends {} ? { [K in keyof T]?: DeepPartial }
+ : Partial;
+
+type KeysOfUnion = T extends T ? keyof T : never;
+export type Exact = P extends Builtin ? P
+ : P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
+
+function toTimestamp(dateStr: string): Timestamp {
+ const nanoDate = new NanoDate(dateStr);
+
+ const date = { getTime: (): number => nanoDate.valueOf() } as const;
+ const seconds = Math.trunc(date.getTime() / 1_000);
+
+ let nanos = nanoDate.getMilliseconds() * 1_000_000;
+ nanos += nanoDate.getMicroseconds() * 1_000;
+ nanos += nanoDate.getNanoseconds();
+
+ return { seconds, nanos };
+}
+
+function fromTimestamp(t: Timestamp): string {
+ const seconds = t.seconds || 0;
+ const nanos = (t.nanos || 0) % 1_000;
+ const micros = Math.trunc(((t.nanos || 0) % 1_000_000) / 1_000);
+ let millis = seconds * 1_000;
+ millis += Math.trunc((t.nanos || 0) / 1_000_000);
+
+ const nanoDate = new NanoDate(millis);
+ nanoDate.setMicroseconds(micros);
+ nanoDate.setNanoseconds(nanos);
+
+ return nanoDate.toISOStringFull();
+}
+
+function isSet(value: any): boolean {
+ return value !== null && value !== undefined;
+}
diff --git a/integration/grpc-js-use-date-string-nano/parameters.txt b/integration/grpc-js-use-date-string-nano/parameters.txt
new file mode 100644
index 000000000..693c75e45
--- /dev/null
+++ b/integration/grpc-js-use-date-string-nano/parameters.txt
@@ -0,0 +1 @@
+outputServices=grpc-js,useDate=string-nano
\ No newline at end of file
diff --git a/integration/grpc-js-use-date-string/grpc-js-use-date-string.ts b/integration/grpc-js-use-date-string/grpc-js-use-date-string.ts
index b0c924dc9..9b5a4faa0 100644
--- a/integration/grpc-js-use-date-string/grpc-js-use-date-string.ts
+++ b/integration/grpc-js-use-date-string/grpc-js-use-date-string.ts
@@ -151,7 +151,7 @@ export type Exact = P extends Builtin ? P
function toTimestamp(dateStr: string): Timestamp {
const date = new globalThis.Date(dateStr);
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/grpc-js-use-date-true/grpc-js-use-date-true.ts b/integration/grpc-js-use-date-true/grpc-js-use-date-true.ts
index c21eac1a8..40334bf08 100644
--- a/integration/grpc-js-use-date-true/grpc-js-use-date-true.ts
+++ b/integration/grpc-js-use-date-true/grpc-js-use-date-true.ts
@@ -150,7 +150,7 @@ export type Exact
= P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/grpc-js/simple.ts b/integration/grpc-js/simple.ts
index 353610cf6..4a136e9aa 100644
--- a/integration/grpc-js/simple.ts
+++ b/integration/grpc-js/simple.ts
@@ -647,7 +647,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/import-mapping/mapping.ts b/integration/import-mapping/mapping.ts
index 8800ed517..44ee57ef2 100644
--- a/integration/import-mapping/mapping.ts
+++ b/integration/import-mapping/mapping.ts
@@ -335,7 +335,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/import-suffix/parent.pb.ts b/integration/import-suffix/parent.pb.ts
index 48210d877..f48d139ea 100644
--- a/integration/import-suffix/parent.pb.ts
+++ b/integration/import-suffix/parent.pb.ts
@@ -113,7 +113,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/meta-typings/import_dir/thing.ts b/integration/meta-typings/import_dir/thing.ts
index 6e0f7a227..af0ebba06 100644
--- a/integration/meta-typings/import_dir/thing.ts
+++ b/integration/meta-typings/import_dir/thing.ts
@@ -109,7 +109,7 @@ export const protoMetadata: ProtoMetadata = {
};
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/meta-typings/simple.ts b/integration/meta-typings/simple.ts
index 8c39b5b49..937a59762 100644
--- a/integration/meta-typings/simple.ts
+++ b/integration/meta-typings/simple.ts
@@ -2951,7 +2951,7 @@ export const protoMetadata: ProtoMetadata = {
};
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/nice-grpc/simple.ts b/integration/nice-grpc/simple.ts
index c9824f0a8..cd2f18ad5 100644
--- a/integration/nice-grpc/simple.ts
+++ b/integration/nice-grpc/simple.ts
@@ -380,7 +380,7 @@ export type DeepPartial = T extends Builtin ? T
: Partial;
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/optional-long/test.ts b/integration/optional-long/test.ts
index 8dc400d03..ca44c6796 100644
--- a/integration/optional-long/test.ts
+++ b/integration/optional-long/test.ts
@@ -79,7 +79,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = numberToLong(date.getTime() / 1_000);
+ const seconds = numberToLong(Math.trunc(date.getTime() / 1_000));
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-json-name/simple.ts b/integration/simple-json-name/simple.ts
index f89562f37..7646692af 100644
--- a/integration/simple-json-name/simple.ts
+++ b/integration/simple-json-name/simple.ts
@@ -203,7 +203,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-optionals/import_dir/thing.ts b/integration/simple-optionals/import_dir/thing.ts
index f4e27d406..49ce0ed45 100644
--- a/integration/simple-optionals/import_dir/thing.ts
+++ b/integration/simple-optionals/import_dir/thing.ts
@@ -78,7 +78,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-optionals/simple.ts b/integration/simple-optionals/simple.ts
index 8e446f144..a360451d0 100644
--- a/integration/simple-optionals/simple.ts
+++ b/integration/simple-optionals/simple.ts
@@ -1955,7 +1955,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-optionals/thing.ts b/integration/simple-optionals/thing.ts
index b5da9edcc..469d11393 100644
--- a/integration/simple-optionals/thing.ts
+++ b/integration/simple-optionals/thing.ts
@@ -78,7 +78,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-prototype-defaults/import_dir/thing.ts b/integration/simple-prototype-defaults/import_dir/thing.ts
index 426053cff..29074100c 100644
--- a/integration/simple-prototype-defaults/import_dir/thing.ts
+++ b/integration/simple-prototype-defaults/import_dir/thing.ts
@@ -78,7 +78,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-prototype-defaults/simple.ts b/integration/simple-prototype-defaults/simple.ts
index 7d9bdb633..bd2662386 100644
--- a/integration/simple-prototype-defaults/simple.ts
+++ b/integration/simple-prototype-defaults/simple.ts
@@ -2950,7 +2950,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-snake/import_dir/thing.ts b/integration/simple-snake/import_dir/thing.ts
index 5f80dc135..190d6d8f3 100644
--- a/integration/simple-snake/import_dir/thing.ts
+++ b/integration/simple-snake/import_dir/thing.ts
@@ -78,7 +78,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-snake/simple.ts b/integration/simple-snake/simple.ts
index 2fc3b1294..642443709 100644
--- a/integration/simple-snake/simple.ts
+++ b/integration/simple-snake/simple.ts
@@ -2017,7 +2017,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-unrecognized-enum/import_dir/thing.ts b/integration/simple-unrecognized-enum/import_dir/thing.ts
index 0fe76ebd1..6f3f3a7ca 100644
--- a/integration/simple-unrecognized-enum/import_dir/thing.ts
+++ b/integration/simple-unrecognized-enum/import_dir/thing.ts
@@ -78,7 +78,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple-unrecognized-enum/simple.ts b/integration/simple-unrecognized-enum/simple.ts
index fe8a7b066..2025b503d 100644
--- a/integration/simple-unrecognized-enum/simple.ts
+++ b/integration/simple-unrecognized-enum/simple.ts
@@ -1943,7 +1943,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple/import_dir/thing.ts b/integration/simple/import_dir/thing.ts
index 0fe76ebd1..6f3f3a7ca 100644
--- a/integration/simple/import_dir/thing.ts
+++ b/integration/simple/import_dir/thing.ts
@@ -78,7 +78,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/simple/simple.ts b/integration/simple/simple.ts
index a611e5a7b..85c8a2adf 100644
--- a/integration/simple/simple.ts
+++ b/integration/simple/simple.ts
@@ -3072,7 +3072,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/static-only-type-registry/foo.ts b/integration/static-only-type-registry/foo.ts
index 6941ffc8b..88479ce04 100644
--- a/integration/static-only-type-registry/foo.ts
+++ b/integration/static-only-type-registry/foo.ts
@@ -214,7 +214,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/static-only/foo.ts b/integration/static-only/foo.ts
index fc9400375..49d3417d0 100644
--- a/integration/static-only/foo.ts
+++ b/integration/static-only/foo.ts
@@ -207,7 +207,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/type-annotations/foo.ts b/integration/type-annotations/foo.ts
index ef4e39ae6..5720cf796 100644
--- a/integration/type-annotations/foo.ts
+++ b/integration/type-annotations/foo.ts
@@ -210,7 +210,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude | "$type">]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { $type: "google.protobuf.Timestamp", seconds, nanos };
}
diff --git a/integration/type-registry/foo.ts b/integration/type-registry/foo.ts
index 619415d64..01d135889 100644
--- a/integration/type-registry/foo.ts
+++ b/integration/type-registry/foo.ts
@@ -217,7 +217,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude | "$type">]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { $type: "google.protobuf.Timestamp", seconds, nanos };
}
diff --git a/integration/use-date-false/metadata.ts b/integration/use-date-false/metadata.ts
index fd39acde6..ef69aaa8b 100644
--- a/integration/use-date-false/metadata.ts
+++ b/integration/use-date-false/metadata.ts
@@ -80,7 +80,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/use-date-false/use-date-test.ts b/integration/use-date-false/use-date-test.ts
index e81c22ceb..58f599aea 100644
--- a/integration/use-date-false/use-date-test.ts
+++ b/integration/use-date-false/use-date-test.ts
@@ -25,7 +25,7 @@ describe("useDate=false", () => {
expect(Metadata.fromJSON(json).lastEdited).toMatchInlineSnapshot(`
{
"nanos": 234000000,
- "seconds": 123456789.234,
+ "seconds": 123456789,
}
`);
});
diff --git a/integration/use-date-string/use-date-string.ts b/integration/use-date-string/use-date-string.ts
index d20076369..597094714 100644
--- a/integration/use-date-string/use-date-string.ts
+++ b/integration/use-date-string/use-date-string.ts
@@ -248,7 +248,7 @@ export type Exact = P extends Builtin ? P
function toTimestamp(dateStr: string): Timestamp {
const date = new globalThis.Date(dateStr);
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/use-date-true/use-date-true.ts b/integration/use-date-true/use-date-true.ts
index a5d2b4050..e47a9fd29 100644
--- a/integration/use-date-true/use-date-true.ts
+++ b/integration/use-date-true/use-date-true.ts
@@ -288,7 +288,7 @@ export type Exact
= P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/use-json-name/use-json-name.ts b/integration/use-json-name/use-json-name.ts
index d299271a3..bb82ad4da 100644
--- a/integration/use-json-name/use-json-name.ts
+++ b/integration/use-json-name/use-json-name.ts
@@ -351,7 +351,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/use-map-type/use-map-type.ts b/integration/use-map-type/use-map-type.ts
index d577b240f..3934c16bf 100644
--- a/integration/use-map-type/use-map-type.ts
+++ b/integration/use-map-type/use-map-type.ts
@@ -747,7 +747,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/integration/use-optionals-all/test.ts b/integration/use-optionals-all/test.ts
index 831a7a4ec..d1279d8e5 100644
--- a/integration/use-optionals-all/test.ts
+++ b/integration/use-optionals-all/test.ts
@@ -738,7 +738,7 @@ export type Exact = P extends Builtin ? P
: P & { [K in keyof P]: Exact
} & { [K in Exclude>]: never };
function toTimestamp(date: Date): Timestamp {
- const seconds = date.getTime() / 1_000;
+ const seconds = Math.trunc(date.getTime() / 1_000);
const nanos = (date.getTime() % 1_000) * 1_000_000;
return { seconds, nanos };
}
diff --git a/package.json b/package.json
index 08bfa7403..9b8edca61 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
"jest": "^29.6.1",
"jest-ts-webcompat-resolver": "^1.0.0",
"mongodb": "^5.7.0",
+ "nano-date": "^4.1.0",
"nice-grpc": "^2.1.4",
"object-hash": "^3.0.0",
"prettier": "^2.8.8",
diff --git a/src/encode.ts b/src/encode.ts
index 0399ff23b..41ff98143 100644
--- a/src/encode.ts
+++ b/src/encode.ts
@@ -14,7 +14,11 @@ export function generateEncoder(ctx: Context, typeName: string): Code {
const TimestampValue = impProto(ctx.options, "google/protobuf/timestamp", name);
let value = code`value`;
- if (ctx.options.useDate === DateOption.DATE || ctx.options.useDate === DateOption.STRING) {
+ if (
+ ctx.options.useDate === DateOption.DATE ||
+ ctx.options.useDate === DateOption.STRING ||
+ ctx.options.useDate === DateOption.STRING_NANO
+ ) {
value = code`${ctx.utils.toTimestamp}(${value})`;
}
return code`${TimestampValue}.encode(${value}).finish()`;
@@ -69,7 +73,11 @@ export function generateDecoder(ctx: Context, typeName: string): Code {
TypeValue = impProto(ctx.options, "google/protobuf/timestamp", name);
const decoder = code`${TypeValue}.decode(value)`;
- if (ctx.options.useDate === DateOption.DATE || ctx.options.useDate === DateOption.STRING) {
+ if (
+ ctx.options.useDate === DateOption.DATE ||
+ ctx.options.useDate === DateOption.STRING ||
+ ctx.options.useDate === DateOption.STRING_NANO
+ ) {
return code`${ctx.utils.fromTimestamp}(${decoder})`;
}
return decoder;
diff --git a/src/main.ts b/src/main.ts
index 1b5108a7f..f8c73b30a 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -680,24 +680,22 @@ function makeTimestampMethods(
bytes: ReturnType,
) {
const Timestamp = impProto(options, "google/protobuf/timestamp", "Timestamp");
+ const NanoDate = imp("NanoDate=nano-date");
- let seconds: string | Code = "date.getTime() / 1_000";
+ let seconds: string | Code = "Math.trunc(date.getTime() / 1_000)";
let toNumberCode: string | Code = "t.seconds";
const makeToNumberCode = (methodCall: string) =>
`t.seconds${options.useOptionals === "all" ? "?" : ""}.${methodCall}`;
if (options.forceLong === LongOption.LONG) {
toNumberCode = makeToNumberCode("toNumber()");
- seconds = code`${longs.numberToLong}(date.getTime() / 1_000)`;
+ seconds = code`${longs.numberToLong}(${seconds})`;
} else if (options.forceLong === LongOption.BIGINT) {
toNumberCode = code`${bytes.globalThis}.Number(${makeToNumberCode("toString()")})`;
- seconds = code`BigInt(Math.trunc(date.getTime() / 1_000))`;
+ seconds = code`BigInt(${seconds})`;
} else if (options.forceLong === LongOption.STRING) {
toNumberCode = code`${bytes.globalThis}.Number(t.seconds)`;
- // Must discard the fractional piece here
- // Otherwise the fraction ends up on the seconds when parsed as a Long
- // (note this only occurs when the string is > 8 characters)
- seconds = "Math.trunc(date.getTime() / 1_000).toString()";
+ seconds = code`${seconds}.toString()`;
}
const maybeTypeField = addTypeToMessages(options) ? `$type: 'google.protobuf.Timestamp',` : "";
@@ -710,6 +708,23 @@ function makeTimestampMethods(
const date = new ${bytes.globalThis}.Date(dateStr);
const seconds = ${seconds};
const nanos = (date.getTime() % 1_000) * 1_000_000;
+ return { ${maybeTypeField} seconds, nanos };
+ }
+ `
+ : options.useDate === DateOption.STRING_NANO
+ ? code`
+ function toTimestamp(dateStr: string): ${Timestamp} {
+ const nanoDate = new ${NanoDate}(dateStr);
+
+ const date = {
+ getTime: (): number => nanoDate.valueOf(),
+ } as const;
+ const seconds = ${seconds};
+
+ let nanos = nanoDate.getMilliseconds() * 1_000_000;
+ nanos += nanoDate.getMicroseconds() * 1_000;
+ nanos += nanoDate.getNanoseconds();
+
return { ${maybeTypeField} seconds, nanos };
}
`
@@ -732,6 +747,22 @@ function makeTimestampMethods(
return new ${bytes.globalThis}.Date(millis).toISOString();
}
`
+ : options.useDate === DateOption.STRING_NANO
+ ? code`
+ function fromTimestamp(t: ${Timestamp}): string {
+ const seconds = ${toNumberCode} || 0;
+ const nanos = (t.nanos || 0) % 1_000;
+ const micros = Math.trunc(((t.nanos || 0) % 1_000_000) / 1_000)
+ let millis = seconds * 1_000;
+ millis += Math.trunc((t.nanos || 0) / 1_000_000);
+
+ const nanoDate = new ${NanoDate}(millis);
+ nanoDate.setMicroseconds(micros);
+ nanoDate.setNanoseconds(nanos);
+
+ return nanoDate.toISOStringFull();
+ }
+ `
: code`
function fromTimestamp(t: ${Timestamp}): Date {
let millis = (${toNumberCode} || 0) * 1_000;
@@ -1043,7 +1074,12 @@ function getDecodeReadSnippet(ctx: Context, field: FieldDescriptorProto) {
};
const decoder = code`${type}.decode(reader, reader.uint32())`;
readSnippet = code`${unwrap(decoder)}`;
- } else if (isTimestamp(field) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)) {
+ } else if (
+ isTimestamp(field) &&
+ (options.useDate === DateOption.DATE ||
+ options.useDate === DateOption.STRING ||
+ options.useDate === DateOption.STRING_NANO)
+ ) {
const type = basicTypeName(ctx, field, { keepValueType: true });
readSnippet = code`${utils.fromTimestamp}(${type}.decode(reader, reader.uint32()))`;
} else if (isObjectId(field) && options.useMongoObjectId) {
@@ -1283,7 +1319,12 @@ function getEncodeWriteSnippet(ctx: Context, field: FieldDescriptorProto): (plac
const tag = ((field.number << 3) | 2) >>> 0;
const type = basicTypeName(ctx, field, { keepValueType: true });
return (place) => code`${type}.encode(${utils.toProtoObjectId}(${place}), writer.uint32(${tag}).fork()).ldelim()`;
- } else if (isTimestamp(field) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)) {
+ } else if (
+ isTimestamp(field) &&
+ (options.useDate === DateOption.DATE ||
+ options.useDate === DateOption.STRING ||
+ options.useDate === DateOption.STRING_NANO)
+ ) {
const tag = ((field.number << 3) | 2) >>> 0;
const type = basicTypeName(ctx, field, { keepValueType: true });
return (place) => code`${type}.encode(${utils.toTimestamp}(${place}), writer.uint32(${tag}).fork()).ldelim()`;
@@ -1635,7 +1676,12 @@ function generateExtension(ctx: Context, message: DescriptorProto | undefined, e
} else if (isObjectId(field) && options.useMongoObjectId) {
const type = basicTypeName(ctx, field, { keepValueType: true });
return (place) => code`${type}.encode(${utils.toProtoObjectId}(${place}), writer.fork()).ldelim()`;
- } else if (isTimestamp(field) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)) {
+ } else if (
+ isTimestamp(field) &&
+ (options.useDate === DateOption.DATE ||
+ options.useDate === DateOption.STRING ||
+ options.useDate === DateOption.STRING_NANO)
+ ) {
const type = basicTypeName(ctx, field, { keepValueType: true });
return (place) => code`${type}.encode(${utils.toTimestamp}(${place}), writer.fork()).ldelim()`;
} else if (isValueType(ctx, field)) {
@@ -1834,7 +1880,10 @@ function generateFromJson(ctx: Context, fullName: string, fullTypeName: string,
}
} else if (isObjectId(field) && options.useMongoObjectId) {
return code`${utils.fromJsonObjectId}(${from})`;
- } else if (isTimestamp(field) && options.useDate === DateOption.STRING) {
+ } else if (
+ isTimestamp(field) &&
+ (options.useDate === DateOption.STRING || options.useDate === DateOption.STRING_NANO)
+ ) {
return code`${utils.globalThis}.String(${from})`;
} else if (
isTimestamp(field) &&
@@ -1883,7 +1932,10 @@ function generateFromJson(ctx: Context, fullName: string, fullTypeName: string,
}
} else if (isObjectId(valueField) && options.useMongoObjectId) {
return code`${utils.fromJsonObjectId}(${from})`;
- } else if (isTimestamp(valueField) && options.useDate === DateOption.STRING) {
+ } else if (
+ isTimestamp(valueField) &&
+ (options.useDate === DateOption.STRING || options.useDate === DateOption.STRING_NANO)
+ ) {
return code`${utils.globalThis}.String(${from})`;
} else if (
isTimestamp(valueField) &&
@@ -2058,7 +2110,10 @@ function generateToJson(
return code`${from}.toString()`;
} else if (isTimestamp(field) && options.useDate === DateOption.DATE) {
return code`${from}.toISOString()`;
- } else if (isTimestamp(field) && options.useDate === DateOption.STRING) {
+ } else if (
+ isTimestamp(field) &&
+ (options.useDate === DateOption.STRING || options.useDate === DateOption.STRING_NANO)
+ ) {
return code`${from}`;
} else if (isTimestamp(field) && options.useDate === DateOption.TIMESTAMP) {
if (options.useJsonTimestamp === JsonTimestampOption.RAW) {
@@ -2077,7 +2132,10 @@ function generateToJson(
return code`${from}.toString()`;
} else if (isTimestamp(valueType) && options.useDate === DateOption.DATE) {
return code`${from}.toISOString()`;
- } else if (isTimestamp(valueType) && options.useDate === DateOption.STRING) {
+ } else if (
+ isTimestamp(valueType) &&
+ (options.useDate === DateOption.STRING || options.useDate === DateOption.STRING_NANO)
+ ) {
return code`${from}`;
} else if (isTimestamp(valueType) && options.useDate === DateOption.TIMESTAMP) {
return code`${utils.fromTimestamp}(${from}).toISOString()`;
@@ -2232,7 +2290,10 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri
return code`${from} as mongodb.ObjectId`;
} else if (
isPrimitive(field) ||
- (isTimestamp(field) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)) ||
+ (isTimestamp(field) &&
+ (options.useDate === DateOption.DATE ||
+ options.useDate === DateOption.STRING ||
+ options.useDate === DateOption.STRING_NANO)) ||
isValueType(ctx, field)
) {
return code`${from}`;
@@ -2258,7 +2319,9 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri
return code`${from} as mongodb.ObjectId`;
} else if (
isTimestamp(valueField) &&
- (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)
+ (options.useDate === DateOption.DATE ||
+ options.useDate === DateOption.STRING ||
+ options.useDate === DateOption.STRING_NANO)
) {
return code`${from}`;
} else if (isValueType(ctx, valueField)) {
diff --git a/src/options.ts b/src/options.ts
index 0e5661435..5f37bc7a8 100644
--- a/src/options.ts
+++ b/src/options.ts
@@ -12,6 +12,7 @@ export enum LongOption {
export enum DateOption {
DATE = "date",
STRING = "string",
+ STRING_NANO = "string-nano",
TIMESTAMP = "timestamp",
}
diff --git a/src/types.ts b/src/types.ts
index 5ab96c83c..576150cd1 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -579,7 +579,7 @@ export function messageToTypeName(
return code`Date`;
}
- if (options.useDate == DateOption.STRING) {
+ if (options.useDate == DateOption.STRING || options.useDate == DateOption.STRING_NANO) {
return code`string`;
}
}
diff --git a/yarn.lock b/yarn.lock
index c25b14787..c71b7ec58 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2334,6 +2334,13 @@ __metadata:
languageName: node
linkType: hard
+"bignumber.js@npm:^9.0.0":
+ version: 9.1.2
+ resolution: "bignumber.js@npm:9.1.2"
+ checksum: 582c03af77ec9cb0ebd682a373ee6c66475db94a4325f92299621d544aa4bd45cb45fd60001610e94aef8ae98a0905fa538241d9638d4422d57abbeeac6fadaf
+ languageName: node
+ linkType: hard
+
"bin-links@npm:^4.0.1":
version: 4.0.1
resolution: "bin-links@npm:4.0.1"
@@ -5428,6 +5435,13 @@ __metadata:
languageName: node
linkType: hard
+"map-or-similar@npm:^1.5.0":
+ version: 1.5.0
+ resolution: "map-or-similar@npm:1.5.0"
+ checksum: f65c0d420e272d0fce4e24db35f6a08109218480bca1d61eaa442cbe6cf46270b840218d3b5e94e4bfcc2595f1d0a1fa5885df750b52aac9ab1d437b29dcce38
+ languageName: node
+ linkType: hard
+
"markdown-it-anchor@npm:^8.4.1":
version: 8.6.7
resolution: "markdown-it-anchor@npm:8.6.7"
@@ -5494,6 +5508,15 @@ __metadata:
languageName: node
linkType: hard
+"memoizerific@npm:^1.11.2":
+ version: 1.11.3
+ resolution: "memoizerific@npm:1.11.3"
+ dependencies:
+ map-or-similar: ^1.5.0
+ checksum: d51bdc3ed8c39b4b73845c90eb62d243ddf21899914352d0c303f5e1d477abcb192f4c605e008caa4a31d823225eeb22a99ba5ee825fb88d0c33382db3aee95a
+ languageName: node
+ linkType: hard
+
"memory-pager@npm:^1.0.2":
version: 1.5.0
resolution: "memory-pager@npm:1.5.0"
@@ -5807,6 +5830,16 @@ __metadata:
languageName: node
linkType: hard
+"nano-date@npm:^4.1.0":
+ version: 4.1.0
+ resolution: "nano-date@npm:4.1.0"
+ dependencies:
+ bignumber.js: ^9.0.0
+ memoizerific: ^1.11.2
+ checksum: 7029b3c1db439e612ce4fdf426b33d4d930bfa682edf6c4c8d3d30b66a89d1f995215ebc06b04a53d2b8c4b5a95d0a25250fb7d66f067f9eca0b546fe918698b
+ languageName: node
+ linkType: hard
+
"natural-compare@npm:^1.4.0":
version: 1.4.0
resolution: "natural-compare@npm:1.4.0"
@@ -7852,6 +7885,7 @@ __metadata:
jest: ^29.6.1
jest-ts-webcompat-resolver: ^1.0.0
mongodb: ^5.7.0
+ nano-date: ^4.1.0
nice-grpc: ^2.1.4
object-hash: ^3.0.0
prettier: ^2.8.8