@sygnl/timestamp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # @sygnl/timestamp
2
+
3
+ Safe timestamp utilities for ad platforms with clock skew protection.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @sygnl/timestamp
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { getSafeTimestamp, formatGoogleTimestamp } from '@sygnl/timestamp';
15
+
16
+ // Prevent future timestamps (clock skew protection)
17
+ const eventTimestamp = Date.now() + 5000; // 5 seconds in the future
18
+ const safeTimestamp = getSafeTimestamp(eventTimestamp);
19
+ // Returns: Date.now() - 1000 (1 second ago, safe for ad platforms)
20
+
21
+ // Format for Google Ads
22
+ const googleTs = formatGoogleTimestamp(eventTimestamp);
23
+ // Returns: "2024-01-01 12:34:56+00:00"
24
+ ```
25
+
26
+ ## Problem
27
+
28
+ Ad platforms (Meta, Google, Snapchat, TikTok, Bing) reject events with timestamps that are:
29
+
30
+ 1. In the future (clock skew between servers)
31
+ 2. Too close to "now" (platforms need processing buffer)
32
+
33
+ This causes conversion tracking failures and lost revenue.
34
+
35
+ ## Solution
36
+
37
+ This package ensures timestamps are always at least 1 second in the past, preventing rejections while maintaining accuracy.
38
+
39
+ ## Features
40
+
41
+ - Clock skew protection
42
+ - Platform-specific formatting (Google, Meta, etc.)
43
+ - Unix epoch conversion (ms and seconds)
44
+ - ISO 8601 formatting
45
+ - Time delta calculation
46
+ - Future timestamp detection
47
+ - Type-safe with TypeScript
48
+
49
+ ## API
50
+
51
+ ### `getSafeTimestamp(timestampMs: number): number`
52
+
53
+ Returns a safe timestamp in milliseconds, guaranteed to be in the past.
54
+
55
+ ```typescript
56
+ const future = Date.now() + 5000;
57
+ getSafeTimestamp(future);
58
+ // Returns: Date.now() - 1000
59
+
60
+ const past = Date.now() - 10000;
61
+ getSafeTimestamp(past);
62
+ // Returns: past (unchanged)
63
+ ```
64
+
65
+ ### `getSafeTimestampSeconds(timestampMs: number): number`
66
+
67
+ Returns a safe timestamp in Unix epoch seconds (for Meta CAPI, Bing, etc.).
68
+
69
+ ```typescript
70
+ getSafeTimestampSeconds(1704067200000);
71
+ // Returns: 1704067199 (in seconds)
72
+ ```
73
+
74
+ ### `formatGoogleTimestamp(timestampMs: number): string`
75
+
76
+ Formats timestamp for Google Ads: "YYYY-MM-DD HH:mm:ss+00:00"
77
+
78
+ ```typescript
79
+ formatGoogleTimestamp(1704067200000);
80
+ // Returns: "2024-01-01 00:00:00+00:00"
81
+ ```
82
+
83
+ ### `formatISO(timestampMs: number): string`
84
+
85
+ Formats timestamp as ISO 8601: "YYYY-MM-DDTHH:mm:ss.sssZ"
86
+
87
+ ```typescript
88
+ formatISO(1704067200000);
89
+ // Returns: "2024-01-01T00:00:00.000Z"
90
+ ```
91
+
92
+ ### `getTimeDelta(timestampMs: number): number`
93
+
94
+ Returns time difference between timestamp and now (for debugging).
95
+
96
+ ```typescript
97
+ const future = Date.now() + 5000;
98
+ getTimeDelta(future);
99
+ // Returns: ~5000 (5 seconds in the future)
100
+
101
+ const past = Date.now() - 10000;
102
+ getTimeDelta(past);
103
+ // Returns: ~-10000 (10 seconds in the past)
104
+ ```
105
+
106
+ ### `isFutureTimestamp(timestampMs: number, bufferMs?: number): boolean`
107
+
108
+ Checks if timestamp is in the future.
109
+
110
+ ```typescript
111
+ const future = Date.now() + 1000;
112
+ isFutureTimestamp(future);
113
+ // Returns: true
114
+ ```
115
+
116
+ ## Use Cases
117
+
118
+ ### Meta Conversions API
119
+
120
+ ```typescript
121
+ import { getSafeTimestampSeconds } from '@sygnl/timestamp';
122
+
123
+ const payload = {
124
+ event_name: 'Purchase',
125
+ event_time: getSafeTimestampSeconds(event.timestamp),
126
+ user_data: { /* ... */ }
127
+ };
128
+ ```
129
+
130
+ ### Google Ads Enhanced Conversions
131
+
132
+ ```typescript
133
+ import { formatGoogleTimestamp } from '@sygnl/timestamp';
134
+
135
+ const conversion = {
136
+ conversionDateTime: formatGoogleTimestamp(event.timestamp),
137
+ // ...
138
+ };
139
+ ```
140
+
141
+ ### Universal Solution
142
+
143
+ ```typescript
144
+ import { getSafeTimestamp } from '@sygnl/timestamp';
145
+
146
+ // Works for all platforms
147
+ const safeTs = getSafeTimestamp(clientTimestamp);
148
+
149
+ // Then format as needed per platform
150
+ ```
151
+
152
+ ## License
153
+
154
+ Apache-2.0
155
+
156
+ Copyright 2026 Edge Foundry, Inc.
package/dist/index.cjs ADDED
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ formatGoogleTimestamp: () => formatGoogleTimestamp,
24
+ formatISO: () => formatISO,
25
+ getSafeTimestamp: () => getSafeTimestamp,
26
+ getSafeTimestampSeconds: () => getSafeTimestampSeconds,
27
+ getTimeDelta: () => getTimeDelta,
28
+ isFutureTimestamp: () => isFutureTimestamp
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+ function getSafeTimestamp(timestampMs) {
32
+ return Math.min(timestampMs, Date.now() - 1e3);
33
+ }
34
+ function getSafeTimestampSeconds(timestampMs) {
35
+ return Math.floor(getSafeTimestamp(timestampMs) / 1e3);
36
+ }
37
+ function formatGoogleTimestamp(timestampMs) {
38
+ const safeTimestamp = getSafeTimestamp(timestampMs);
39
+ const date = new Date(safeTimestamp);
40
+ const year = date.getUTCFullYear();
41
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
42
+ const day = String(date.getUTCDate()).padStart(2, "0");
43
+ const hours = String(date.getUTCHours()).padStart(2, "0");
44
+ const minutes = String(date.getUTCMinutes()).padStart(2, "0");
45
+ const seconds = String(date.getUTCSeconds()).padStart(2, "0");
46
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}+00:00`;
47
+ }
48
+ function formatISO(timestampMs) {
49
+ const safeTimestamp = getSafeTimestamp(timestampMs);
50
+ return new Date(safeTimestamp).toISOString();
51
+ }
52
+ function getTimeDelta(timestampMs) {
53
+ return timestampMs - Date.now();
54
+ }
55
+ function isFutureTimestamp(timestampMs, bufferMs = 0) {
56
+ return timestampMs > Date.now() + bufferMs;
57
+ }
58
+ // Annotate the CommonJS export names for ESM import in node:
59
+ 0 && (module.exports = {
60
+ formatGoogleTimestamp,
61
+ formatISO,
62
+ getSafeTimestamp,
63
+ getSafeTimestampSeconds,
64
+ getTimeDelta,
65
+ isFutureTimestamp
66
+ });
67
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @sygnl/timestamp\n * \n * Safe timestamp utilities for ad platforms with clock skew protection.\n * \n * Prevents timestamp rejections from ad platform APIs (Google, Meta, Snapchat, TikTok, etc.)\n * by ensuring timestamps are never in the future and include a processing buffer.\n * \n * @example\n * ```typescript\n * import { getSafeTimestamp, formatGoogleTimestamp } from '@sygnl/timestamp';\n * \n * // Prevent future timestamps\n * const safeTs = getSafeTimestamp(eventTimestamp);\n * \n * // Format for Google Ads\n * const googleTs = formatGoogleTimestamp(eventTimestamp);\n * ```\n */\n\n/**\n * Get a safe timestamp that will never be rejected by ad platforms\n * \n * Ad platforms reject timestamps that are:\n * - In the future (due to clock skew between servers)\n * - Too close to \"now\" (platforms need processing buffer)\n * \n * This function ensures the timestamp is always at least 1 second in the past.\n * \n * @param timestampMs - The original event timestamp in milliseconds\n * @returns A safe timestamp in milliseconds, guaranteed to be in the past\n * \n * @example\n * ```typescript\n * const now = Date.now();\n * const future = now + 5000; // 5 seconds in the future\n * \n * getSafeTimestamp(future);\n * // Returns: Date.now() - 1000 (1 second ago)\n * \n * const past = now - 10000; // 10 seconds ago\n * getSafeTimestamp(past);\n * // Returns: past (unchanged)\n * ```\n */\nexport function getSafeTimestamp(timestampMs: number): number {\n // Use Math.min to cap at \"now - 1 second\"\n // This prevents future timestamps and adds a safety buffer\n return Math.min(timestampMs, Date.now() - 1000);\n}\n\n/**\n * Get safe timestamp in seconds (Unix epoch)\n * \n * Many ad platforms (Meta, Bing, Google) expect timestamps in seconds.\n * This is a convenience function that converts ms → seconds safely.\n * \n * @param timestampMs - The original event timestamp in milliseconds\n * @returns A safe timestamp in seconds (Unix epoch)\n * \n * @example\n * ```typescript\n * const now = Date.now(); // 1704067200000\n * getSafeTimestampSeconds(now);\n * // Returns: 1704067199 (1 second ago, in seconds)\n * ```\n */\nexport function getSafeTimestampSeconds(timestampMs: number): number {\n return Math.floor(getSafeTimestamp(timestampMs) / 1000);\n}\n\n/**\n * Format safe timestamp as ISO 8601 string for Google Ads\n * \n * Google Ads requires: \"YYYY-MM-DD HH:mm:ss+00:00\"\n * \n * @param timestampMs - The original event timestamp in milliseconds\n * @returns ISO 8601 formatted string in UTC\n * \n * @example\n * ```typescript\n * formatGoogleTimestamp(1704067200000);\n * // Returns: \"2024-01-01 00:00:00+00:00\"\n * ```\n */\nexport function formatGoogleTimestamp(timestampMs: number): string {\n const safeTimestamp = getSafeTimestamp(timestampMs);\n const date = new Date(safeTimestamp);\n \n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const hours = String(date.getUTCHours()).padStart(2, '0');\n const minutes = String(date.getUTCMinutes()).padStart(2, '0');\n const seconds = String(date.getUTCSeconds()).padStart(2, '0');\n \n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}+00:00`;\n}\n\n/**\n * Format safe timestamp as ISO 8601 string (standard format)\n * \n * Returns: \"YYYY-MM-DDTHH:mm:ss.sssZ\"\n * \n * @param timestampMs - The original event timestamp in milliseconds\n * @returns ISO 8601 formatted string with milliseconds\n * \n * @example\n * ```typescript\n * formatISO(1704067200000);\n * // Returns: \"2024-01-01T00:00:00.000Z\"\n * ```\n */\nexport function formatISO(timestampMs: number): string {\n const safeTimestamp = getSafeTimestamp(timestampMs);\n return new Date(safeTimestamp).toISOString();\n}\n\n/**\n * Get time difference between event timestamp and now\n * \n * Useful for debugging clock skew issues.\n * \n * @param timestampMs - The event timestamp in milliseconds\n * @returns Difference in milliseconds (positive = future, negative = past)\n * \n * @example\n * ```typescript\n * const future = Date.now() + 5000;\n * getTimeDelta(future);\n * // Returns: ~5000 (5 seconds in the future)\n * \n * const past = Date.now() - 10000;\n * getTimeDelta(past);\n * // Returns: ~-10000 (10 seconds in the past)\n * ```\n */\nexport function getTimeDelta(timestampMs: number): number {\n return timestampMs - Date.now();\n}\n\n/**\n * Check if timestamp is in the future (has clock skew)\n * \n * @param timestampMs - The event timestamp in milliseconds\n * @param bufferMs - Buffer in milliseconds (default: 0)\n * @returns true if timestamp is in the future\n * \n * @example\n * ```typescript\n * const future = Date.now() + 1000;\n * isFutureTimestamp(future);\n * // Returns: true\n * ```\n */\nexport function isFutureTimestamp(timestampMs: number, bufferMs: number = 0): boolean {\n return timestampMs > Date.now() + bufferMs;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6CO,SAAS,iBAAiB,aAA6B;AAG5D,SAAO,KAAK,IAAI,aAAa,KAAK,IAAI,IAAI,GAAI;AAChD;AAkBO,SAAS,wBAAwB,aAA6B;AACnE,SAAO,KAAK,MAAM,iBAAiB,WAAW,IAAI,GAAI;AACxD;AAgBO,SAAS,sBAAsB,aAA6B;AACjE,QAAM,gBAAgB,iBAAiB,WAAW;AAClD,QAAM,OAAO,IAAI,KAAK,aAAa;AAEnC,QAAM,OAAO,KAAK,eAAe;AACjC,QAAM,QAAQ,OAAO,KAAK,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AAC5D,QAAM,MAAM,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACrD,QAAM,QAAQ,OAAO,KAAK,YAAY,CAAC,EAAE,SAAS,GAAG,GAAG;AACxD,QAAM,UAAU,OAAO,KAAK,cAAc,CAAC,EAAE,SAAS,GAAG,GAAG;AAC5D,QAAM,UAAU,OAAO,KAAK,cAAc,CAAC,EAAE,SAAS,GAAG,GAAG;AAE5D,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO,IAAI,OAAO;AAC/D;AAgBO,SAAS,UAAU,aAA6B;AACrD,QAAM,gBAAgB,iBAAiB,WAAW;AAClD,SAAO,IAAI,KAAK,aAAa,EAAE,YAAY;AAC7C;AAqBO,SAAS,aAAa,aAA6B;AACxD,SAAO,cAAc,KAAK,IAAI;AAChC;AAgBO,SAAS,kBAAkB,aAAqB,WAAmB,GAAY;AACpF,SAAO,cAAc,KAAK,IAAI,IAAI;AACpC;","names":[]}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @sygnl/timestamp
3
+ *
4
+ * Safe timestamp utilities for ad platforms with clock skew protection.
5
+ *
6
+ * Prevents timestamp rejections from ad platform APIs (Google, Meta, Snapchat, TikTok, etc.)
7
+ * by ensuring timestamps are never in the future and include a processing buffer.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { getSafeTimestamp, formatGoogleTimestamp } from '@sygnl/timestamp';
12
+ *
13
+ * // Prevent future timestamps
14
+ * const safeTs = getSafeTimestamp(eventTimestamp);
15
+ *
16
+ * // Format for Google Ads
17
+ * const googleTs = formatGoogleTimestamp(eventTimestamp);
18
+ * ```
19
+ */
20
+ /**
21
+ * Get a safe timestamp that will never be rejected by ad platforms
22
+ *
23
+ * Ad platforms reject timestamps that are:
24
+ * - In the future (due to clock skew between servers)
25
+ * - Too close to "now" (platforms need processing buffer)
26
+ *
27
+ * This function ensures the timestamp is always at least 1 second in the past.
28
+ *
29
+ * @param timestampMs - The original event timestamp in milliseconds
30
+ * @returns A safe timestamp in milliseconds, guaranteed to be in the past
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const now = Date.now();
35
+ * const future = now + 5000; // 5 seconds in the future
36
+ *
37
+ * getSafeTimestamp(future);
38
+ * // Returns: Date.now() - 1000 (1 second ago)
39
+ *
40
+ * const past = now - 10000; // 10 seconds ago
41
+ * getSafeTimestamp(past);
42
+ * // Returns: past (unchanged)
43
+ * ```
44
+ */
45
+ declare function getSafeTimestamp(timestampMs: number): number;
46
+ /**
47
+ * Get safe timestamp in seconds (Unix epoch)
48
+ *
49
+ * Many ad platforms (Meta, Bing, Google) expect timestamps in seconds.
50
+ * This is a convenience function that converts ms → seconds safely.
51
+ *
52
+ * @param timestampMs - The original event timestamp in milliseconds
53
+ * @returns A safe timestamp in seconds (Unix epoch)
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const now = Date.now(); // 1704067200000
58
+ * getSafeTimestampSeconds(now);
59
+ * // Returns: 1704067199 (1 second ago, in seconds)
60
+ * ```
61
+ */
62
+ declare function getSafeTimestampSeconds(timestampMs: number): number;
63
+ /**
64
+ * Format safe timestamp as ISO 8601 string for Google Ads
65
+ *
66
+ * Google Ads requires: "YYYY-MM-DD HH:mm:ss+00:00"
67
+ *
68
+ * @param timestampMs - The original event timestamp in milliseconds
69
+ * @returns ISO 8601 formatted string in UTC
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * formatGoogleTimestamp(1704067200000);
74
+ * // Returns: "2024-01-01 00:00:00+00:00"
75
+ * ```
76
+ */
77
+ declare function formatGoogleTimestamp(timestampMs: number): string;
78
+ /**
79
+ * Format safe timestamp as ISO 8601 string (standard format)
80
+ *
81
+ * Returns: "YYYY-MM-DDTHH:mm:ss.sssZ"
82
+ *
83
+ * @param timestampMs - The original event timestamp in milliseconds
84
+ * @returns ISO 8601 formatted string with milliseconds
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * formatISO(1704067200000);
89
+ * // Returns: "2024-01-01T00:00:00.000Z"
90
+ * ```
91
+ */
92
+ declare function formatISO(timestampMs: number): string;
93
+ /**
94
+ * Get time difference between event timestamp and now
95
+ *
96
+ * Useful for debugging clock skew issues.
97
+ *
98
+ * @param timestampMs - The event timestamp in milliseconds
99
+ * @returns Difference in milliseconds (positive = future, negative = past)
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const future = Date.now() + 5000;
104
+ * getTimeDelta(future);
105
+ * // Returns: ~5000 (5 seconds in the future)
106
+ *
107
+ * const past = Date.now() - 10000;
108
+ * getTimeDelta(past);
109
+ * // Returns: ~-10000 (10 seconds in the past)
110
+ * ```
111
+ */
112
+ declare function getTimeDelta(timestampMs: number): number;
113
+ /**
114
+ * Check if timestamp is in the future (has clock skew)
115
+ *
116
+ * @param timestampMs - The event timestamp in milliseconds
117
+ * @param bufferMs - Buffer in milliseconds (default: 0)
118
+ * @returns true if timestamp is in the future
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const future = Date.now() + 1000;
123
+ * isFutureTimestamp(future);
124
+ * // Returns: true
125
+ * ```
126
+ */
127
+ declare function isFutureTimestamp(timestampMs: number, bufferMs?: number): boolean;
128
+
129
+ export { formatGoogleTimestamp, formatISO, getSafeTimestamp, getSafeTimestampSeconds, getTimeDelta, isFutureTimestamp };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @sygnl/timestamp
3
+ *
4
+ * Safe timestamp utilities for ad platforms with clock skew protection.
5
+ *
6
+ * Prevents timestamp rejections from ad platform APIs (Google, Meta, Snapchat, TikTok, etc.)
7
+ * by ensuring timestamps are never in the future and include a processing buffer.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { getSafeTimestamp, formatGoogleTimestamp } from '@sygnl/timestamp';
12
+ *
13
+ * // Prevent future timestamps
14
+ * const safeTs = getSafeTimestamp(eventTimestamp);
15
+ *
16
+ * // Format for Google Ads
17
+ * const googleTs = formatGoogleTimestamp(eventTimestamp);
18
+ * ```
19
+ */
20
+ /**
21
+ * Get a safe timestamp that will never be rejected by ad platforms
22
+ *
23
+ * Ad platforms reject timestamps that are:
24
+ * - In the future (due to clock skew between servers)
25
+ * - Too close to "now" (platforms need processing buffer)
26
+ *
27
+ * This function ensures the timestamp is always at least 1 second in the past.
28
+ *
29
+ * @param timestampMs - The original event timestamp in milliseconds
30
+ * @returns A safe timestamp in milliseconds, guaranteed to be in the past
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const now = Date.now();
35
+ * const future = now + 5000; // 5 seconds in the future
36
+ *
37
+ * getSafeTimestamp(future);
38
+ * // Returns: Date.now() - 1000 (1 second ago)
39
+ *
40
+ * const past = now - 10000; // 10 seconds ago
41
+ * getSafeTimestamp(past);
42
+ * // Returns: past (unchanged)
43
+ * ```
44
+ */
45
+ declare function getSafeTimestamp(timestampMs: number): number;
46
+ /**
47
+ * Get safe timestamp in seconds (Unix epoch)
48
+ *
49
+ * Many ad platforms (Meta, Bing, Google) expect timestamps in seconds.
50
+ * This is a convenience function that converts ms → seconds safely.
51
+ *
52
+ * @param timestampMs - The original event timestamp in milliseconds
53
+ * @returns A safe timestamp in seconds (Unix epoch)
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const now = Date.now(); // 1704067200000
58
+ * getSafeTimestampSeconds(now);
59
+ * // Returns: 1704067199 (1 second ago, in seconds)
60
+ * ```
61
+ */
62
+ declare function getSafeTimestampSeconds(timestampMs: number): number;
63
+ /**
64
+ * Format safe timestamp as ISO 8601 string for Google Ads
65
+ *
66
+ * Google Ads requires: "YYYY-MM-DD HH:mm:ss+00:00"
67
+ *
68
+ * @param timestampMs - The original event timestamp in milliseconds
69
+ * @returns ISO 8601 formatted string in UTC
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * formatGoogleTimestamp(1704067200000);
74
+ * // Returns: "2024-01-01 00:00:00+00:00"
75
+ * ```
76
+ */
77
+ declare function formatGoogleTimestamp(timestampMs: number): string;
78
+ /**
79
+ * Format safe timestamp as ISO 8601 string (standard format)
80
+ *
81
+ * Returns: "YYYY-MM-DDTHH:mm:ss.sssZ"
82
+ *
83
+ * @param timestampMs - The original event timestamp in milliseconds
84
+ * @returns ISO 8601 formatted string with milliseconds
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * formatISO(1704067200000);
89
+ * // Returns: "2024-01-01T00:00:00.000Z"
90
+ * ```
91
+ */
92
+ declare function formatISO(timestampMs: number): string;
93
+ /**
94
+ * Get time difference between event timestamp and now
95
+ *
96
+ * Useful for debugging clock skew issues.
97
+ *
98
+ * @param timestampMs - The event timestamp in milliseconds
99
+ * @returns Difference in milliseconds (positive = future, negative = past)
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const future = Date.now() + 5000;
104
+ * getTimeDelta(future);
105
+ * // Returns: ~5000 (5 seconds in the future)
106
+ *
107
+ * const past = Date.now() - 10000;
108
+ * getTimeDelta(past);
109
+ * // Returns: ~-10000 (10 seconds in the past)
110
+ * ```
111
+ */
112
+ declare function getTimeDelta(timestampMs: number): number;
113
+ /**
114
+ * Check if timestamp is in the future (has clock skew)
115
+ *
116
+ * @param timestampMs - The event timestamp in milliseconds
117
+ * @param bufferMs - Buffer in milliseconds (default: 0)
118
+ * @returns true if timestamp is in the future
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * const future = Date.now() + 1000;
123
+ * isFutureTimestamp(future);
124
+ * // Returns: true
125
+ * ```
126
+ */
127
+ declare function isFutureTimestamp(timestampMs: number, bufferMs?: number): boolean;
128
+
129
+ export { formatGoogleTimestamp, formatISO, getSafeTimestamp, getSafeTimestampSeconds, getTimeDelta, isFutureTimestamp };
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ // src/index.ts
2
+ function getSafeTimestamp(timestampMs) {
3
+ return Math.min(timestampMs, Date.now() - 1e3);
4
+ }
5
+ function getSafeTimestampSeconds(timestampMs) {
6
+ return Math.floor(getSafeTimestamp(timestampMs) / 1e3);
7
+ }
8
+ function formatGoogleTimestamp(timestampMs) {
9
+ const safeTimestamp = getSafeTimestamp(timestampMs);
10
+ const date = new Date(safeTimestamp);
11
+ const year = date.getUTCFullYear();
12
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
13
+ const day = String(date.getUTCDate()).padStart(2, "0");
14
+ const hours = String(date.getUTCHours()).padStart(2, "0");
15
+ const minutes = String(date.getUTCMinutes()).padStart(2, "0");
16
+ const seconds = String(date.getUTCSeconds()).padStart(2, "0");
17
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}+00:00`;
18
+ }
19
+ function formatISO(timestampMs) {
20
+ const safeTimestamp = getSafeTimestamp(timestampMs);
21
+ return new Date(safeTimestamp).toISOString();
22
+ }
23
+ function getTimeDelta(timestampMs) {
24
+ return timestampMs - Date.now();
25
+ }
26
+ function isFutureTimestamp(timestampMs, bufferMs = 0) {
27
+ return timestampMs > Date.now() + bufferMs;
28
+ }
29
+ export {
30
+ formatGoogleTimestamp,
31
+ formatISO,
32
+ getSafeTimestamp,
33
+ getSafeTimestampSeconds,
34
+ getTimeDelta,
35
+ isFutureTimestamp
36
+ };
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @sygnl/timestamp\n * \n * Safe timestamp utilities for ad platforms with clock skew protection.\n * \n * Prevents timestamp rejections from ad platform APIs (Google, Meta, Snapchat, TikTok, etc.)\n * by ensuring timestamps are never in the future and include a processing buffer.\n * \n * @example\n * ```typescript\n * import { getSafeTimestamp, formatGoogleTimestamp } from '@sygnl/timestamp';\n * \n * // Prevent future timestamps\n * const safeTs = getSafeTimestamp(eventTimestamp);\n * \n * // Format for Google Ads\n * const googleTs = formatGoogleTimestamp(eventTimestamp);\n * ```\n */\n\n/**\n * Get a safe timestamp that will never be rejected by ad platforms\n * \n * Ad platforms reject timestamps that are:\n * - In the future (due to clock skew between servers)\n * - Too close to \"now\" (platforms need processing buffer)\n * \n * This function ensures the timestamp is always at least 1 second in the past.\n * \n * @param timestampMs - The original event timestamp in milliseconds\n * @returns A safe timestamp in milliseconds, guaranteed to be in the past\n * \n * @example\n * ```typescript\n * const now = Date.now();\n * const future = now + 5000; // 5 seconds in the future\n * \n * getSafeTimestamp(future);\n * // Returns: Date.now() - 1000 (1 second ago)\n * \n * const past = now - 10000; // 10 seconds ago\n * getSafeTimestamp(past);\n * // Returns: past (unchanged)\n * ```\n */\nexport function getSafeTimestamp(timestampMs: number): number {\n // Use Math.min to cap at \"now - 1 second\"\n // This prevents future timestamps and adds a safety buffer\n return Math.min(timestampMs, Date.now() - 1000);\n}\n\n/**\n * Get safe timestamp in seconds (Unix epoch)\n * \n * Many ad platforms (Meta, Bing, Google) expect timestamps in seconds.\n * This is a convenience function that converts ms → seconds safely.\n * \n * @param timestampMs - The original event timestamp in milliseconds\n * @returns A safe timestamp in seconds (Unix epoch)\n * \n * @example\n * ```typescript\n * const now = Date.now(); // 1704067200000\n * getSafeTimestampSeconds(now);\n * // Returns: 1704067199 (1 second ago, in seconds)\n * ```\n */\nexport function getSafeTimestampSeconds(timestampMs: number): number {\n return Math.floor(getSafeTimestamp(timestampMs) / 1000);\n}\n\n/**\n * Format safe timestamp as ISO 8601 string for Google Ads\n * \n * Google Ads requires: \"YYYY-MM-DD HH:mm:ss+00:00\"\n * \n * @param timestampMs - The original event timestamp in milliseconds\n * @returns ISO 8601 formatted string in UTC\n * \n * @example\n * ```typescript\n * formatGoogleTimestamp(1704067200000);\n * // Returns: \"2024-01-01 00:00:00+00:00\"\n * ```\n */\nexport function formatGoogleTimestamp(timestampMs: number): string {\n const safeTimestamp = getSafeTimestamp(timestampMs);\n const date = new Date(safeTimestamp);\n \n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const hours = String(date.getUTCHours()).padStart(2, '0');\n const minutes = String(date.getUTCMinutes()).padStart(2, '0');\n const seconds = String(date.getUTCSeconds()).padStart(2, '0');\n \n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}+00:00`;\n}\n\n/**\n * Format safe timestamp as ISO 8601 string (standard format)\n * \n * Returns: \"YYYY-MM-DDTHH:mm:ss.sssZ\"\n * \n * @param timestampMs - The original event timestamp in milliseconds\n * @returns ISO 8601 formatted string with milliseconds\n * \n * @example\n * ```typescript\n * formatISO(1704067200000);\n * // Returns: \"2024-01-01T00:00:00.000Z\"\n * ```\n */\nexport function formatISO(timestampMs: number): string {\n const safeTimestamp = getSafeTimestamp(timestampMs);\n return new Date(safeTimestamp).toISOString();\n}\n\n/**\n * Get time difference between event timestamp and now\n * \n * Useful for debugging clock skew issues.\n * \n * @param timestampMs - The event timestamp in milliseconds\n * @returns Difference in milliseconds (positive = future, negative = past)\n * \n * @example\n * ```typescript\n * const future = Date.now() + 5000;\n * getTimeDelta(future);\n * // Returns: ~5000 (5 seconds in the future)\n * \n * const past = Date.now() - 10000;\n * getTimeDelta(past);\n * // Returns: ~-10000 (10 seconds in the past)\n * ```\n */\nexport function getTimeDelta(timestampMs: number): number {\n return timestampMs - Date.now();\n}\n\n/**\n * Check if timestamp is in the future (has clock skew)\n * \n * @param timestampMs - The event timestamp in milliseconds\n * @param bufferMs - Buffer in milliseconds (default: 0)\n * @returns true if timestamp is in the future\n * \n * @example\n * ```typescript\n * const future = Date.now() + 1000;\n * isFutureTimestamp(future);\n * // Returns: true\n * ```\n */\nexport function isFutureTimestamp(timestampMs: number, bufferMs: number = 0): boolean {\n return timestampMs > Date.now() + bufferMs;\n}\n"],"mappings":";AA6CO,SAAS,iBAAiB,aAA6B;AAG5D,SAAO,KAAK,IAAI,aAAa,KAAK,IAAI,IAAI,GAAI;AAChD;AAkBO,SAAS,wBAAwB,aAA6B;AACnE,SAAO,KAAK,MAAM,iBAAiB,WAAW,IAAI,GAAI;AACxD;AAgBO,SAAS,sBAAsB,aAA6B;AACjE,QAAM,gBAAgB,iBAAiB,WAAW;AAClD,QAAM,OAAO,IAAI,KAAK,aAAa;AAEnC,QAAM,OAAO,KAAK,eAAe;AACjC,QAAM,QAAQ,OAAO,KAAK,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AAC5D,QAAM,MAAM,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACrD,QAAM,QAAQ,OAAO,KAAK,YAAY,CAAC,EAAE,SAAS,GAAG,GAAG;AACxD,QAAM,UAAU,OAAO,KAAK,cAAc,CAAC,EAAE,SAAS,GAAG,GAAG;AAC5D,QAAM,UAAU,OAAO,KAAK,cAAc,CAAC,EAAE,SAAS,GAAG,GAAG;AAE5D,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,KAAK,IAAI,OAAO,IAAI,OAAO;AAC/D;AAgBO,SAAS,UAAU,aAA6B;AACrD,QAAM,gBAAgB,iBAAiB,WAAW;AAClD,SAAO,IAAI,KAAK,aAAa,EAAE,YAAY;AAC7C;AAqBO,SAAS,aAAa,aAA6B;AACxD,SAAO,cAAc,KAAK,IAAI;AAChC;AAgBO,SAAS,kBAAkB,aAAqB,WAAmB,GAAY;AACpF,SAAO,cAAc,KAAK,IAAI,IAAI;AACpC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@sygnl/timestamp",
3
+ "version": "1.0.0",
4
+ "description": "Safe timestamp utilities for ad platforms with clock skew protection",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "keywords": [
20
+ "timestamp",
21
+ "clock-skew",
22
+ "ad-platforms",
23
+ "meta",
24
+ "google",
25
+ "snapchat",
26
+ "tiktok",
27
+ "time",
28
+ "unix-epoch"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "test:coverage": "vitest run --coverage",
35
+ "typecheck": "tsc --noEmit",
36
+ "prepublishOnly": "npm run build"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^20.10.0",
40
+ "tsup": "^8.0.1",
41
+ "typescript": "^5.3.3",
42
+ "vitest": "^1.0.4",
43
+ "@vitest/coverage-v8": "^1.0.4"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ },
48
+ "author": "Edge Foundry, Inc.",
49
+ "license": "Apache-2.0"
50
+ }