@malloydata/malloy-tests 0.0.434 → 0.0.436

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.
@@ -274,11 +274,11 @@ describe.each(runtimes.runtimeList)('%s date and time', (dbName, runtime) => {
274
274
  });
275
275
  });
276
276
 
277
- test.when(runtime.dialect.hasTimestamptz)(
277
+ test.when(timestamptz)(
278
278
  'extract from timestamptz without query timezone',
279
279
  async () => {
280
280
  // TIMESTAMPTZ representing midnight UTC
281
- // Without query timezone, extract should happen in UTC (or stored tz for Trino)
281
+ // Without query timezone, extract should happen in UTC
282
282
  // Expected: hour = 0, day = 20
283
283
  await expect(
284
284
  `run: ${dbName}.sql("SELECT 1 as x") -> {
@@ -291,6 +291,80 @@ describe.each(runtimes.runtimeList)('%s date and time', (dbName, runtime) => {
291
291
  }
292
292
  );
293
293
 
294
+ // 20:00 on the 30th in Mexico City is 02:00 on the 31st in UTC. With no
295
+ // query time zone the value is read in UTC, not in the zone it was written in.
296
+ describe('timestamptz written in another zone', () => {
297
+ const tzModel = () =>
298
+ mkTestModel(runtime, {
299
+ tzdata: [
300
+ {
301
+ tstz: TV.timestamptz('2020-01-30 20:00:00[America/Mexico_City]'),
302
+ same_instant: TV.timestamp('2020-01-31 02:00:00'),
303
+ same_digits: TV.timestamp('2020-01-30 20:00:00'),
304
+ },
305
+ ],
306
+ });
307
+
308
+ test.when(timestamptz)('extract', async () => {
309
+ await expect(
310
+ 'run: tzdata -> { select: h is hour(tstz), d is day(tstz) }'
311
+ ).toMatchResult(tzModel(), {h: 2, d: 31});
312
+ });
313
+
314
+ test.when(timestamptz)('truncate', async () => {
315
+ await expect('run: tzdata -> { select: d is tstz.day }').toMatchResult(
316
+ tzModel(),
317
+ {d: '2020-01-31T00:00:00.000Z'}
318
+ );
319
+ });
320
+
321
+ test.when(timestamptz)('add a month', async () => {
322
+ // Jan 31 + 1 month is Feb 29; Jan 30 + 1 month in Mexico City is Mar 1 UTC
323
+ await expect(
324
+ 'run: tzdata -> { select: next is tstz + 1 month }'
325
+ ).toMatchResult(tzModel(), {next: '2020-02-29T02:00:00.000Z'});
326
+ });
327
+
328
+ test.when(timestamptz)('cast to date', async () => {
329
+ await expect('run: tzdata -> { select: d is tstz::date }').toMatchResult(
330
+ tzModel(),
331
+ {d: '2020-01-31'}
332
+ );
333
+ });
334
+
335
+ test.when(timestamptz)('cast keeps the instant', async () => {
336
+ await expect(`
337
+ run: tzdata -> {
338
+ select:
339
+ ts is tstz::timestamp
340
+ same is pick 'yes' when tstz::timestamp = tstz else 'no'
341
+ back is pick 'yes' when same_instant::timestamptz = tstz else 'no'
342
+ }
343
+ `).toMatchResult(tzModel(), {
344
+ ts: '2020-01-31T02:00:00.000Z',
345
+ same: 'yes',
346
+ back: 'yes',
347
+ });
348
+ });
349
+
350
+ test.when(timestamptz)('compare to timestamp', async () => {
351
+ await expect(`
352
+ run: tzdata -> {
353
+ select:
354
+ eq_instant is pick 'yes' when tstz = same_instant else 'no'
355
+ eq_digits is pick 'yes' when tstz = same_digits else 'no'
356
+ gt_digits is pick 'yes' when tstz > same_digits else 'no'
357
+ lt_digits is pick 'yes' when same_digits < tstz else 'no'
358
+ }
359
+ `).toMatchResult(tzModel(), {
360
+ eq_instant: 'yes',
361
+ eq_digits: 'no',
362
+ gt_digits: 'yes',
363
+ lt_digits: 'yes',
364
+ });
365
+ });
366
+ });
367
+
294
368
  describe('date truncation', () => {
295
369
  test('date trunc day', async () => {
296
370
  const eq = sqlEq('t_date.day', '@2021-02-24');
@@ -526,16 +600,16 @@ describe.each(runtimes.runtimeList)('%s date and time', (dbName, runtime) => {
526
600
  });
527
601
  await expect(`
528
602
  run: fourTimes -> { select:
529
- isBefore is tbefore ? @2026-01-01
530
- isFirst is tfirst ? @2026-01-01
531
- isLast is tlast ? @2026-01-01
532
- isAfter is tafter ? @2026-01-01
603
+ isBefore is pick 'in' when tbefore ? @2026-01-01 else 'out'
604
+ isFirst is pick 'in' when tfirst ? @2026-01-01 else 'out'
605
+ isLast is pick 'in' when tlast ? @2026-01-01 else 'out'
606
+ isAfter is pick 'in' when tafter ? @2026-01-01 else 'out'
533
607
  }
534
608
  `).toMatchResult(tm, {
535
- isBefore: false,
536
- isFirst: true,
537
- isLast: true,
538
- isAfter: false,
609
+ isBefore: 'out',
610
+ isFirst: 'in',
611
+ isLast: 'in',
612
+ isAfter: 'out',
539
613
  });
540
614
  });
541
615
  });
@@ -730,6 +804,7 @@ describe.each(runtimes.runtimeList)('%s: tz literals', (dbName, runtime) => {
730
804
 
731
805
  describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
732
806
  const testModel = wrapTestModel(runtime, '');
807
+ const timestamptz = runtime.dialect.hasTimestamptz;
733
808
  const ts = new TestSelect(runtime.dialect);
734
809
  const selectMidnight = `"""${ts.generate({
735
810
  utc_midnight_ts: ts.mk_timestamp('2020-02-20 00:00:00'),
@@ -762,7 +837,7 @@ describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
762
837
  ).toMatchResult(testModel, {mex_midnight: 18, mex_day: 19});
763
838
  });
764
839
 
765
- test.when(runtime.dialect.hasTimestamptz)(
840
+ test.when(timestamptz)(
766
841
  'extract from timestamptz with query timezone',
767
842
  async () => {
768
843
  // TIMESTAMPTZ representing midnight UTC
@@ -792,6 +867,16 @@ describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
792
867
  ).toMatchResult(testModel, {mex_day: mex_19.toJSDate()});
793
868
  });
794
869
 
870
+ test('truncate hour in a zone a half hour off UTC', async () => {
871
+ // 02:10 in Kolkata is 20:40 UTC, and the Kolkata hour starts at 20:30
872
+ await expect(
873
+ `run: ${dbName}.sql("SELECT 1 as x") -> {
874
+ timezone: 'Asia/Kolkata'
875
+ select: kol_hour is @2020-01-31 02:10:00.hour
876
+ }`
877
+ ).toMatchResult(testModel, {kol_hour: '2020-01-30T20:30:00.000Z'});
878
+ });
879
+
795
880
  test('truncate week', async () => {
796
881
  // the 19th in mexico is a wednesday, so trunc to the 15th
797
882
  const mex_19 = LuxonDateTime.fromISO('2020-02-19T00:00:00', {zone});
@@ -814,18 +899,15 @@ describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
814
899
  }`
815
900
  ).toMatchResult(testModel, {mex_date: '2020-02-19'});
816
901
  });
817
- test.when(runtime.dialect.hasTimestamptz)(
818
- 'cast timestamptz to date',
819
- async () => {
820
- await expect(
821
- `run: ${dbName}.sql("SELECT 1 as x") -> {
902
+ test.when(timestamptz)('cast timestamptz to date', async () => {
903
+ await expect(
904
+ `run: ${dbName}.sql("SELECT 1 as x") -> {
822
905
  timezone: '${zone}'
823
906
  extend: { dimension: utc_tstz is @2020-02-20 00:00:00[UTC]::timestamptz }
824
907
  select: mex_date is utc_tstz::date
825
908
  }`
826
- ).toMatchResult(testModel, {mex_date: '2020-02-19'});
827
- }
828
- );
909
+ ).toMatchResult(testModel, {mex_date: '2020-02-19'});
910
+ });
829
911
  test('cast date to timestamp', async () => {
830
912
  await expect(
831
913
  `run: ${dbName}.sql(${selectMidnight}) -> {
@@ -842,31 +924,95 @@ describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
842
924
  }`
843
925
  ).toMatchResult(testModel, {d2020: '2020-02-20'});
844
926
  });
845
- test.when(runtime.dialect.hasTimestamptz)(
846
- 'cast date to timestamptz',
847
- async () => {
848
- await expect(
849
- `run: ${dbName}.sql(${selectMidnight}) -> {
927
+ test.when(timestamptz)('cast date to timestamptz', async () => {
928
+ await expect(
929
+ `run: ${dbName}.sql(${selectMidnight}) -> {
850
930
  timezone: '${zone}'
851
931
  select: mex_date is date_2020::timestamptz
852
932
  }`
853
- ).toMatchResult(testModel, {mex_date: zone_2020.toJSDate()});
854
- }
855
- );
933
+ ).toMatchResult(testModel, {mex_date: zone_2020.toJSDate()});
934
+ });
856
935
 
857
- test.when(runtime.dialect.hasTimestamptz)(
858
- 'compare timestamptz to range',
859
- async () => {
860
- const tzModel = mkTestModel(runtime, {
936
+ test.when(timestamptz)('compare timestamptz to range', async () => {
937
+ const tzModel = mkTestModel(runtime, {
938
+ tzdata: [
939
+ {updated: TV.timestamptz('2020-01-15 00:00:00[America/Mexico_City]')},
940
+ ],
941
+ });
942
+ await expect(
943
+ "run: tzdata -> { select: cmp is pick 'in' when updated ? @2020-01 else 'out' }"
944
+ ).toMatchResult(tzModel, {cmp: 'in'});
945
+ });
946
+
947
+ // 11:00 on the 31st in Tokyo is 20:00 on the 30th in Mexico City. With a
948
+ // query time zone the value is read there, not in the zone it was written in.
949
+ describe('timestamptz written in another zone', () => {
950
+ const tzModel = () =>
951
+ mkTestModel(runtime, {
861
952
  tzdata: [
862
- {updated: TV.timestamptz('2020-01-15 00:00:00[America/Mexico_City]')},
953
+ {
954
+ tstz: TV.timestamptz('2020-01-31 11:00:00[Asia/Tokyo]'),
955
+ same_instant: TV.timestamp('2020-01-31 02:00:00'),
956
+ },
863
957
  ],
864
958
  });
865
- await expect(
866
- 'run: tzdata -> { select: cmp is updated ? @2020-01 }'
867
- ).toMatchResult(tzModel, {cmp: true});
868
- }
869
- );
959
+
960
+ test.when(timestamptz)('extract', async () => {
961
+ await expect(`
962
+ run: tzdata -> {
963
+ timezone: '${zone}'
964
+ select: h is hour(tstz), d is day(tstz)
965
+ }
966
+ `).toMatchResult(tzModel(), {h: 20, d: 30});
967
+ });
968
+
969
+ test.when(timestamptz)('truncate', async () => {
970
+ const mex_30 = LuxonDateTime.fromISO('2020-01-30T00:00:00', {zone});
971
+ await expect(`
972
+ run: tzdata -> {
973
+ timezone: '${zone}'
974
+ select: d is tstz.day
975
+ }
976
+ `).toMatchResult(tzModel(), {d: mex_30.toJSDate()});
977
+ });
978
+
979
+ test.when(timestamptz)('add a month', async () => {
980
+ // Jan 30 + 1 month is Feb 29 in Mexico City; Jan 31 + 1 month in Tokyo
981
+ // or UTC is a day earlier
982
+ const mex_29 = LuxonDateTime.fromISO('2020-02-29T20:00:00', {zone});
983
+ await expect(`
984
+ run: tzdata -> {
985
+ timezone: '${zone}'
986
+ select: next is tstz + 1 month
987
+ }
988
+ `).toMatchResult(tzModel(), {next: mex_29.toJSDate()});
989
+ });
990
+
991
+ test.when(timestamptz)('cast to date', async () => {
992
+ await expect(`
993
+ run: tzdata -> {
994
+ timezone: '${zone}'
995
+ select: d is tstz::date
996
+ }
997
+ `).toMatchResult(tzModel(), {d: '2020-01-30'});
998
+ });
999
+
1000
+ test.when(timestamptz)('cast keeps the instant', async () => {
1001
+ await expect(`
1002
+ run: tzdata -> {
1003
+ timezone: '${zone}'
1004
+ select:
1005
+ ts is tstz::timestamp
1006
+ same is pick 'yes' when tstz::timestamp = tstz else 'no'
1007
+ back is pick 'yes' when same_instant::timestamptz = tstz else 'no'
1008
+ }
1009
+ `).toMatchResult(tzModel(), {
1010
+ ts: '2020-01-31T02:00:00.000Z',
1011
+ same: 'yes',
1012
+ back: 'yes',
1013
+ });
1014
+ });
1015
+ });
870
1016
 
871
1017
  // Test for timezone rendering issue with nested queries
872
1018
  test.when(runtime.supportsNesting)(
@@ -946,16 +1092,16 @@ describe.each(runtimes.runtimeList)('%s: query tz', (dbName, runtime) => {
946
1092
  timezone: 'Europe/Dublin'
947
1093
  select:
948
1094
  june1_correct,
949
- month_ok is by_month = june1_correct, by_month
950
- day_ok is by_day = june1_correct, by_day
951
- hour_ok is by_hour = june1_correct, by_hour
952
- quarter_ok is by_quarter = august1_correct, by_quarter
1095
+ month_ok is pick 'yes' when by_month = june1_correct else 'no', by_month
1096
+ day_ok is pick 'yes' when by_day = june1_correct else 'no', by_day
1097
+ hour_ok is pick 'yes' when by_hour = june1_correct else 'no', by_hour
1098
+ quarter_ok is pick 'yes' when by_quarter = august1_correct else 'no', by_quarter
953
1099
  }`
954
1100
  ).toMatchResult(testModel, {
955
- month_ok: true,
956
- day_ok: true,
957
- hour_ok: true,
958
- quarter_ok: true,
1101
+ month_ok: 'yes',
1102
+ day_ok: 'yes',
1103
+ hour_ok: 'yes',
1104
+ quarter_ok: 'yes',
959
1105
  });
960
1106
  });
961
1107
  });