gpstime 0.9.2__py3-none-any.whl → 0.10.0__py3-none-any.whl

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.
gpstime/__init__.py CHANGED
@@ -24,7 +24,7 @@ import argparse
24
24
  import subprocess
25
25
 
26
26
  from dateutil.parser import parse as duparse
27
- from dateutil.tz import tzutc, tzlocal
27
+ from dateutil.tz import tzutc, tzlocal, tzoffset
28
28
 
29
29
  try:
30
30
  from .__version__ import version as __version__
@@ -36,7 +36,7 @@ except ModuleNotFoundError:
36
36
  # (3.2.0-1)
37
37
  except TypeError:
38
38
  __version__ = setuptools_scm.get_version()
39
- except LookupError:
39
+ except Exception:
40
40
  __version__ = '?.?.?'
41
41
 
42
42
  from .leaps import LEAPDATA
@@ -49,6 +49,37 @@ ISO_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
49
49
  # UNIX time for GPS 0 (1980-01-06T00:00:00Z)
50
50
  GPS0 = 315964800
51
51
 
52
+ # Timezone abbreviation mappings for dateutil parser.
53
+ # These are needed because abbreviations like "PST" are ambiguous and not
54
+ # part of the IANA timezone database. Without this mapping, dateutil will
55
+ # only recognize abbreviations that match the current local timezone.
56
+ # Note: Some abbreviations are ambiguous (e.g., CST = US Central or China Standard).
57
+ # We default to US interpretations for ambiguous abbreviations.
58
+ TZINFOS = {
59
+ # US timezones
60
+ 'PST': tzoffset('PST', -8*3600), # Pacific Standard Time
61
+ 'PDT': tzoffset('PDT', -7*3600), # Pacific Daylight Time
62
+ 'MST': tzoffset('MST', -7*3600), # Mountain Standard Time
63
+ 'MDT': tzoffset('MDT', -6*3600), # Mountain Daylight Time
64
+ 'CST': tzoffset('CST', -6*3600), # Central Standard Time (US)
65
+ 'CDT': tzoffset('CDT', -5*3600), # Central Daylight Time
66
+ 'EST': tzoffset('EST', -5*3600), # Eastern Standard Time
67
+ 'EDT': tzoffset('EDT', -4*3600), # Eastern Daylight Time
68
+ # European timezones
69
+ 'GMT': tzoffset('GMT', 0), # Greenwich Mean Time
70
+ 'UTC': tzoffset('UTC', 0), # Coordinated Universal Time
71
+ 'WET': tzoffset('WET', 0), # Western European Time
72
+ 'WEST': tzoffset('WEST', 1*3600), # Western European Summer Time
73
+ 'CET': tzoffset('CET', 1*3600), # Central European Time
74
+ 'CEST': tzoffset('CEST', 2*3600), # Central European Summer Time
75
+ 'EET': tzoffset('EET', 2*3600), # Eastern European Time
76
+ 'EEST': tzoffset('EEST', 3*3600), # Eastern European Summer Time
77
+ # Other common timezones
78
+ 'JST': tzoffset('JST', 9*3600), # Japan Standard Time
79
+ 'KST': tzoffset('KST', 9*3600), # Korea Standard Time
80
+ 'IST': tzoffset('IST', 5*3600+1800), # India Standard Time (UTC+5:30)
81
+ }
82
+
52
83
  ##################################################
53
84
 
54
85
 
@@ -89,7 +120,7 @@ def _du_date_parse(string='now'):
89
120
  """Parse date/time string to UNIX timestamp with dateutils parse
90
121
 
91
122
  """
92
- return float(duparse(string).timestamp())
123
+ return float(duparse(string, tzinfos=TZINFOS).timestamp())
93
124
 
94
125
 
95
126
  def _date_parse(string="now"):
gpstime/__main__.py CHANGED
@@ -1,11 +1,40 @@
1
1
  import argparse
2
+ import os
3
+ import sys
4
+ from datetime import datetime
2
5
 
3
- from dateutil.tz import tzutc, tzlocal
6
+ from dateutil.tz import tzutc, tzlocal, gettz
4
7
 
5
8
  from . import __version__
6
9
  from . import ISO_FORMAT, gpstime, GPSTimeParseAction
7
10
 
8
11
 
