diff --git a/Whatsapp_Chat_Exporter/data_model.py b/Whatsapp_Chat_Exporter/data_model.py index aa63c9c..c0c4f88 100644 --- a/Whatsapp_Chat_Exporter/data_model.py +++ b/Whatsapp_Chat_Exporter/data_model.py @@ -8,14 +8,14 @@ class Timing: Handles timestamp formatting with timezone support. """ - def __init__(self, timezone_offset: Optional[int]) -> None: + def __init__(self, timezone_offset: Optional[Union[int, float]] = None) -> None: """ Initialize Timing object. Args: - timezone_offset (Optional[int]): Hours offset from UTC + timezone_offset (Optional[Union[int, float]]): Hours offset from UTC. Defaults to None (auto-detect). """ - self.timezone_offset = timezone_offset + self.tz = TimeZone(timezone_offset) if timezone_offset is not None else None def format_timestamp(self, timestamp: Optional[Union[int, float]], format: str) -> Optional[str]: """ @@ -30,7 +30,7 @@ class Timing: """ if timestamp is not None: timestamp = timestamp / 1000 if timestamp > 9999999999 else timestamp - return datetime.fromtimestamp(timestamp, TimeZone(self.timezone_offset)).strftime(format) + return datetime.fromtimestamp(timestamp, self.tz).strftime(format) return None @@ -39,12 +39,12 @@ class TimeZone(tzinfo): Custom timezone class with fixed offset. """ - def __init__(self, offset: int) -> None: + def __init__(self, offset: Union[int, float]) -> None: """ Initialize TimeZone object. Args: - offset (int): Hours offset from UTC + offset (Union[int, float]): Hours offset from UTC """ self.offset = offset diff --git a/tests/test_data_model.py b/tests/test_data_model.py new file mode 100644 index 0000000..7ad56a4 --- /dev/null +++ b/tests/test_data_model.py @@ -0,0 +1,55 @@ +import pytest +from Whatsapp_Chat_Exporter.data_model import TimeZone, Timing +from datetime import timedelta + + +class TestTimeZone: + def test_utcoffset(self): + tz = TimeZone(5.5) + assert tz.utcoffset(None) == timedelta(hours=5.5) + + def test_dst(self): + tz = TimeZone(2) + assert tz.dst(None) == timedelta(0) + + +class TestTiming: + @pytest.mark.parametrize("offset, expected_hour", [ + (8, "08:00"), # Integer (e.g., Hong Kong Standard Time) + (-8, "16:00"), # Negative Integer (e.g., PST) + (5.5, "05:30"), # Positive Float (e.g., IST) + (-3.5, "20:30"), # Negative Float (e.g., Newfoundland) + ]) + + def test_format_timestamp_various_offsets(self, offset, expected_hour): + """Verify that both int and float offsets calculate time correctly.""" + t = Timing(offset) + result = t.format_timestamp(1672531200, "%H:%M") + assert result == expected_hour + + @pytest.mark.parametrize("ts_input", [ + 1672531200, # Unix timestamp as int + 1672531200.0, # Unix timestamp as float + ]) + + def test_timestamp_input_types(self, ts_input): + """Verify the method accepts both int and float timestamps.""" + t = Timing(0) + result = t.format_timestamp(ts_input, "%Y") + assert result == "2023" + + def test_timing_none_offset(self): + """Verify initialization with None doesn't crash and uses system time.""" + t = Timing(None) + assert t.tz is None + # Should still return a valid string based on local machine time without crashing + result = t.format_timestamp(1672531200, "%Y") + assert result == "2023" + + def test_millisecond_scaling(self): + """Verify that timestamps in milliseconds are correctly scaled down.""" + t = Timing(0) + # Milliseconds as int + assert t.format_timestamp(1672531200000, "%Y") == "2023" + # Milliseconds as float + assert t.format_timestamp(1672531200000.0, "%Y") == "2023"