Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions petl/test/transform/test_normalize_timezone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import unittest
from petl.transform.normalize_timezone import normalize_timezone

class TestNormalizeTimezone(unittest.TestCase):

def test_basic_conversion(self):
input_data = [
{'timestamp': '2023-12-01T10:00:00', 'timezone': 'America/New_York'},
{'timestamp': '2023-12-01T15:00:00', 'timezone': 'Europe/London'}
]
result = list(normalize_timezone(input_data))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like python3.6 doesn't work with this:

  _________________ TestNormalizeTimezone.test_basic_conversion __________________
  
  table = [{'timestamp': '2023-12-01T10:00:00', 'timezone': 'America/New_York'}, {'timestamp': '2023-12-01T15:00:00', 'timezone': 'Europe/London'}]
  timestamp_col = 'timestamp', tz_col = 'timezone'
  
      def normalize_timezone(table, timestamp_col='timestamp', tz_col='timezone'):
          """
          Normalize timestamps to UTC while retaining original timezone.
      
          Args:
              table: petl table (iterable of rows/dicts)
              timestamp_col (str): column name with timestamp strings
              tz_col (str): column name with timezone name (e.g., 'America/New_York')
      
          Yields:
              Each row with two added fields: 'timestamp_utc' and 'timezone_original'
          """
          for row in table:
              try:
                  original_ts = row[timestamp_col]
                  original_tz = row[tz_col]
      
                  # Parse the timestamp
  >               naive_dt = datetime.fromisoformat(original_ts)
  E               AttributeError: type object 'datetime.datetime' has no attribute 'fromisoformat'
  
  petl/transform/normalize_timezone.py:22: AttributeError
  
  During handling of the above exception, another exception occurred:
  
  self = <petl.test.transform.test_normalize_timezone.TestNormalizeTimezone testMethod=test_basic_conversion>
  
      def test_basic_conversion(self):
          input_data = [
              {'timestamp': '2023-12-01T10:00:00', 'timezone': 'America/New_York'},
              {'timestamp': '2023-12-01T15:00:00', 'timezone': 'Europe/London'}
          ]
  >       result = list(normalize_timezone(input_data))
  
  petl/test/transform/test_normalize_timezone.py:11: 
  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
...

Can you rework this test to be skipped when python <= 3.6, please?

self.assertEqual(result[0]['timestamp_utc'], '2023-12-01T15:00:00+00:00')
self.assertEqual(result[1]['timestamp_utc'], '2023-12-01T15:00:00+00:00')
self.assertEqual(result[0]['timezone_original'], 'America/New_York')

def test_invalid_timezone(self):
input_data = [{'timestamp': '2023-12-01T10:00:00', 'timezone': 'Invalid/Zone'}]
with self.assertRaises(ValueError):
list(normalize_timezone(input_data))

def test_missing_timestamp(self):
input_data = [{'timezone': 'UTC'}]
with self.assertRaises(ValueError):
list(normalize_timezone(input_data))

if __name__ == '__main__':
unittest.main()
38 changes: 38 additions & 0 deletions petl/transform/normalize_timezone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from datetime import datetime
import pytz
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, pytz is not a hard requirement when using petl.

Due to this, the CI jobs running on windows are failing with the following error:

=================================== ERRORS ====================================
_______ ERROR collecting petl/test/transform/test_normalize_timezone.py _______
ImportError while importing test module 'D:\a\petl\petl\petl\test\transform\test_normalize_timezone.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
C:\hostedtoolcache\windows\Python\3.6.8\x64\lib\importlib\__init__.py:126: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
petl\test\transform\test_normalize_timezone.py:2: in <module>
    from petl.transform.normalize_timezone import normalize_timezone
petl\transform\normalize_timezone.py:2: in <module>
    import pytz
E   ModuleNotFoundError: No module named 'pytz'

Would you mind making the pytz import to be called only when explicitly using this functionality?


def normalize_timezone(table, timestamp_col='timestamp', tz_col='timezone'):
"""
Normalize timestamps to UTC while retaining original timezone.

Args:
table: petl table (iterable of rows/dicts)
timestamp_col (str): column name with timestamp strings
tz_col (str): column name with timezone name (e.g., 'America/New_York')

Yields:
Each row with two added fields: 'timestamp_utc' and 'timezone_original'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a code example here would be interesting, but not required.

"""
for row in table:
try:
original_ts = row[timestamp_col]
original_tz = row[tz_col]

# Parse the timestamp
naive_dt = datetime.fromisoformat(original_ts)

Check warning

Code scanning / Pylint (reported by Codacy)

Class 'datetime' has no 'fromisoformat' member Warning

Class 'datetime' has no 'fromisoformat' member

# Attach original timezone
local_dt = pytz.timezone(original_tz).localize(naive_dt)

# Convert to UTC
utc_dt = local_dt.astimezone(pytz.UTC)

# Create a new row with original + new fields
new_row = dict(row)
new_row['timestamp_utc'] = utc_dt.isoformat()
new_row['timezone_original'] = original_tz

yield new_row

except Exception as e:
raise ValueError(f"Failed to normalize row {row} due to error: {e}")
Loading