12
+ def check_tz_validity():
13
+ """Check if TZ environment variable is valid.
14
+
15
+ Some timezone abbreviations (like 'EDT') are not valid POSIX timezone
16
+ values and will cause the system to fall back to UTC. This function
17
+ detects this condition and warns the user.
18
+
19
+ Returns:
20
+ True if TZ is unset or valid, False if TZ is set but invalid.
21
+ """
22
+ tz_env = os.environ.get('TZ')
23
+ if not tz_env:
24
+ return True
25
+
26
+ # Use dateutil's gettz to check if TZ is recognized.
27
+ # gettz returns tzlocal() for unrecognized values, and tzfile() for valid ones.
28
+ tz_result = gettz(tz_env)
29
+ if isinstance(tz_result, tzlocal):
30
+ print(f"warning: TZ='{tz_env}' is not a valid timezone specification; "
31
+ f"use IANA names (e.g., 'America/New_York') or POSIX format "
32
+ f"(e.g., 'EST5EDT')",
33
+ file=sys.stderr)
34
+ return False
35
+ return True
36
+
37
+
9
38
  PARSER = argparse.ArgumentParser(
10
39
  description="""GPS time conversion
11
40
 
@@ -54,6 +83,11 @@ def tzname(tz):
54
83
  def main():
55
84
  args = PARSER.parse_args()
56
85
 
86
+ tz_valid = check_tz_validity()
87
+
88
+ if args.tz == 'local' and not tz_valid:
89
+ sys.exit("error: -l/--local requires a valid TZ environment variable")
90
+
57
91
  if args.tz == 'gps' and args.format == ISO_FORMAT:
58
92
  PARSER.error("argument -g/--gps: not allowed with argument -i/--iso")
59
93
 
@@ -70,7 +104,8 @@ def main():
70
104
  if not args.tz:
71
105
  ltz = tzlocal()
72
106
  utz = tzutc()
73
- print('{}: {}'.format(tzname(ltz), gt.astimezone(ltz).strftime(args.format)))
107
+ if tz_valid:
108
+ print('{}: {}'.format(tzname(ltz), gt.astimezone(ltz).strftime(args.format)))
74
109
  print('{}: {}'.format('UTC', gt.astimezone(utz).strftime(args.format)))
75
110
  print('{}: {:.6f}'.format('GPS', gps))
76
111
  elif args.tz == 'gps':
gpstime/__version__.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.9.2'
32
- __version_tuple__ = version_tuple = (0, 9, 2)
31
+ __version__ = version = '0.10.0'
32
+ __version_tuple__ = version_tuple = (0, 10, 0)
33
33
 
34
- __commit_id__ = commit_id = 'gfa330f139'
34
+ __commit_id__ = commit_id = 'g4e7f71cba'
@@ -0,0 +1,502 @@
1
+ import datetime
2
+ import os
3
+ import shutil
4
+ import sys
5
+ import time
6
+ from contextlib import contextmanager
7
+
8
+ import pytest
9
+ from dateutil.tz import tzutc
10
+
11
+ import gpstime
12
+
13
+
14
+ @contextmanager
15
+ def temporary_tz(tz_value):
16
+ """Context manager to temporarily set the TZ environment variable."""
17
+ old_tz = os.environ.get('TZ')
18
+ try:
19
+ if tz_value is None:
20
+ os.environ.pop('TZ', None)
21
+ else:
22
+ os.environ['TZ'] = tz_value
23
+ time.tzset()
24
+ yield
25
+ finally:
26
+ if old_tz is None:
27
+ os.environ.pop('TZ', None)
28
+ else:
29
+ os.environ['TZ'] = old_tz
30
+ time.tzset()
31
+
32
+
33
+ def cu_date_available():
34
+ """return True if coreutils date parser available"""
35
+ return shutil.which("date") is not None
36
+
37
+
38
+ # Helper functions for roundtrip tests
39
+ def roundtrip_gps(gps):
40
+ assert gpstime.unix2gps(gpstime.gps2unix(gps)) == gps
41
+
42
+
43
+ def roundtrip_unix(unix):
44
+ assert gpstime.gps2unix(gpstime.unix2gps(unix)) == unix
45
+
46
+
47
+ ##################################################
48
+
49
+
50
+ def test_conversion():
51
+ assert gpstime.gps2unix(1133585676) == 1449550459
52
+ assert gpstime.unix2gps(1449550459) == 1133585676
53
+ assert 1133585676 == gpstime.unix2gps(gpstime.gps2unix(1133585676))
54
+
55
+
56
+ def test_conversion_past():
57
+ assert gpstime.gps2unix(123456789) == 439421586
58
+ assert gpstime.unix2gps(439421586) == 123456789
59
+
60
+
61
+ def test_conversion_onleap():
62
+ assert gpstime.gps2unix(425520006) == 741484798
63
+ assert gpstime.gps2unix(425520007) == 741484799
64
+ assert gpstime.gps2unix(425520008) == 741484799
65
+ assert gpstime.gps2unix(425520009) == 741484800
66
+ assert gpstime.unix2gps(741484798) == 425520006
67
+ assert gpstime.unix2gps(741484799) == 425520007
68
+ assert gpstime.unix2gps(741484800) == 425520009
69
+
70
+
71
+ def test_roundtrip():
72
+ roundtrip_gps(123456789)
73
+ roundtrip_unix(439421586)
74
+
75
+
76
+ @pytest.mark.xfail
77
+ def test_roundtrip_onleap():
78
+ roundtrip_gps(425520008)
79
+
80
+
81
+ def test_gpstime_new():
82
+ assert gpstime.gpstime(2015, 12, 8, 4, 54, 19, 0, tzutc()).gps() == 1133585676.0
83
+
84
+
85
+ def test_gpstime_fromdatetime():
86
+ dt = datetime.datetime(2015, 12, 8, 4, 54, 19, 0, tzutc())
87
+ gt = gpstime.gpstime.fromdatetime(dt)
88
+ assert isinstance(gt, gpstime.gpstime)
89
+ assert gt.gps() == 1133585676.0
90
+
91
+
92
+ def test_gpstime_parse_now_roundtrip():
93
+ gt0 = gpstime.gpstime.parse()
94
+ gt1 = gpstime.gpstime.parse(gt0.gps())
95
+ assert gt0 == gt1
96
+
97
+
98
+ def test_gpstime_parse_utc():
99
+ assert gpstime.gpstime.parse('Dec 08 2015 04:54:19.2 UTC').gps() == 1133585676.2
100
+
101
+
102
+ def test_gpstime_parse_utc2():
103
+ assert gpstime.gpstime.parse('2014-07-03 17:16:14 UTC').gps() == 1088442990.0
104
+
105
+
106
+ def test_gpstime_parse_local():
107
+ assert gpstime.gpstime.parse('Dec 08 2015 04:54:19.2 PST').gps() == 1133614476.2
108
+
109
+
110
+ def test_gpstime_parse_gps():
111
+ assert gpstime.gpstime.parse(1133585676.2).gps() == 1133585676.2
112
+
113
+
114
+ def test_gpstime_parse_gps_iso():
115
+ assert gpstime.gpstime.parse(1133585676.2).iso() == '2015-12-08T04:54:19.200000Z'
116
+
117
+
118
+ def test_gpstime_parse_iso():
119
+ assert gpstime.gpstime.parse('2015-12-08T04:54:19.200000Z').iso() == '2015-12-08T04:54:19.200000Z'
120
+
121
+
122
+ def test_gpstime_parse_iso_alt():
123
+ assert gpstime.gpstime.parse('2015-12-08_04:54:19.200000Z').iso() == '2015-12-08T04:54:19.200000Z'
124
+
125
+
126
+ @pytest.mark.skipif(not cu_date_available(), reason="coreutils date not available")
127
+ def test_gpstime_parse_timestamp():
128
+ assert gpstime.gpstime.parse('@1474821047').gps() == 1158856264
129
+
130
+
131
+ @pytest.mark.skipif(not cu_date_available(), reason="coreutils date not available")
132
+ def test_gpstime_parse_now0():
133
+ assert isinstance(gpstime.gpstime.parse(), gpstime.gpstime)
134
+
135
+
136
+ @pytest.mark.skipif(not cu_date_available(), reason="coreutils date not available")
137
+ def test_gpstime_parse_now1():
138
+ assert isinstance(gpstime.gpstime.parse('now'), gpstime.gpstime)
139
+
140
+
141
+ @pytest.mark.skipif(not cu_date_available(), reason="coreutils date not available")
142
+ def test_gpstime_parse_relative0():
143
+ assert isinstance(gpstime.gpstime.parse('yesterday'), gpstime.gpstime)
144
+
145
+
146
+ @pytest.mark.skipif(not cu_date_available(), reason="coreutils date not available")
147
+ def test_gpstime_parse_relative1():
148
+ assert isinstance(gpstime.gpstime.parse('1 month ago'), gpstime.gpstime)
149
+
150
+
151
+ def test_gpstime_parse_tconvert():
152
+ assert gpstime.gpstime.tconvert('2015-12-08T04:54:19.200000Z') == gpstime.gpstime.tconvert(1133585676.2)
153
+
154
+
155
+ def test_gpstime_fromgps():
156
+ assert gpstime.gpstime.fromgps(1133585676).iso() == '2015-12-08T04:54:19.000000Z'
157
+
158
+
159
+ def test_gpstime_fromgps_rounding():
160
+ assert gpstime.gpstime.fromgps(1133585676.274).iso() == '2015-12-08T04:54:19.274000Z'
161
+ assert gpstime.gpstime.fromgps(1133585676.874).iso() == '2015-12-08T04:54:19.874000Z'
162
+
163
+
164
+ @pytest.mark.skipif(not cu_date_available(), reason="coreutils date not available")
165
+ def test_non_standard_iso_format():
166
+ """Tests whether dates with '/' separators are parsed as iso dates.
167
+ Core utils instead parses them as if the 'T' character meant
168
+ a military time zone.
169
+ """
170
+ # both date utils and core utils will parse this using the default timezone
171
+ # date utils will also parse this using the default time zone
172
+ # but core utils will parse this using the military timezone 'T'
173
+ assert gpstime.gpstime.parse('2025-02-08T04:54:19') == gpstime.gpstime.parse('2025/02/08T04:54:19')
174
+
175
+
176
+ def test_gpstime_fromgps_timestamp():
177
+ assert gpstime.gpstime.fromgps(1133585676).timestamp() == 1449550459
178
+
179
+
180
+ def test_gpstime_fromgpsns_timestamp():
181
+ assert gpstime.gpstime.fromgpsns(1133585676200000000).gps() == 1133585676.2
182
+
183
+
184
+ def test_gpstime_tconvert_classmethod():
185
+ assert gpstime.gpstime.tconvert(1133585676.2).gps() == 1133585676.2
186
+
187
+
188
+ def test_gpstime_tconvert_iso():
189
+ assert gpstime.tconvert('2015-12-08T04:54:19.200000Z') == 1133585676.2
190
+
191
+
192
+ def test_gpstime_tconvert_gps():
193
+ assert gpstime.tconvert(1133585676.2) == '2015-12-08 04:54:19.200000 UTC'
194
+
195
+
196
+ @pytest.mark.xfail
197
+ def test_gpstime_parse_leap():
198
+ assert gpstime.gpstime.parse('Jun 30 1993 23:59:60 UTC').gps() == 425520008
199
+
200
+
201
+ @pytest.mark.xfail
202
+ def test_gpstime_parse_gps_leap():
203
+ assert gpstime.gpstime.parse(425520008).iso() == '1993-06-30T23:59:60.000000Z'
204
+
205
+
206
+ @pytest.mark.skipif(sys.version_info < (3, 8), reason="returns datetime in python < 3.8")
207
+ def test_gpstime_add():
208
+ dt = gpstime.gpstime.parse(1133585676.2)
209
+ delta = datetime.timedelta(seconds=1)
210
+ assert isinstance(dt + delta, gpstime.gpstime)
211
+ dt += delta
212
+ assert isinstance(dt, gpstime.gpstime)
213
+
214
+
215
+ @pytest.mark.skipif(sys.version_info < (3, 8), reason="returns datetime in python < 3.8")
216
+ def test_gpstime_subtract():
217
+ dt = gpstime.gpstime.parse(1133585676.2)
218
+ delta = datetime.timedelta(seconds=1)
219
+ assert isinstance(dt - delta, gpstime.gpstime)
220
+ dt -= delta
221
+ assert isinstance(dt, gpstime.gpstime)
222
+
223
+
224
+ ##################################################
225
+ # TZ environment variable tests
226
+ ##################################################
227
+
228
+
229
+ def test_parse_with_explicit_utc_ignores_tz_env():
230
+ """Parsing with explicit UTC should return same result regardless of TZ."""
231
+ expected_gps = 1133585676.2
232
+ date_string = 'Dec 08 2015 04:54:19.2 UTC'
233
+
234
+ with temporary_tz('America/New_York'):
235
+ result_ny = gpstime.gpstime.parse(date_string).gps()
236
+
237
+ with temporary_tz('Asia/Tokyo'):
238
+ result_tokyo = gpstime.gpstime.parse(date_string).gps()
239
+
240
+ with temporary_tz('UTC'):
241
+ result_utc = gpstime.gpstime.parse(date_string).gps()
242
+
243
+ assert result_ny == expected_gps
244
+ assert result_tokyo == expected_gps
245
+ assert result_utc == expected_gps
246
+
247
+
248
+ def test_parse_with_tz_utc():
249
+ """Parsing without explicit timezone when TZ=UTC."""
250
+ with temporary_tz('UTC'):
251
+ gt = gpstime.gpstime.parse('2015-12-08 04:54:19.2')
252
+ assert isinstance(gt, gpstime.gpstime)
253
+ assert gt.gps() == 1133585676.2
254
+
255
+
256
+ def test_parse_with_tz_new_york():
257
+ """Parsing without explicit timezone when TZ=America/New_York."""
258
+ with temporary_tz('America/New_York'):
259
+ gt = gpstime.gpstime.parse('2015-12-08 04:54:19.2')
260
+ assert isinstance(gt, gpstime.gpstime)
261
+ # New York is UTC-5 in December (EST), so 04:54:19 EST = 09:54:19 UTC
262
+ # GPS for 2015-12-08 09:54:19.2 UTC
263
+ assert gt.gps() == 1133603676.2
264
+
265
+
266
+ def test_parse_with_tz_los_angeles():
267
+ """Parsing without explicit timezone when TZ=America/Los_Angeles."""
268
+ with temporary_tz('America/Los_Angeles'):
269
+ gt = gpstime.gpstime.parse('2015-12-08 04:54:19.2')
270
+ assert isinstance(gt, gpstime.gpstime)
271
+ # Los Angeles is UTC-8 in December (PST), so 04:54:19 PST = 12:54:19 UTC
272
+ # GPS for 2015-12-08 12:54:19.2 UTC
273
+ assert gt.gps() == 1133614476.2
274
+
275
+
276
+ def test_parse_with_tz_tokyo():
277
+ """Parsing without explicit timezone when TZ=Asia/Tokyo."""
278
+ with temporary_tz('Asia/Tokyo'):
279
+ gt = gpstime.gpstime.parse('2015-12-08 04:54:19.2')
280
+ assert isinstance(gt, gpstime.gpstime)
281
+ # Tokyo is UTC+9, so 04:54:19 JST = 19:54:19 UTC previous day (Dec 7)
282
+ # GPS for 2015-12-07 19:54:19.2 UTC
283
+ assert gt.gps() == 1133553276.2
284
+
285
+
286
+ def test_parse_with_tz_offset_format():
287
+ """Parsing with TZ set to POSIX offset format."""
288
+ with temporary_tz('EST+5'):
289
+ gt = gpstime.gpstime.parse('2015-12-08 04:54:19.2')
290
+ assert isinstance(gt, gpstime.gpstime)
291
+ # EST+5 means UTC-5, so 04:54:19 = 09:54:19 UTC
292
+ assert gt.gps() == 1133603676.2
293
+
294
+
295
+ def test_parse_with_invalid_tz_falls_back():
296
+ """Parsing with invalid TZ value should still return a gpstime object."""
297
+ with temporary_tz('Invalid/Timezone'):
298
+ gt = gpstime.gpstime.parse('2015-12-08 04:54:19.2 UTC')
299
+ assert isinstance(gt, gpstime.gpstime)
300
+ # Explicit UTC in string should still work
301
+ assert gt.gps() == 1133585676.2
302
+
303
+
304
+ def test_parse_with_empty_tz():
305
+ """Parsing with empty TZ string."""
306
+ with temporary_tz(''):
307
+ gt = gpstime.gpstime.parse('2015-12-08 04:54:19.2 UTC')
308
+ assert isinstance(gt, gpstime.gpstime)
309
+ assert gt.gps() == 1133585676.2
310
+
311
+
312
+ def test_parse_with_tz_unset():
313
+ """Parsing with TZ environment variable unset."""
314
+ with temporary_tz(None):
315
+ gt = gpstime.gpstime.parse('2015-12-08 04:54:19.2 UTC')
316
+ assert isinstance(gt, gpstime.gpstime)
317
+ assert gt.gps() == 1133585676.2
318
+
319
+
320
+ def test_parse_iso_format_with_various_tz():
321
+ """ISO format with Z suffix should be consistent across TZ settings."""
322
+ iso_string = '2015-12-08T04:54:19.200000Z'
323
+ expected_gps = 1133585676.2
324
+
325
+ for tz in ['UTC', 'America/New_York', 'Europe/London', 'Asia/Tokyo']:
326
+ with temporary_tz(tz):
327
+ gt = gpstime.gpstime.parse(iso_string)
328
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
329
+
330
+
331
+ def test_parse_with_pst_in_string():
332
+ """Parsing with PST timezone in string should be consistent regardless of TZ env."""
333
+ date_string = 'Dec 08 2015 04:54:19.2 PST'
334
+ # PST is UTC-8, so 04:54:19 PST = 12:54:19 UTC
335
+ expected_gps = 1133614476.2
336
+
337
+ for tz in ['UTC', 'America/New_York', 'Asia/Tokyo']:
338
+ with temporary_tz(tz):
339
+ gt = gpstime.gpstime.parse(date_string)
340
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
341
+
342
+
343
+ def test_parse_with_est_in_string():
344
+ """Parsing with EST timezone in string should be consistent regardless of TZ env."""
345
+ date_string = 'Dec 08 2015 04:54:19.2 EST'
346
+ # EST is UTC-5, so 04:54:19 EST = 09:54:19 UTC
347
+ expected_gps = 1133603676.2
348
+
349
+ for tz in ['UTC', 'America/Los_Angeles', 'Europe/London']:
350
+ with temporary_tz(tz):
351
+ gt = gpstime.gpstime.parse(date_string)
352
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
353
+
354
+
355
+ def test_parse_with_cet_in_string():
356
+ """Parsing with CET timezone in string should be consistent regardless of TZ env."""
357
+ date_string = 'Dec 08 2015 04:54:19.2 CET'
358
+ # CET is UTC+1, so 04:54:19 CET = 03:54:19 UTC
359
+ expected_gps = 1133582076.2
360
+
361
+ for tz in ['UTC', 'America/New_York', 'Asia/Tokyo']:
362
+ with temporary_tz(tz):
363
+ gt = gpstime.gpstime.parse(date_string)
364
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
365
+
366
+
367
+ def test_parse_with_jst_in_string():
368
+ """Parsing with JST timezone in string should be consistent regardless of TZ env."""
369
+ date_string = 'Dec 08 2015 04:54:19.2 JST'
370
+ # JST is UTC+9, so 04:54:19 JST = 19:54:19 UTC previous day (Dec 7)
371
+ expected_gps = 1133553276.2
372
+
373
+ for tz in ['UTC', 'America/New_York', 'Europe/London']:
374
+ with temporary_tz(tz):
375
+ gt = gpstime.gpstime.parse(date_string)
376
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
377
+
378
+
379
+ def test_parse_with_numeric_offset_plus():
380
+ """Parsing with positive numeric timezone offset in string."""
381
+ date_string = '2015-12-08 04:54:19.2 +0530'
382
+ # +0530 is UTC+5:30, so 04:54:19 +0530 = 23:24:19.2 UTC previous day (Dec 7)
383
+ expected_gps = 1133565876.2
384
+
385
+ for tz in ['UTC', 'America/New_York', 'Asia/Tokyo']:
386
+ with temporary_tz(tz):
387
+ gt = gpstime.gpstime.parse(date_string)
388
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
389
+
390
+
391
+ def test_parse_with_numeric_offset_minus():
392
+ """Parsing with negative numeric timezone offset in string."""
393
+ date_string = '2015-12-08 04:54:19.2 -0700'
394
+ # -0700 is UTC-7, so 04:54:19 -0700 = 11:54:19 UTC
395
+ expected_gps = 1133610876.2
396
+
397
+ for tz in ['UTC', 'America/New_York', 'Asia/Tokyo']:
398
+ with temporary_tz(tz):
399
+ gt = gpstime.gpstime.parse(date_string)
400
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
401
+
402
+
403
+ def test_parse_with_iso_offset_format():
404
+ """Parsing ISO format with explicit timezone offset."""
405
+ date_string = '2015-12-08T04:54:19.2+05:30'
406
+ # +05:30 is UTC+5:30, so 04:54:19 +0530 = 23:24:19.2 UTC previous day (Dec 7)
407
+ expected_gps = 1133565876.2
408
+
409
+ for tz in ['UTC', 'America/Los_Angeles', 'Europe/Paris']:
410
+ with temporary_tz(tz):
411
+ gt = gpstime.gpstime.parse(date_string)
412
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
413
+
414
+
415
+ def test_parse_with_iso_negative_offset():
416
+ """Parsing ISO format with negative timezone offset."""
417
+ date_string = '2015-12-08T04:54:19.2-08:00'
418
+ # -08:00 is UTC-8 (PST), so 04:54:19 -08:00 = 12:54:19 UTC
419
+ expected_gps = 1133614476.2
420
+
421
+ for tz in ['UTC', 'America/New_York', 'Asia/Tokyo']:
422
+ with temporary_tz(tz):
423
+ gt = gpstime.gpstime.parse(date_string)
424
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
425
+
426
+
427
+ def test_parse_with_gmt_offset_in_string():
428
+ """Parsing with GMT offset notation in string."""
429
+ date_string = 'Dec 08 2015 04:54:19.2 GMT-5'
430
+ # Note: GMT-5 uses POSIX semantics where negative means east of GMT (UTC+5)
431
+ # so 04:54:19 GMT-5 = 23:54:19 UTC previous day (Dec 7)
432
+ expected_gps = 1133567676.2
433
+
434
+ for tz in ['UTC', 'America/Los_Angeles', 'Asia/Tokyo']:
435
+ with temporary_tz(tz):
436
+ gt = gpstime.gpstime.parse(date_string)
437
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
438
+
439
+
440
+ def test_parse_pdt_daylight_saving():
441
+ """Parsing with PDT (Pacific Daylight Time) in string."""
442
+ # Using a date in summer when PDT applies
443
+ date_string = 'Jul 15 2015 12:00:00 PDT'
444
+ # PDT is UTC-7, so 12:00:00 PDT = 19:00:00 UTC
445
+ expected_gps = 1121022017.0
446
+
447
+ for tz in ['UTC', 'America/New_York', 'Europe/London']:
448
+ with temporary_tz(tz):
449
+ gt = gpstime.gpstime.parse(date_string)
450
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
451
+
452
+
453
+ def test_parse_edt_daylight_saving():
454
+ """Parsing with EDT (Eastern Daylight Time) in string."""
455
+ # Using a date in summer when EDT applies
456
+ date_string = 'Jul 15 2015 12:00:00 EDT'
457
+ # EDT is UTC-4, so 12:00:00 EDT = 16:00:00 UTC
458
+ expected_gps = 1121011217.0
459
+
460
+ for tz in ['UTC', 'America/Los_Angeles', 'Asia/Tokyo']:
461
+ with temporary_tz(tz):
462
+ gt = gpstime.gpstime.parse(date_string)
463
+ assert gt.gps() == expected_gps, f"Failed for TZ={tz}"
464
+
465
+
466
+ ##################################################
467
+ # TZ validity warning tests
468
+ ##################################################
469
+
470
+
471
+ def test_check_tz_validity_warns_on_invalid_tz(capsys):
472
+ """Invalid TZ abbreviations should trigger a warning."""
473
+ from gpstime.__main__ import check_tz_validity
474
+
475
+ for invalid_tz in ['EDT', 'PST', 'CDT', 'INVALID']:
476
+ with temporary_tz(invalid_tz):
477
+ check_tz_validity()
478
+ captured = capsys.readouterr()
479
+ assert 'warning' in captured.err.lower(), f"Expected warning for TZ={invalid_tz}"
480
+ assert invalid_tz in captured.err, f"Warning should mention TZ={invalid_tz}"
481
+
482
+
483
+ def test_check_tz_validity_no_warning_on_valid_tz(capsys):
484
+ """Valid TZ values should not trigger a warning."""
485
+ from gpstime.__main__ import check_tz_validity
486
+
487
+ for valid_tz in ['UTC', 'GMT', 'America/New_York', 'America/Los_Angeles',
488
+ 'Europe/London', 'Asia/Tokyo', 'EST5EDT', 'PST8PDT']:
489
+ with temporary_tz(valid_tz):
490
+ check_tz_validity()
491
+ captured = capsys.readouterr()
492
+ assert 'warning' not in captured.err.lower(), f"Unexpected warning for TZ={valid_tz}"
493
+
494
+
495
+ def test_check_tz_validity_no_warning_when_tz_unset(capsys):
496
+ """No warning when TZ environment variable is not set."""
497
+ from gpstime.__main__ import check_tz_validity
498
+
499
+ with temporary_tz(None):
500
+ check_tz_validity()
501
+ captured = capsys.readouterr()
502
+ assert captured.err == '', "No warning expected when TZ is unset"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gpstime
3
- Version: 0.9.2
3
+ Version: 0.10.0
4
4
  Summary: GPS-aware datetime module
5
5
  Author-email: Jameson Graef Rollins <jameson.rollins@ligo.org>
6
6
  License-Expression: GPL-3.0-or-later
@@ -18,6 +18,8 @@ Requires-Dist: platformdirs; python_version >= "3.10"
18
18
  Requires-Dist: appdirs; python_version < "3.10"
19
19
  Requires-Dist: python-dateutil
20
20
  Requires-Dist: requests
21
+ Provides-Extra: test
22
+ Requires-Dist: pytest; extra == "test"
21
23
  Dynamic: license-file
22
24
 
23
25
  GPS-aware datetime module
@@ -0,0 +1,12 @@
1
+ gpstime/__init__.py,sha256=_rbMuDdRvODcavB4mYIvBupGO9gWnPHE0GPAj_Z-45U,9085
2
+ gpstime/__main__.py,sha256=Nx0gb5RsLy57aVrWjLPr4QRBQSp1lzJWYQEtAclpuRg,4032
3
+ gpstime/__version__.py,sha256=igblEm12og3foJqQDhe-uMOKTisKN23pDxOm1eUc7oY,714
4
+ gpstime/leaps.py,sha256=QduewuPZce_BpbB5zkMR4c_sZeATn1oD7XuPIECRpOQ,7825
5
+ gpstime/test_gpstime.py,sha256=6J8BT9IDOYwY08Qi2jtNnkp3S2p22c_dKiOxaw3Q0zg,17500
6
+ gpstime-0.10.0.dist-info/licenses/COPYING,sha256=kD3Y57xS042kgHR4KTOzNOiOHLxiOtxwe2gomX6l0cM,1724
7
+ gpstime-0.10.0.dist-info/licenses/COPYING-GPL-3,sha256=_ILKi2_bGNTj6Fz9irWNG80_Gymr54KJWr2R1kdj-Oc,35068
8
+ gpstime-0.10.0.dist-info/METADATA,sha256=NX6lMABPw_wSBB8V5wEmxzkoZ1wZCdph2BzYxLN_IeU,1538
9
+ gpstime-0.10.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
+ gpstime-0.10.0.dist-info/entry_points.txt,sha256=zwYfq0iBds4lr6t_UvZM8jfJc4Y5hiXC3_xm3gu-IpA,50
11
+ gpstime-0.10.0.dist-info/top_level.txt,sha256=LJlZJoesrJMc-u-T2IuSXd6udNzp1TaVD6aJa5Zwj54,8
12
+ gpstime-0.10.0.dist-info/RECORD,,
gpstime/test.py DELETED
@@ -1,197 +0,0 @@
1
- import datetime
2
- import unittest
3
- import shutil
4
- import sys
5
-
6
- from dateutil.tz import tzutc
7
-
8
- import gpstime
9
-
10
- ##################################################
11
-
12
- def cu_date_available():
13
- """return True if coreutils date parser available"""
14
- if shutil.which("date"):
15
- return True
16
- return False
17
-
18
-
19
- class TestGPStime(unittest.TestCase):
20
-
21
- def roundtrip_gps(self, gps):
22
- self.assertEqual(gpstime.unix2gps(gpstime.gps2unix(gps)), gps)
23
-
24
- def roundtrip_unix(self, unix):
25
- self.assertEqual(gpstime.gps2unix(gpstime.unix2gps(unix)), unix)
26
-
27
- ##########
28
-
29
- def test_conversion(self):
30
- self.assertEqual(gpstime.gps2unix(1133585676), 1449550459)
31
- self.assertEqual(gpstime.unix2gps(1449550459), 1133585676)
32
- self.assertEqual(1133585676, gpstime.unix2gps(gpstime.gps2unix(1133585676)))
33
-
34
- def test_conversion_past(self):
35
- self.assertEqual(gpstime.gps2unix(123456789), 439421586)
36
- self.assertEqual(gpstime.unix2gps(439421586), 123456789)
37
-
38
- def test_conversion_onleap(self):
39
- self.assertEqual(gpstime.gps2unix(425520006), 741484798)
40
- self.assertEqual(gpstime.gps2unix(425520007), 741484799)
41
- self.assertEqual(gpstime.gps2unix(425520008), 741484799)
42
- self.assertEqual(gpstime.gps2unix(425520009), 741484800)
43
- self.assertEqual(gpstime.unix2gps(741484798), 425520006)
44
- self.assertEqual(gpstime.unix2gps(741484799), 425520007)
45
- self.assertEqual(gpstime.unix2gps(741484800), 425520009)
46
-
47
- def test_roundtrip(self):
48
- self.roundtrip_gps(123456789)
49
- self.roundtrip_unix(439421586)
50
-
51
- @unittest.expectedFailure
52
- def test_roundtrip_onleap(self):
53
- self.roundtrip_gps(425520008)
54
-
55
- def test_gpstime_new(self):
56
- self.assertEqual(gpstime.gpstime(2015, 12, 8, 4, 54, 19, 0, tzutc()).gps(),
57
- 1133585676.0)
58
-
59
- def test_gpstime_fromdatetime(self):
60
- dt = datetime.datetime(2015, 12, 8, 4, 54, 19, 0, tzutc())
61
- gt = gpstime.gpstime.fromdatetime(dt)
62
- self.assertIsInstance(gt, gpstime.gpstime)
63
- self.assertEqual(gt.gps(), 1133585676.0)
64
-
65
- def test_gpstime_parse_now_roundtrip(self):
66
- gt0 = gpstime.gpstime.parse()
67
- gt1 = gpstime.gpstime.parse(gt0.gps())
68
- self.assertEqual(gt0, gt1)
69
-
70
- def test_gpstime_parse_utc(self):
71
- self.assertEqual(gpstime.gpstime.parse('Dec 08 2015 04:54:19.2 UTC').gps(),
72
- 1133585676.2)
73
-
74
- def test_gpstime_parse_utc2(self):
75
- self.assertEqual(gpstime.gpstime.parse('2014-07-03 17:16:14 UTC').gps(),
76
- 1088442990.0)
77
-
78
- def test_gpstime_parse_local(self):
79
- self.assertEqual(gpstime.gpstime.parse('Dec 08 2015 04:54:19.2 PST').gps(),
80
- 1133614476.2)
81
-
82
- def test_gpstime_parse_gps(self):
83
- self.assertEqual(gpstime.gpstime.parse(1133585676.2).gps(),
84
- 1133585676.2)
85
-
86
- def test_gpstime_parse_gps_iso(self):
87
- self.assertEqual(gpstime.gpstime.parse(1133585676.2).iso(),
88
- '2015-12-08T04:54:19.200000Z')
89
-
90
- def test_gpstime_parse_iso(self):
91
- self.assertEqual(gpstime.gpstime.parse('2015-12-08T04:54:19.200000Z').iso(),
92
- '2015-12-08T04:54:19.200000Z')
93
-
94
- def test_gpstime_parse_iso_alt(self):
95
- self.assertEqual(gpstime.gpstime.parse('2015-12-08_04:54:19.200000Z').iso(),
96
- '2015-12-08T04:54:19.200000Z')
97
-
98
- @unittest.skipIf(not cu_date_available(), "coreutils date not available")
99
- def test_gpstime_parse_timestamp(self):
100
- self.assertEqual(gpstime.gpstime.parse('@1474821047').gps(),
101
- 1158856264)
102
-
103
- @unittest.skipIf(not cu_date_available(), "coreutils date not available")
104
- def test_gpstime_parse_now0(self):
105
- self.assertIsInstance(gpstime.gpstime.parse(), gpstime.gpstime)
106
-
107
- @unittest.skipIf(not cu_date_available(), "coreutils date not available")
108
- def test_gpstime_parse_now1(self):
109
- self.assertIsInstance(gpstime.gpstime.parse('now'), gpstime.gpstime)
110
-
111
- @unittest.skipIf(not cu_date_available(), "coreutils date not available")
112
- def test_gpstime_parse_relative0(self):
113
- self.assertIsInstance(gpstime.gpstime.parse('yesterday'), gpstime.gpstime)
114
-
115
- @unittest.skipIf(not cu_date_available(), "coreutils date not available")
116
- def test_gpstime_parse_relative1(self):
117
- self.assertIsInstance(gpstime.gpstime.parse('1 month ago'), gpstime.gpstime)
118
-
119
- def test_gpstime_parse_tconvert(self):
120
- self.assertEqual(gpstime.gpstime.tconvert('2015-12-08T04:54:19.200000Z'),
121
- gpstime.gpstime.tconvert(1133585676.2))
122
-
123
- def test_gpstime_fromgps(self):
124
- self.assertEqual(gpstime.gpstime.fromgps(1133585676).iso(),
125
- '2015-12-08T04:54:19.000000Z')
126
-
127
- def test_gpstime_fromgps_rounding(self):
128
- self.assertEqual(gpstime.gpstime.fromgps(1133585676.274).iso(),
129
- '2015-12-08T04:54:19.274000Z')
130
- self.assertEqual(gpstime.gpstime.fromgps(1133585676.874).iso(),
131
- '2015-12-08T04:54:19.874000Z')
132
-
133
- @unittest.skipIf(not cu_date_available(), "coreutils date not available")
134
- def test_non_standard_iso_format(self):
135
- """ Tests whether dates with '/' separators are parsed as iso dates.
136
- Core utils instead parses them as if the 'T' character meant
137
- a military time zone.
138
- """
139
-
140
- # both date utils and core utils will parse this using the default timezone
141
- self.assertEqual(gpstime.gpstime.parse('2025-02-08T04:54:19'),
142
-
143
- # date utils will also parse this using the default time zone
144
- # but core utils will parse this using the military timezone 'T'
145
- gpstime.gpstime.parse('2025/02/08T04:54:19'))
146
-
147
-
148
- def test_gpstime_fromgps_timestamp(self):
149
- self.assertEqual(gpstime.gpstime.fromgps(1133585676).timestamp(),
150
- 1449550459)
151
-
152
- def test_gpstime_fromgpsns_timestamp(self):
153
- self.assertEqual(gpstime.gpstime.fromgpsns(1133585676200000000).gps(),
154
- 1133585676.2)
155
-
156
- def test_gpstime_tconvert_classmethod(self):
157
- self.assertEqual(gpstime.gpstime.tconvert(1133585676.2).gps(),
158
- 1133585676.2)
159
-
160
- def test_gpstime_tconvert_iso(self):
161
- self.assertEqual(gpstime.tconvert('2015-12-08T04:54:19.200000Z'),
162
- 1133585676.2)
163
-
164
- def test_gpstime_tconvert_gps(self):
165
- self.assertEqual(gpstime.tconvert(1133585676.2),
166
- '2015-12-08 04:54:19.200000 UTC')
167
-
168
- @unittest.expectedFailure
169
- def test_gpstime_parse_leap(self):
170
- self.assertEqual(gpstime.gpstime.parse('Jun 30 1993 23:59:60 UTC').gps(),
171
- 425520008)
172
-
173
- @unittest.expectedFailure
174
- def test_gpstime_parse_gps_leap(self):
175
- self.assertEqual(gpstime.gpstime.parse(425520008).iso(),
176
- '1993-06-30T23:59:60.000000Z')
177
-
178
- @unittest.skipIf(sys.version_info < (3, 8), "returns datetime in python < 3.8")
179
- def test_gpstime_add(self):
180
- dt = gpstime.gpstime.parse(1133585676.2)
181
- delta = datetime.timedelta(seconds=1)
182
- self.assertTrue(isinstance(dt + delta, gpstime.gpstime))
183
- dt += delta
184
- self.assertTrue(isinstance(dt, gpstime.gpstime))
185
-
186
- @unittest.skipIf(sys.version_info < (3, 8), "returns datetime in python < 3.8")
187
- def test_gpstime_subtract(self):
188
- dt = gpstime.gpstime.parse(1133585676.2)
189
- delta = datetime.timedelta(seconds=1)
190
- self.assertTrue(isinstance(dt - delta, gpstime.gpstime))
191
- dt -= delta
192
- self.assertTrue(isinstance(dt, gpstime.gpstime))
193
-
194
- ##################################################
195
-
196
- if __name__ == '__main__':
197
- unittest.main(verbosity=5, failfast=False, buffer=True)
@@ -1,12 +0,0 @@
1
- gpstime/__init__.py,sha256=yuB-PFRzkKYPQ_p9pk2kZlFk6l67y3v9QMrhBoMrRxw,7315
2
- gpstime/__main__.py,sha256=ndEEI2WAOkZzQrwQ7zJs0nbTsMTpIutGyoZR_Tr6DYE,2882
3
- gpstime/__version__.py,sha256=rfa0ApL1R5aaryILIPegRCDXm2F9QOapXvN9_pEOr2k,712
4
- gpstime/leaps.py,sha256=QduewuPZce_BpbB5zkMR4c_sZeATn1oD7XuPIECRpOQ,7825
5
- gpstime/test.py,sha256=0NZVr46dAuxn1gYUMJtU89z0Kd5bn8S7lJAUzPujbns,7845
6
- gpstime-0.9.2.dist-info/licenses/COPYING,sha256=kD3Y57xS042kgHR4KTOzNOiOHLxiOtxwe2gomX6l0cM,1724
7
- gpstime-0.9.2.dist-info/licenses/COPYING-GPL-3,sha256=_ILKi2_bGNTj6Fz9irWNG80_Gymr54KJWr2R1kdj-Oc,35068
8
- gpstime-0.9.2.dist-info/METADATA,sha256=VJk9bakwej2aG4aXXyAJRqxjixc3U4GAP0UnLF1a0OE,1477
9
- gpstime-0.9.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
- gpstime-0.9.2.dist-info/entry_points.txt,sha256=zwYfq0iBds4lr6t_UvZM8jfJc4Y5hiXC3_xm3gu-IpA,50
11
- gpstime-0.9.2.dist-info/top_level.txt,sha256=LJlZJoesrJMc-u-T2IuSXd6udNzp1TaVD6aJa5Zwj54,8
12
- gpstime-0.9.2.dist-info/RECORD,,