# Path: fimfarchive/stories.py
# class Story:
#     """
#     Represents a story.
#     """
# 
#     def __init__(
#             self,
#             key: int,
#             fetcher: Fetcher = None,
#             meta: Dict[str, Any] = None,
#             data: bytes = None,
#             flavors: Iterable[Flavor] = (),
#             ) -> None:
#         """
#         Constructor.
# 
#         Args:
#             key: Primary key of the story.
#             fetcher: Fetcher to use for lazy fetching.
#             meta: Meta to populate the story with.
#             data: Data to populate the story with.
#             flavors: Content type hints.
#         """
#         if fetcher is None and (data is None or meta is None):
#             raise ValueError("Story must contain fetcher if lazy.")
# 
#         self.key = key
#         self.fetcher = fetcher
#         self.flavors = set(flavors)
#         self._meta = meta
#         self._data = data
# 
#     @property
#     def is_fetched(self) -> bool:
#         """
#         True if no more fetches are necessary.
#         """
#         return self.has_meta and self.has_data
# 
#     @property
#     def has_meta(self) -> bool:
#         """
#         True if story meta has been fetched.
#         """
#         return self._meta is not None
# 
#     @property
#     def meta(self) -> Dict[str, Any]:
#         """
#         Returns the story meta.
# 
#         Meta may be fetched if this story instance is lazy.
# 
#         Raises:
#             InvalidStoryError: If a valid story is not found.
#             StorySourceError: If source does not return any data.
#         """
#         if self._meta is None and self.fetcher:
#             self._meta = self.fetcher.fetch_meta(self.key)
# 
#         if self._meta is None:
#             raise StorySourceError("Meta is missing.")
# 
#         return self._meta
# 
#     @property
#     def has_data(self) -> bool:
#         """
#         True if story data has been fetched.
#         """
#         return self._data is not None
# 
#     @property
#     def data(self) -> bytes:
#         """
#         Returns the story data.
# 
#         Data may be fetched if this story instance is lazy.
# 
#         Raises:
#             InvalidStoryError: If a valid story is not found.
#             StorySourceError: If source does not return any data.
#         """
#         if self._data is None and self.fetcher:
#             self._data = self.fetcher.fetch_data(self.key)
# 
#         if self._data is None:
#             raise StorySourceError("Data is missing.")
# 
#         return self._data
# 
#     def merge(self, **params) -> 'Story':
#         """
#         Returns a shallow copy, optionally replacing attributes.
# 
#         Args:
#             **params: Overrides parameters from the current instance.
# 
#         Raises:
#             TypeError: If passed an unexpected parameter.
#         """
#         kwargs = {k.lstrip('_'): v for k, v in vars(self).items()}
#         kwargs.update(params)
# 
#         return type(self)(**kwargs)
# 
# Path: fimfarchive/utils.py
# class JayWalker:
#     """
#     Walker for JSON objects.
#     """
# 
#     def walk(self, data) -> None:
#         """
#         Walks the attributes of a JSON object.
# 
#         Args:
#             data: The object to walk.
#         """
#         iterator: Iterator
# 
#         if isinstance(data, dict):
#             iterator = iter(data.items())
#         elif isinstance(data, list):
#             iterator = enumerate(data)
#         else:
#             return
# 
#         for key, value in iterator:
#             self.handle(data, key, value)
# 
#     def handle(self, data, key, value) -> None:
#         """
#         Handles a single JSON entry.
# 
#         Args:
#             data: The current object.
#             key: The key of the entry.
#             value: The value of the entry.
#         """
#         self.walk(value)
# 
# Path: fimfarchive/converters/base.py
# class Converter(ABC):
#     """
#     Converter for stories.
#     """
# 
#     @abstractmethod
#     def __call__(self, story: Story) -> Story:
#         """
#         Applies the conversion.
# 
#         Args:
#             *story: The story to convert.
# 
#         Returns:
#             A converted story.
#         """


from copy import deepcopy
from typing import Any, Optional
from fimfarchive.stories import Story
from fimfarchive.utils import JayWalker
from .base import Converter
import arrow
    """
    Normalizes timezones of date values to UTC.
    """

    def handle(self, data, key, value) -> None:
        if str(key).startswith('date_'):
            data[key] = self.normalize(value)
        else:
            self.walk(value)

    def normalize(self, value: Any) -> Optional[str]:
        """
        Normalizes a single date value.
        """
        parsed = arrow.get(value or 0)

        if parsed.int_timestamp == 0:
            return None

        return parsed.to('utc').isoformat()


class LocalUtcConverter(Converter):
    """
    Converts date strings to UTC.
    """

    def __init__(self) -> None:
        self.normalizer = DateNormalizer()


