Apply bounds to timestamps during backup import
This commit is contained in:
parent
8c57d243c0
commit
69ac276d0c
6 changed files with 172 additions and 45 deletions
|
@ -480,7 +480,7 @@ export class BackupExportStream extends Readable {
|
||||||
: null,
|
: null,
|
||||||
expireTimerVersion: attributes.expireTimerVersion,
|
expireTimerVersion: attributes.expireTimerVersion,
|
||||||
muteUntilMs: attributes.muteExpiresAt
|
muteUntilMs: attributes.muteExpiresAt
|
||||||
? getSafeLongFromTimestamp(attributes.muteExpiresAt)
|
? getSafeLongFromTimestamp(attributes.muteExpiresAt, Long.MAX_VALUE)
|
||||||
: null,
|
: null,
|
||||||
markedUnread: attributes.markedUnread === true,
|
markedUnread: attributes.markedUnread === true,
|
||||||
dontNotifyForMentionsIfMuted:
|
dontNotifyForMentionsIfMuted:
|
||||||
|
|
|
@ -56,9 +56,10 @@ import type {
|
||||||
} from '../../model-types.d';
|
} from '../../model-types.d';
|
||||||
import { assertDev, strictAssert } from '../../util/assert';
|
import { assertDev, strictAssert } from '../../util/assert';
|
||||||
import {
|
import {
|
||||||
getTimestampFromLong,
|
getCheckedTimestampFromLong,
|
||||||
getTimestampOrUndefinedFromLong,
|
getCheckedTimestampOrUndefinedFromLong,
|
||||||
} from '../../util/timestampLongUtils';
|
} from '../../util/timestampLongUtils';
|
||||||
|
import { MAX_SAFE_DATE } from '../../util/timestamp';
|
||||||
import { DurationInSeconds, SECOND } from '../../util/durations';
|
import { DurationInSeconds, SECOND } from '../../util/durations';
|
||||||
import { calculateExpirationTimestamp } from '../../util/expirationTimer';
|
import { calculateExpirationTimestamp } from '../../util/expirationTimer';
|
||||||
import { dropNull } from '../../util/dropNull';
|
import { dropNull } from '../../util/dropNull';
|
||||||
|
@ -913,7 +914,7 @@ export class BackupImportStream extends Writable {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contact.notRegistered) {
|
if (contact.notRegistered) {
|
||||||
const timestamp = getTimestampOrUndefinedFromLong(
|
const timestamp = getCheckedTimestampOrUndefinedFromLong(
|
||||||
contact.notRegistered.unregisteredTimestamp
|
contact.notRegistered.unregisteredTimestamp
|
||||||
);
|
);
|
||||||
attrs.discoveredUnregisteredAt = timestamp || this.now;
|
attrs.discoveredUnregisteredAt = timestamp || this.now;
|
||||||
|
@ -1053,7 +1054,8 @@ export class BackupImportStream extends Writable {
|
||||||
serviceId,
|
serviceId,
|
||||||
role: dropNull(role) ?? SignalService.Member.Role.UNKNOWN,
|
role: dropNull(role) ?? SignalService.Member.Role.UNKNOWN,
|
||||||
addedByUserId: fromAciObject(Aci.fromUuidBytes(addedByUserId)),
|
addedByUserId: fromAciObject(Aci.fromUuidBytes(addedByUserId)),
|
||||||
timestamp: timestamp != null ? getTimestampFromLong(timestamp) : 0,
|
timestamp:
|
||||||
|
timestamp != null ? getCheckedTimestampFromLong(timestamp) : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -1066,7 +1068,8 @@ export class BackupImportStream extends Writable {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
aci: fromAciObject(Aci.fromUuidBytes(userId)),
|
aci: fromAciObject(Aci.fromUuidBytes(userId)),
|
||||||
timestamp: timestamp != null ? getTimestampFromLong(timestamp) : 0,
|
timestamp:
|
||||||
|
timestamp != null ? getCheckedTimestampFromLong(timestamp) : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -1082,7 +1085,8 @@ export class BackupImportStream extends Writable {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
serviceId,
|
serviceId,
|
||||||
timestamp: timestamp != null ? getTimestampFromLong(timestamp) : 0,
|
timestamp:
|
||||||
|
timestamp != null ? getCheckedTimestampFromLong(timestamp) : 0,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
revision: dropNull(version),
|
revision: dropNull(version),
|
||||||
|
@ -1186,7 +1190,9 @@ export class BackupImportStream extends Writable {
|
||||||
isBlockList: false,
|
isBlockList: false,
|
||||||
members: [],
|
members: [],
|
||||||
|
|
||||||
deletedAtTimestamp: getTimestampFromLong(listItem.deletionTimestamp),
|
deletedAtTimestamp: getCheckedTimestampFromLong(
|
||||||
|
listItem.deletionTimestamp
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1217,7 +1223,7 @@ export class BackupImportStream extends Writable {
|
||||||
name,
|
name,
|
||||||
restrictions: fromCallLinkRestrictionsProto(restrictions),
|
restrictions: fromCallLinkRestrictionsProto(restrictions),
|
||||||
revoked: false,
|
revoked: false,
|
||||||
expiration: getTimestampFromLong(expirationMs) || null,
|
expiration: getCheckedTimestampOrUndefinedFromLong(expirationMs) ?? null,
|
||||||
storageNeedsSync: false,
|
storageNeedsSync: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1259,9 +1265,18 @@ export class BackupImportStream extends Writable {
|
||||||
? DurationInSeconds.fromMillis(chat.expirationTimerMs.toNumber())
|
? DurationInSeconds.fromMillis(chat.expirationTimerMs.toNumber())
|
||||||
: undefined;
|
: undefined;
|
||||||
conversation.expireTimerVersion = chat.expireTimerVersion || 1;
|
conversation.expireTimerVersion = chat.expireTimerVersion || 1;
|
||||||
conversation.muteExpiresAt = getTimestampOrUndefinedFromLong(
|
|
||||||
|
if (
|
||||||
|
chat.muteUntilMs != null &&
|
||||||
|
chat.muteUntilMs.toNumber() >= MAX_SAFE_DATE
|
||||||
|
) {
|
||||||
|
// Muted forever
|
||||||
|
conversation.muteExpiresAt = Number.MAX_SAFE_INTEGER;
|
||||||
|
} else {
|
||||||
|
conversation.muteExpiresAt = getCheckedTimestampOrUndefinedFromLong(
|
||||||
chat.muteUntilMs
|
chat.muteUntilMs
|
||||||
);
|
);
|
||||||
|
}
|
||||||
conversation.markedUnread = chat.markedUnread === true;
|
conversation.markedUnread = chat.markedUnread === true;
|
||||||
conversation.dontNotifyForMentionsIfMuted =
|
conversation.dontNotifyForMentionsIfMuted =
|
||||||
chat.dontNotifyForMentionsIfMuted === true;
|
chat.dontNotifyForMentionsIfMuted === true;
|
||||||
|
@ -1303,7 +1318,7 @@ export class BackupImportStream extends Writable {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { aboutMe } = options;
|
const { aboutMe } = options;
|
||||||
|
|
||||||
const timestamp = item?.dateSent?.toNumber();
|
const timestamp = getCheckedTimestampOrUndefinedFromLong(item?.dateSent);
|
||||||
const logId = `fromChatItem(${timestamp})`;
|
const logId = `fromChatItem(${timestamp})`;
|
||||||
|
|
||||||
strictAssert(this.ourConversation != null, `${logId}: AccountData missing`);
|
strictAssert(this.ourConversation != null, `${logId}: AccountData missing`);
|
||||||
|
@ -1344,7 +1359,7 @@ export class BackupImportStream extends Writable {
|
||||||
chatConvo.unreadCount = (chatConvo.unreadCount ?? 0) + 1;
|
chatConvo.unreadCount = (chatConvo.unreadCount ?? 0) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expirationStartTimestamp = getTimestampOrUndefinedFromLong(
|
const expirationStartTimestamp = getCheckedTimestampOrUndefinedFromLong(
|
||||||
item.expireStartDate
|
item.expireStartDate
|
||||||
);
|
);
|
||||||
const expireTimer =
|
const expireTimer =
|
||||||
|
@ -1566,7 +1581,7 @@ export class BackupImportStream extends Writable {
|
||||||
status: sendStatus,
|
status: sendStatus,
|
||||||
updatedAt:
|
updatedAt:
|
||||||
status.timestamp != null && !status.timestamp.isZero()
|
status.timestamp != null && !status.timestamp.isZero()
|
||||||
? getTimestampFromLong(status.timestamp)
|
? getCheckedTimestampFromLong(status.timestamp)
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1584,8 +1599,12 @@ export class BackupImportStream extends Writable {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (incoming) {
|
if (incoming) {
|
||||||
const receivedAtMs = incoming.dateReceived?.toNumber() || this.now;
|
const receivedAtMs =
|
||||||
const serverTimestamp = incoming.dateServerSent?.toNumber() || undefined;
|
getCheckedTimestampOrUndefinedFromLong(incoming.dateReceived) ??
|
||||||
|
this.now;
|
||||||
|
const serverTimestamp = getCheckedTimestampOrUndefinedFromLong(
|
||||||
|
incoming.dateServerSent
|
||||||
|
);
|
||||||
|
|
||||||
const unidentifiedDeliveryReceived = incoming.sealedSender === true;
|
const unidentifiedDeliveryReceived = incoming.sealedSender === true;
|
||||||
|
|
||||||
|
@ -1704,7 +1723,7 @@ export class BackupImportStream extends Writable {
|
||||||
url,
|
url,
|
||||||
title: dropNull(preview.title),
|
title: dropNull(preview.title),
|
||||||
description: dropNull(preview.description),
|
description: dropNull(preview.description),
|
||||||
date: getTimestampFromLong(preview.date),
|
date: getCheckedTimestampFromLong(preview.date),
|
||||||
image: preview.image
|
image: preview.image
|
||||||
? convertFilePointerToAttachment(preview.image)
|
? convertFilePointerToAttachment(preview.image)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
@ -1749,7 +1768,7 @@ export class BackupImportStream extends Writable {
|
||||||
'Edit history has non-standard messages'
|
'Edit history has non-standard messages'
|
||||||
);
|
);
|
||||||
|
|
||||||
const timestamp = getTimestampFromLong(rev.dateSent);
|
const timestamp = getCheckedTimestampFromLong(rev.dateSent);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
patch: {
|
patch: {
|
||||||
|
@ -1808,7 +1827,9 @@ export class BackupImportStream extends Writable {
|
||||||
strictAssert(authorConvo !== undefined, 'author conversation not found');
|
strictAssert(authorConvo !== undefined, 'author conversation not found');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: getTimestampFromLong(quote.targetSentTimestamp) || null,
|
id:
|
||||||
|
getCheckedTimestampOrUndefinedFromLong(quote.targetSentTimestamp) ??
|
||||||
|
null,
|
||||||
referencedMessageNotFound: quote.targetSentTimestamp == null,
|
referencedMessageNotFound: quote.targetSentTimestamp == null,
|
||||||
authorAci: isAciString(authorConvo.serviceId)
|
authorAci: isAciString(authorConvo.serviceId)
|
||||||
? authorConvo.serviceId
|
? authorConvo.serviceId
|
||||||
|
@ -1888,8 +1909,8 @@ export class BackupImportStream extends Writable {
|
||||||
return {
|
return {
|
||||||
emoji,
|
emoji,
|
||||||
fromId: authorConvo.id,
|
fromId: authorConvo.id,
|
||||||
targetTimestamp: getTimestampFromLong(sentTimestamp),
|
targetTimestamp: getCheckedTimestampFromLong(sentTimestamp),
|
||||||
timestamp: getTimestampFromLong(sentTimestamp),
|
timestamp: getCheckedTimestampFromLong(sentTimestamp),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2300,8 +2321,9 @@ export class BackupImportStream extends Writable {
|
||||||
: null,
|
: null,
|
||||||
peerId: groupId,
|
peerId: groupId,
|
||||||
direction: isRingerMe ? CallDirection.Outgoing : CallDirection.Incoming,
|
direction: isRingerMe ? CallDirection.Outgoing : CallDirection.Incoming,
|
||||||
timestamp: startedCallTimestamp.toNumber(),
|
timestamp: getCheckedTimestampFromLong(startedCallTimestamp),
|
||||||
endedTimestamp: getTimestampFromLong(endedCallTimestamp) || null,
|
endedTimestamp:
|
||||||
|
getCheckedTimestampOrUndefinedFromLong(endedCallTimestamp) ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.saveCallHistory(callHistory);
|
await this.saveCallHistory(callHistory);
|
||||||
|
@ -2359,7 +2381,7 @@ export class BackupImportStream extends Writable {
|
||||||
startedById: null,
|
startedById: null,
|
||||||
peerId,
|
peerId,
|
||||||
direction,
|
direction,
|
||||||
timestamp: startedCallTimestamp.toNumber(),
|
timestamp: getCheckedTimestampFromLong(startedCallTimestamp),
|
||||||
endedTimestamp: null,
|
endedTimestamp: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3128,7 +3150,7 @@ export class BackupImportStream extends Writable {
|
||||||
mode: CallMode.Adhoc,
|
mode: CallMode.Adhoc,
|
||||||
type: CallType.Adhoc,
|
type: CallType.Adhoc,
|
||||||
direction: CallDirection.Unknown,
|
direction: CallDirection.Unknown,
|
||||||
timestamp: callTimestamp.toNumber(),
|
timestamp: getCheckedTimestampFromLong(callTimestamp),
|
||||||
status: fromAdHocCallStateProto(state),
|
status: fromAdHocCallStateProto(state),
|
||||||
endedTimestamp: null,
|
endedTimestamp: null,
|
||||||
};
|
};
|
||||||
|
|
|
@ -251,7 +251,8 @@ export async function toContactRecord(
|
||||||
contactRecord.archived = Boolean(conversation.get('isArchived'));
|
contactRecord.archived = Boolean(conversation.get('isArchived'));
|
||||||
contactRecord.markedUnread = Boolean(conversation.get('markedUnread'));
|
contactRecord.markedUnread = Boolean(conversation.get('markedUnread'));
|
||||||
contactRecord.mutedUntilTimestamp = getSafeLongFromTimestamp(
|
contactRecord.mutedUntilTimestamp = getSafeLongFromTimestamp(
|
||||||
conversation.get('muteExpiresAt')
|
conversation.get('muteExpiresAt'),
|
||||||
|
Long.MAX_VALUE
|
||||||
);
|
);
|
||||||
if (conversation.get('hideStory') !== undefined) {
|
if (conversation.get('hideStory') !== undefined) {
|
||||||
contactRecord.hideStory = Boolean(conversation.get('hideStory'));
|
contactRecord.hideStory = Boolean(conversation.get('hideStory'));
|
||||||
|
@ -505,7 +506,8 @@ export function toGroupV1Record(
|
||||||
groupV1Record.archived = Boolean(conversation.get('isArchived'));
|
groupV1Record.archived = Boolean(conversation.get('isArchived'));
|
||||||
groupV1Record.markedUnread = Boolean(conversation.get('markedUnread'));
|
groupV1Record.markedUnread = Boolean(conversation.get('markedUnread'));
|
||||||
groupV1Record.mutedUntilTimestamp = getSafeLongFromTimestamp(
|
groupV1Record.mutedUntilTimestamp = getSafeLongFromTimestamp(
|
||||||
conversation.get('muteExpiresAt')
|
conversation.get('muteExpiresAt'),
|
||||||
|
Long.MAX_VALUE
|
||||||
);
|
);
|
||||||
|
|
||||||
applyUnknownFields(groupV1Record, conversation);
|
applyUnknownFields(groupV1Record, conversation);
|
||||||
|
@ -527,7 +529,8 @@ export function toGroupV2Record(
|
||||||
groupV2Record.archived = Boolean(conversation.get('isArchived'));
|
groupV2Record.archived = Boolean(conversation.get('isArchived'));
|
||||||
groupV2Record.markedUnread = Boolean(conversation.get('markedUnread'));
|
groupV2Record.markedUnread = Boolean(conversation.get('markedUnread'));
|
||||||
groupV2Record.mutedUntilTimestamp = getSafeLongFromTimestamp(
|
groupV2Record.mutedUntilTimestamp = getSafeLongFromTimestamp(
|
||||||
conversation.get('muteExpiresAt')
|
conversation.get('muteExpiresAt'),
|
||||||
|
Long.MAX_VALUE
|
||||||
);
|
);
|
||||||
groupV2Record.dontNotifyForMentionsIfMuted = Boolean(
|
groupV2Record.dontNotifyForMentionsIfMuted = Boolean(
|
||||||
conversation.get('dontNotifyForMentionsIfMuted')
|
conversation.get('dontNotifyForMentionsIfMuted')
|
||||||
|
|
|
@ -8,7 +8,10 @@ import {
|
||||||
getSafeLongFromTimestamp,
|
getSafeLongFromTimestamp,
|
||||||
getTimestampFromLong,
|
getTimestampFromLong,
|
||||||
getTimestampOrUndefinedFromLong,
|
getTimestampOrUndefinedFromLong,
|
||||||
|
getCheckedTimestampFromLong,
|
||||||
|
getCheckedTimestampOrUndefinedFromLong,
|
||||||
} from '../../util/timestampLongUtils';
|
} from '../../util/timestampLongUtils';
|
||||||
|
import { MAX_SAFE_DATE } from '../../util/timestamp';
|
||||||
|
|
||||||
describe('getSafeLongFromTimestamp', () => {
|
describe('getSafeLongFromTimestamp', () => {
|
||||||
it('returns zero when passed undefined', () => {
|
it('returns zero when passed undefined', () => {
|
||||||
|
@ -21,22 +24,38 @@ describe('getSafeLongFromTimestamp', () => {
|
||||||
assert.strictEqual(getSafeLongFromTimestamp(-456).toString(), '-456');
|
assert.strictEqual(getSafeLongFromTimestamp(-456).toString(), '-456');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns Long.MAX_VALUE when passed Infinity', () => {
|
it('returns MAX_SAFE_DATE when passed Infinity', () => {
|
||||||
assert(getSafeLongFromTimestamp(Infinity).equals(Long.MAX_VALUE));
|
assert.strictEqual(
|
||||||
|
getSafeLongFromTimestamp(Infinity).toNumber(),
|
||||||
|
MAX_SAFE_DATE
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns Long.MAX_VALUE when passed very large numbers, outside of JavaScript's safely representable range", () => {
|
it('returns Long.MAX_VALUE when passed Infinity and overriden', () => {
|
||||||
assert.equal(getSafeLongFromTimestamp(Number.MAX_VALUE), Long.MAX_VALUE);
|
assert(
|
||||||
|
getSafeLongFromTimestamp(Infinity, Long.MAX_VALUE).equals(Long.MAX_VALUE)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns MAX_SAFE_DATE when passed very large numbers, outside of JavaScript's safely representable range", () => {
|
||||||
|
assert.strictEqual(
|
||||||
|
getSafeLongFromTimestamp(Number.MAX_VALUE).toNumber(),
|
||||||
|
MAX_SAFE_DATE
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getTimestampFromLong', () => {
|
describe('getTimestampFromLong', () => {
|
||||||
|
it('returns zero when passed negative Long', () => {
|
||||||
|
assert.equal(getTimestampFromLong(Long.fromNumber(-1)), 0);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns zero when passed 0 Long', () => {
|
it('returns zero when passed 0 Long', () => {
|
||||||
assert.equal(getTimestampFromLong(Long.fromNumber(0)), 0);
|
assert.equal(getTimestampFromLong(Long.fromNumber(0)), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns Number.MAX_SAFE_INTEGER when passed Long.MAX_VALUE', () => {
|
it('returns MAX_SAFE_DATE when passed Long.MAX_VALUE', () => {
|
||||||
assert.equal(getTimestampFromLong(Long.MAX_VALUE), Number.MAX_SAFE_INTEGER);
|
assert.equal(getTimestampFromLong(Long.MAX_VALUE), MAX_SAFE_DATE);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a normal number', () => {
|
it('returns a normal number', () => {
|
||||||
|
@ -48,6 +67,24 @@ describe('getTimestampFromLong', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getCheckedTimestampFromLong', () => {
|
||||||
|
it('throws on absent Long', () => {
|
||||||
|
assert.throws(() => getCheckedTimestampFromLong(null));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on negative Long', () => {
|
||||||
|
assert.throws(() => getCheckedTimestampFromLong(Long.fromNumber(-1)));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on Long.MAX_VALUE', () => {
|
||||||
|
assert.throws(() => getCheckedTimestampFromLong(Long.MAX_VALUE));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw otherwise', () => {
|
||||||
|
assert.equal(getCheckedTimestampFromLong(Long.fromNumber(16)), 16);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getTimestampOrUndefinedFromLong', () => {
|
describe('getTimestampOrUndefinedFromLong', () => {
|
||||||
it('returns undefined when passed 0 Long', () => {
|
it('returns undefined when passed 0 Long', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
@ -56,10 +93,10 @@ describe('getTimestampOrUndefinedFromLong', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns Number.MAX_SAFE_INTEGER when passed Long.MAX_VALUE', () => {
|
it('returns MAX_SAFE_DATE when passed Long.MAX_VALUE', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
getTimestampOrUndefinedFromLong(Long.MAX_VALUE),
|
getTimestampOrUndefinedFromLong(Long.MAX_VALUE),
|
||||||
Number.MAX_SAFE_INTEGER
|
MAX_SAFE_DATE
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -71,3 +108,26 @@ describe('getTimestampOrUndefinedFromLong', () => {
|
||||||
assert.equal(getTimestampOrUndefinedFromLong(null), undefined);
|
assert.equal(getTimestampOrUndefinedFromLong(null), undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getCheckedTimestampOrUndefinedFromLong', () => {
|
||||||
|
it('throws on negative Long', () => {
|
||||||
|
assert.throws(() =>
|
||||||
|
getCheckedTimestampOrUndefinedFromLong(Long.fromNumber(-1))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined on absent Long', () => {
|
||||||
|
assert.equal(getCheckedTimestampOrUndefinedFromLong(null), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns undefined on zero Long', () => {
|
||||||
|
assert.equal(getCheckedTimestampOrUndefinedFromLong(Long.ZERO), undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a normal number', () => {
|
||||||
|
assert.equal(
|
||||||
|
getCheckedTimestampOrUndefinedFromLong(Long.fromNumber(16)),
|
||||||
|
16
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -207,8 +207,8 @@ export function formatDate(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_SAFE_DATE = 8640000000000000;
|
export const MAX_SAFE_DATE = 8640000000000000;
|
||||||
const MIN_SAFE_DATE = -8640000000000000;
|
export const MIN_SAFE_DATE = -8640000000000000;
|
||||||
|
|
||||||
export function toBoundedDate(timestamp: number): Date {
|
export function toBoundedDate(timestamp: number): Date {
|
||||||
return new Date(Math.max(MIN_SAFE_DATE, Math.min(timestamp, MAX_SAFE_DATE)));
|
return new Date(Math.max(MIN_SAFE_DATE, Math.min(timestamp, MAX_SAFE_DATE)));
|
||||||
|
|
|
@ -3,23 +3,55 @@
|
||||||
|
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
|
|
||||||
export function getSafeLongFromTimestamp(timestamp = 0): Long {
|
import { MAX_SAFE_DATE } from './timestamp';
|
||||||
if (timestamp >= Number.MAX_SAFE_INTEGER) {
|
|
||||||
return Long.MAX_VALUE;
|
export function getSafeLongFromTimestamp(
|
||||||
|
timestamp = 0,
|
||||||
|
maxValue: Long | number = MAX_SAFE_DATE
|
||||||
|
): Long {
|
||||||
|
if (timestamp >= MAX_SAFE_DATE) {
|
||||||
|
if (typeof maxValue === 'number') {
|
||||||
|
return Long.fromNumber(maxValue);
|
||||||
|
}
|
||||||
|
return maxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Long.fromNumber(timestamp);
|
return Long.fromNumber(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTimestampFromLong(value?: Long | null): number {
|
export function getTimestampFromLong(value?: Long | null): number {
|
||||||
if (!value) {
|
if (!value || value.isNegative()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const num = value.toNumber();
|
const num = value.toNumber();
|
||||||
|
|
||||||
if (num >= Number.MAX_SAFE_INTEGER) {
|
if (num > MAX_SAFE_DATE) {
|
||||||
return Number.MAX_SAFE_INTEGER;
|
return MAX_SAFE_DATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class InvalidTimestampError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(`InvalidTimestampError: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCheckedTimestampFromLong(value?: Long | null): number {
|
||||||
|
if (value == null) {
|
||||||
|
throw new InvalidTimestampError('No number');
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = value.toNumber();
|
||||||
|
|
||||||
|
if (num < 0) {
|
||||||
|
throw new InvalidTimestampError('Underflow');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num > MAX_SAFE_DATE) {
|
||||||
|
throw new InvalidTimestampError('Overflow');
|
||||||
}
|
}
|
||||||
|
|
||||||
return num;
|
return num;
|
||||||
|
@ -34,3 +66,13 @@ export function getTimestampOrUndefinedFromLong(
|
||||||
|
|
||||||
return getTimestampFromLong(value);
|
return getTimestampFromLong(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCheckedTimestampOrUndefinedFromLong(
|
||||||
|
value?: Long | null
|
||||||
|
): number | undefined {
|
||||||
|
if (!value || value.isZero()) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCheckedTimestampFromLong(value);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue