# 🎨 Formatting & Serialization

Eones includes specialized utilities for formatting and serialization of `Date` and `Delta` objects.

## Date Formatting

### Basic Formatting

```python
from eones.formats import format_date
from eones.date import Date

# Basic formatting
d = Date(2024, 6, 15)
print(format_date(d))  # "2024-06-15" (default format)
print(format_date(d, "%d/%m/%Y"))  # "15/06/2024"
```

### Formatting with Timezone

```python
# Formatting with timezone
d_madrid = Date(2024, 6, 15, tz="Europe/Madrid")
print(format_date(d_madrid, "%Y-%m-%d %H:%M %Z"))  # "2024-06-15 00:00 CEST"

# Used internally by Date.to_string()
print(d.to_string("%A, %B %d, %Y"))  # Uses format_date internally
```

## Duration Serialization

### ISO 8601 Serialization

```python
from eones.formats import format_duration
from eones.delta import Delta

# ISO 8601 serialization
delta = Delta(years=1, months=2, days=3, hours=4, minutes=30)
print(format_duration(delta))  # "P1Y2M3DT4H30M"
```

### Special Duration Cases

```python
# Special cases
delta_time_only = Delta(hours=2, minutes=15)
print(format_duration(delta_time_only))  # "PT2H15M"

delta_date_only = Delta(years=1, days=10)
print(format_duration(delta_date_only))  # "P1Y10D"

# Used internally by Delta.to_iso()
print(delta.to_iso())  # Uses format_duration internally
```

## Common Use Cases

### Serialization for REST APIs

```python
def date_for_api(date):
    """Converts a date for use in REST APIs."""
    return {
        "date": format_date(date, "%Y-%m-%d"),
        "readable_date": format_date(date, "%B %d, %Y"),
        "timestamp": date.timestamp()
    }

# Usage example
date = Date(2024, 12, 25)
api_data = date_for_api(date)
print(api_data)
# {
#     "date": "2024-12-25",
#     "readable_date": "December 25, 2024",
#     "timestamp": 1735084800.0
# }
```

### Database Storage

```python
def save_event(name, date, duration):
    """Prepares event data for database storage."""
    return {
        "name": name,
        "date_iso": format_date(date, "%Y-%m-%dT%H:%M:%S%z"),
        "duration_iso": format_duration(duration)
    }

# Usage example
event_date = Date(2024, 6, 15, 14, 30)
event_duration = Delta(hours=2, minutes=30)

event_data = save_event("Team meeting", event_date, event_duration)
print(event_data)
# {
#     "name": "Team meeting",
#     "date_iso": "2024-06-15T14:30:00+00:00",
#     "duration_iso": "PT2H30M"
# }
```

### Structured Logging

```python
import logging

def log_event(event, date):
    """Logs an event with readable date format."""
    formatted_date = format_date(date, '%Y-%m-%d %H:%M')
    logging.info(f"Event '{event}' scheduled for {formatted_date}")

# Usage example
log_date = Date(2024, 6, 15, 10, 30)
log_event("Automatic backup", log_date)
# INFO:root:Event 'Automatic backup' scheduled for 2024-06-15 10:30
```

### JSON Export

```python
import json
from datetime import datetime

class EonesJSONEncoder(json.JSONEncoder):
    """Custom JSON encoder for Eones objects."""
    
    def default(self, obj):
        if isinstance(obj, Date):
            return {
                "_type": "Date",
                "iso": format_date(obj, "%Y-%m-%dT%H:%M:%S%z"),
                "readable": format_date(obj, "%B %d, %Y at %H:%M")
            }
        elif isinstance(obj, Delta):
            return {
                "_type": "Delta",
                "iso": format_duration(obj),
                "components": {
                    "years": obj.years,
                    "months": obj.months,
                    "days": obj.days,
                    "hours": obj.hours,
                    "minutes": obj.minutes,
                    "seconds": obj.seconds
                }
            }
        return super().default(obj)

# Usage example
data = {
    "event": "Conference",
    "date": Date(2024, 9, 15, 9, 0),
    "duration": Delta(hours=8, minutes=30)
}

json_str = json.dumps(data, cls=EonesJSONEncoder, indent=2)
print(json_str)
```
 
### Native JSON Serialization
 
Eones `Date` and `Delta` objects include a native `.for_json()` method that returns strings compatible with standard JSON encoders.
 
```python
# Native .for_json() support
date = Date(2023, 1, 1, naive="utc")
print(date.for_json())  # "2023-01-01T00:00:00+00:00"
 
delta = Delta(days=1, hours=2)
print(delta.for_json())  # "P1DT2H"
```
 
You can use this in your own encoders to easily serialize Eones objects:
 
```python
def eones_json_serializer(obj):
    if hasattr(obj, 'for_json'):
        return obj.for_json()
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
 
# Usage
data = {"due": Date(2024, 12, 25), "duration": Delta(weeks=1)}
print(json.dumps(data, default=eones_json_serializer))
```

### JSON Import

```python
def decode_eones_json(dct):
    """Decodes Eones objects from JSON."""
    if "_type" in dct:
        if dct["_type"] == "Date":
            return Date.from_string(dct["iso"])
        elif dct["_type"] == "Delta":
            return Delta.from_iso(dct["iso"])
    return dct

# Usage example
json_data = '''
{
  "event": "Conference",
  "date": {
    "_type": "Date",
    "iso": "2024-09-15T09:00:00+00:00"
  },
  "duration": {
    "_type": "Delta",
    "iso": "PT8H30M"
  }
}
'''

data = json.loads(json_data, object_hook=decode_eones_json)
print(f"Event: {data['event']}")
print(f"Date: {data['date'].format('%d/%m/%Y %H:%M')}")
print(f"Duration: {data['duration'].total_seconds() / 3600} hours")
```

### Locale-Specific Formatting

```python
def format_date_locale(date, locale="en"):
    """Formats date according to specified locale."""
    formats = {
        "es": "%d de %B de %Y",
        "en": "%B %d, %Y",
        "fr": "%d %B %Y",
        "de": "%d. %B %Y"
    }
    
    format_str = formats.get(locale, formats["en"])
    return format_date(date, format_str)

# Usage example
date = Date(2024, 6, 15)
print(format_date_locale(date, "es"))  # "15 de June de 2024"
print(format_date_locale(date, "en"))  # "June 15, 2024"
print(format_date_locale(date, "fr"))  # "15 June 2024"
```

### Format Validation

```python
def validate_date_format(date_str, format_str):
    """Validates if a date string matches the expected format."""
    try:
        date = Date.from_string(date_str)
        formatted_date = format_date(date, format_str)
        return date_str == formatted_date
    except Exception:
        return False

# Usage example
print(validate_date_format("2024-06-15", "%Y-%m-%d"))  # True
print(validate_date_format("15/06/2024", "%Y-%m-%d"))  # False
```

## ISO 8601 Parsing with Timezone Offsets

Eones provides comprehensive support for parsing ISO 8601 formatted strings with timezone offsets, both through the `Date.from_iso()` method and the `Parser` class.

### Using Date.from_iso()

```python
from eones import Date

# Basic ISO 8601 with UTC
date1 = Date.from_iso("2024-01-15T10:30:00Z")
print(date1)  # 2024-01-15T10:30:00+00:00

# ISO 8601 with positive offset
date2 = Date.from_iso("2024-01-15T10:30:00+03:00")
print(date2)  # 2024-01-15T10:30:00+03:00
print(date2.timezone)  # UTC+03:00

# ISO 8601 with negative offset
date3 = Date.from_iso("2024-01-15T10:30:00-05:00")
print(date3)  # 2024-01-15T10:30:00-05:00
print(date3.timezone)  # UTC-05:00

# ISO 8601 with microseconds and offset
date4 = Date.from_iso("2024-01-15T10:30:00.123456+02:30")
print(date4)  # 2024-01-15T10:30:00.123456+02:30
```

### Supported Offset Formats

```python
# Various offset formats are supported
formats = [
    "2024-01-15T10:30:00Z",           # UTC (Zulu time)
    "2024-01-15T10:30:00+00:00",      # UTC with explicit offset
    "2024-01-15T10:30:00+03:00",      # Positive offset with colon
    "2024-01-15T10:30:00-05:00",      # Negative offset with colon
    "2024-01-15T10:30:00+0300",       # Positive offset without colon
    "2024-01-15T10:30:00-0500",       # Negative offset without colon
    "2024-01-15T10:30:00.123+01:00",  # With milliseconds
    "2024-01-15T10:30:00.123456-02:00" # With microseconds
]

for fmt in formats:
    date = Date.from_iso(fmt)
    print(f"{fmt:<35} -> {date} (TZ: {date.timezone})")
```

### Using Parser with ISO 8601 Formats

```python
from eones import Parser

# Create a parser (default formats now include ISO 8601 with offsets)
parser = Parser()

# Parse various ISO 8601 formats
iso_strings = [
    "2024-01-15T10:30:00Z",
    "2024-01-15T10:30:00+03:00",
    "2024-01-15T10:30:00.123456-05:00"
]

for iso_str in iso_strings:
    date = parser.parse(iso_str)
    print(f"Parsed: {date} (Timezone: {date.timezone})")
```

### Custom Timezone for Naive ISO Strings

```python
# When parsing naive ISO strings, you can specify a default timezone
naive_iso = "2024-01-15T10:30:00"

# Using Date.from_iso() with custom timezone
date_ny = Date.from_iso(naive_iso, tz="America/New_York")
print(f"New York: {date_ny} (TZ: {date_ny.timezone})")

# Using Parser with custom default timezone
parser_tokyo = Parser(tz="Asia/Tokyo")
date_tokyo = parser_tokyo.parse(naive_iso)
print(f"Tokyo: {date_tokyo} (TZ: {date_tokyo.timezone})")
```

### Timezone Preservation

```python
# When an ISO string contains timezone information, it's preserved
# even if you specify a different default timezone

iso_with_offset = "2024-01-15T10:30:00+05:30"

# The +05:30 offset is preserved, not overridden by America/New_York
date = Date.from_iso(iso_with_offset, tz="America/New_York")
print(f"Original offset preserved: {date} (TZ: {date.timezone})")
# Output: 2024-01-15T10:30:00+05:30 (TZ: UTC+05:30)
```

### Error Handling for Invalid Offsets

```python
from eones.errors import InvalidFormatError

# Invalid offset formats will raise InvalidFormatError
invalid_formats = [
    "2024-01-15T10:30:00+25:00",  # Invalid hour offset
    "2024-01-15T10:30:00+03:70",  # Invalid minute offset
    "2024-01-15T10:30:00+abc",    # Non-numeric offset
]

for invalid_fmt in invalid_formats:
    try:
        date = Date.from_iso(invalid_fmt)
    except InvalidFormatError as e:
        print(f"Invalid format '{invalid_fmt}': {e}")
```