# Path: fimfarchive/stories.py
# class Story:
#     """
#     Represents a story.
#     """
# 
#     def __init__(
#             self,
#             key: int,
#             fetcher: Fetcher = None,
#             meta: Dict[str, Any] = None,
#             data: bytes = None,
#             flavors: Iterable[Flavor] = (),
#             ) -> None:
#         """
#         Constructor.
# 
#         Args:
#             key: Primary key of the story.
#             fetcher: Fetcher to use for lazy fetching.
#             meta: Meta to populate the story with.
#             data: Data to populate the story with.
#             flavors: Content type hints.
#         """
#         if fetcher is None and (data is None or meta is None):
#             raise ValueError("Story must contain fetcher if lazy.")
# 
#         self.key = key
#         self.fetcher = fetcher
#         self.flavors = set(flavors)
#         self._meta = meta
#         self._data = data
# 
#     @property
#     def is_fetched(self) -> bool:
#         """
#         True if no more fetches are necessary.
#         """
#         return self.has_meta and self.has_data
# 
#     @property
#     def has_meta(self) -> bool:
#         """
#         True if story meta has been fetched.
#         """
#         return self._meta is not None
# 
#     @property
#     def meta(self) -> Dict[str, Any]:
#         """
#         Returns the story meta.
# 
#         Meta may be fetched if this story instance is lazy.
# 
#         Raises:
#             InvalidStoryError: If a valid story is not found.
#             StorySourceError: If source does not return any data.
#         """
#         if self._meta is None and self.fetcher:
#             self._meta = self.fetcher.fetch_meta(self.key)
# 
#         if self._meta is None:
#             raise StorySourceError("Meta is missing.")
# 
#         return self._meta
# 
#     @property
#     def has_data(self) -> bool:
#         """
#         True if story data has been fetched.
#         """
#         return self._data is not None
# 
#     @property
#     def data(self) -> bytes:
#         """
#         Returns the story data.
# 
#         Data may be fetched if this story instance is lazy.
# 
#         Raises:
#             InvalidStoryError: If a valid story is not found.
#             StorySourceError: If source does not return any data.
#         """
#         if self._data is None and self.fetcher:
#             self._data = self.fetcher.fetch_data(self.key)
# 
#         if self._data is None:
#             raise StorySourceError("Data is missing.")
# 
#         return self._data
# 
#     def merge(self, **params) -> 'Story':
#         """
#         Returns a shallow copy, optionally replacing attributes.
# 
#         Args:
#             **params: Overrides parameters from the current instance.
# 
#         Raises:
#             TypeError: If passed an unexpected parameter.
#         """
#         kwargs = {k.lstrip('_'): v for k, v in vars(self).items()}
#         kwargs.update(params)
# 
#         return type(self)(**kwargs)
# 
# Path: fimfarchive/utils.py
# class JayWalker:
#     """
#     Walker for JSON objects.
#     """
# 
#     def walk(self, data) -> None:
#         """
#         Walks the attributes of a JSON object.
# 
#         Args:
#             data: The object to walk.
#         """
#         iterator: Iterator
# 
#         if isinstance(data, dict):
#             iterator = iter(data.items())
#         elif isinstance(data, list):
#             iterator = enumerate(data)
#         else:
#             return
# 
#         for key, value in iterator:
#             self.handle(data, key, value)
# 
#     def handle(self, data, key, value) -> None:
#         """
#         Handles a single JSON entry.
# 
#         Args:
#             data: The current object.
#             key: The key of the entry.
#             value: The value of the entry.
#         """
#         self.walk(value)
# 
# Path: fimfarchive/converters/base.py
# class Converter(ABC):
#     """
#     Converter for stories.
#     """
# 
#     @abstractmethod
#     def __call__(self, story: Story) -> Story:
#         """
#         Applies the conversion.
# 
#         Args:
#             *story: The story to convert.
# 
#         Returns:
#             A converted story.
#         """


# Path: fimfarchive/converters/local_utc.py
from copy import deepcopy
from typing import Any, Optional
from fimfarchive.stories import Story
from fimfarchive.utils import JayWalker
from .base import Converter
import arrow

    """
    Normalizes timezones of date values to UTC.
    """

    def handle(self, data, key, value) -> None:
        if str(key).startswith('date_'):
            data[key] = self.normalize(value)
        else:
            self.walk(value)

    def normalize(self, value: Any) -> Optional[str]:
        """
        Normalizes a single date value.
        """
        parsed = arrow.get(value or 0)

        if parsed.int_timestamp == 0:
            return None

        return parsed.to('utc').isoformat()


class LocalUtcConverter(Converter):
    """
    Converts date strings to UTC.
    """

    def __init__(self) -> None:
        self.normalizer = DateNormalizer()


    def __call__(self, story: Story) -> Story: