@@ -5,7 +5,7 @@ import { DEFAULT_CERTS } from './certs.js';
55import * as pgwire from './pgwire.js' ;
66import { PgType } from './pgwire_types.js' ;
77import { ConnectOptions } from './socket_adapter.js' ;
8- import { DatabaseInputValue , TimeValue } from '@powersync/service-sync-rules' ;
8+ import { DatabaseInputValue , DateTimeValue } from '@powersync/service-sync-rules' ;
99
1010// TODO this is duplicated, but maybe that is ok
1111export interface NormalizedConnectionConfig {
@@ -222,6 +222,8 @@ export function lsnMakeComparable(text: string) {
222222 return h . padStart ( 8 , '0' ) + '/' + l . padStart ( 8 , '0' ) ;
223223}
224224
225+ const timeRegex = / ^ ( [ \d \- ] + ) ( [ \d : ] + ) ( \. \d + ) ? ( [ + - ] [ \d : ] + ) ? $ / ;
226+
225227/**
226228 * Convert a postgres timestamptz to a SQLite-compatible/normalized timestamp.
227229 *
@@ -233,17 +235,17 @@ export function lsnMakeComparable(text: string) {
233235 *
234236 * We have specific exceptions for -infinity and infinity.
235237 */
236- export function timestamptzToSqlite ( source ?: string ) : TimeValue | null {
238+ export function timestamptzToSqlite ( source ?: string ) : DateTimeValue | null {
237239 if ( source == null ) {
238240 return null ;
239241 }
240242 // Make compatible with SQLite
241- const match = / ^ ( [ \d \- ] + ) ( [ \d : ] + ) ( \. \d + ) ? ( [ + - ] [ \d : ] + ) $ / . exec ( source ) ;
243+ const match = timeRegex . exec ( source ) ;
242244 if ( match == null ) {
243245 if ( source == 'infinity' ) {
244- return new TimeValue ( '9999-12-31T23:59:59Z' ) ;
246+ return new DateTimeValue ( '9999-12-31T23:59:59Z' ) ;
245247 } else if ( source == '-infinity' ) {
246- return new TimeValue ( '0000-01-01T00:00:00Z' ) ;
248+ return new DateTimeValue ( '0000-01-01T00:00:00Z' ) ;
247249 } else {
248250 return null ;
249251 }
@@ -258,10 +260,15 @@ export function timestamptzToSqlite(source?: string): TimeValue | null {
258260 return null ;
259261 }
260262
261- const baseValue = parsed . toISOString ( ) . replace ( 'Z' , '' ) ;
262- const baseText = `${ baseValue } Z` ;
263+ const baseValue = parsed . toISOString ( ) . replace ( '.000' , '' ) . replace ( 'Z' , '' ) ;
263264
264- return new TimeValue ( baseText ) ;
265+ // In the new format, we always use ISO 8601. Since Postgres drops zeroes from the fractional seconds, we also pad
266+ // that back to the highest theoretical precision (microseconds). This ensures that sorting returned values as text
267+ // returns them in order of the time value they represent.
268+ //
269+ // In the old format, we keep the sub-second precision only if it's not `.000`.
270+ const missingPrecision = precision ?. padEnd ( 7 , '0' ) ?? '.000000' ;
271+ return new DateTimeValue ( `${ baseValue } ${ missingPrecision } Z` , `${ baseValue . replace ( 'T' , ' ' ) } ${ precision ?? '' } Z` ) ;
265272}
266273
267274/**
@@ -271,17 +278,26 @@ export function timestamptzToSqlite(source?: string): TimeValue | null {
271278 *
272279 * https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-VALUES
273280 */
274- export function timestampToSqlite ( source ?: string ) : TimeValue | null {
281+ export function timestampToSqlite ( source ?: string ) : DateTimeValue | null {
275282 if ( source == null ) {
276283 return null ;
277284 }
278- if ( source == 'infinity' ) {
279- return new TimeValue ( '9999-12-31T23:59:59' ) ;
280- } else if ( source == '-infinity' ) {
281- return new TimeValue ( '0000-01-01T00:00:00' ) ;
282- } else {
283- return new TimeValue ( source . replace ( ' ' , 'T' ) ) ;
285+
286+ const match = timeRegex . exec ( source ) ;
287+ if ( match == null ) {
288+ if ( source == 'infinity' ) {
289+ return new DateTimeValue ( '9999-12-31T23:59:59' ) ;
290+ } else if ( source == '-infinity' ) {
291+ return new DateTimeValue ( '0000-01-01T00:00:00' ) ;
292+ } else {
293+ return null ;
294+ }
284295 }
296+
297+ const [ _ , date , time , precision , __ ] = match as any ;
298+ const missingPrecision = precision ?. padEnd ( 7 , '0' ) ?? '.000000' ;
299+
300+ return new DateTimeValue ( `${ date } T${ time } ${ missingPrecision } ` , source ) ;
285301}
286302/**
287303 * For date, we keep it mostly as-is.
0 commit comments