Skip to content

Offsets

Jalali calendar frequency offsets.

JalaliMonthBegin

Bases: JalaliOffset

Offset to the beginning of a Jalali month.

Source code in jalali_pandas/offsets/month.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class JalaliMonthBegin(JalaliOffset):
    """Offset to the beginning of a Jalali month."""

    _prefix = "JMS"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add months to timestamp, landing on month start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Calculate target month
        total_months = other.year * 12 + other.month - 1 + self._n
        new_year = total_months // 12
        new_month = total_months % 12 + 1

        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=1,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract months from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next month start if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self.__add__(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous month start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt
        return JalaliTimestamp(
            year=dt.year,
            month=dt.month,
            day=1,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on month start."""
        return dt.day == 1

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add months to timestamp, landing on month start.

Source code in jalali_pandas/offsets/month.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add months to timestamp, landing on month start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Calculate target month
    total_months = other.year * 12 + other.month - 1 + self._n
    new_year = total_months // 12
    new_month = total_months % 12 + 1

    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=1,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract months from timestamp.

Source code in jalali_pandas/offsets/month.py
43
44
45
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract months from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on month start.

Source code in jalali_pandas/offsets/month.py
69
70
71
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on month start."""
    return dt.day == 1

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous month start.

Source code in jalali_pandas/offsets/month.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous month start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt
    return JalaliTimestamp(
        year=dt.year,
        month=dt.month,
        day=1,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next month start if not already on one.

Source code in jalali_pandas/offsets/month.py
47
48
49
50
51
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next month start if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self.__add__(dt)

JalaliMonthEnd

Bases: JalaliOffset

Offset to the end of a Jalali month.

Source code in jalali_pandas/offsets/month.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
class JalaliMonthEnd(JalaliOffset):
    """Offset to the end of a Jalali month."""

    _prefix = "JME"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add months to timestamp, landing on month end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Calculate target month
        total_months = other.year * 12 + other.month - 1 + self._n
        new_year = total_months // 12
        new_month = total_months % 12 + 1
        new_day = days_in_month(new_year, new_month)

        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=new_day,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract months from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next month end if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self._get_month_end(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous month end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        # Go to previous month's end
        if dt.month == 1:
            new_year = dt.year - 1
            new_month = 12
        else:
            new_year = dt.year
            new_month = dt.month - 1

        new_day = days_in_month(new_year, new_month)
        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=new_day,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on month end."""
        return dt.day == days_in_month(dt.year, dt.month)

    def _get_month_end(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Get the end of the current month."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        return JalaliTimestamp(
            year=dt.year,
            month=dt.month,
            day=days_in_month(dt.year, dt.month),
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add months to timestamp, landing on month end.

Source code in jalali_pandas/offsets/month.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add months to timestamp, landing on month end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Calculate target month
    total_months = other.year * 12 + other.month - 1 + self._n
    new_year = total_months // 12
    new_month = total_months % 12 + 1
    new_day = days_in_month(new_year, new_month)

    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=new_day,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract months from timestamp.

Source code in jalali_pandas/offsets/month.py
104
105
106
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract months from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on month end.

Source code in jalali_pandas/offsets/month.py
140
141
142
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on month end."""
    return dt.day == days_in_month(dt.year, dt.month)

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous month end.

Source code in jalali_pandas/offsets/month.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous month end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    # Go to previous month's end
    if dt.month == 1:
        new_year = dt.year - 1
        new_month = 12
    else:
        new_year = dt.year
        new_month = dt.month - 1

    new_day = days_in_month(new_year, new_month)
    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=new_day,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next month end if not already on one.

Source code in jalali_pandas/offsets/month.py
108
109
110
111
112
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next month end if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self._get_month_end(dt)

JalaliOffset

Bases: ABC

Abstract base class for Jalali calendar-aware offsets.

This class provides the foundation for implementing calendar-aware date offsets that respect Jalali calendar rules.

Attributes:

Name Type Description
n int

Number of periods.

normalize bool

Whether to normalize to midnight.

Source code in jalali_pandas/offsets/base.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
class JalaliOffset(ABC):
    """Abstract base class for Jalali calendar-aware offsets.

    This class provides the foundation for implementing calendar-aware
    date offsets that respect Jalali calendar rules.

    Attributes:
        n: Number of periods.
        normalize: Whether to normalize to midnight.
    """

    _prefix: str = "J"
    _attributes: tuple[str, ...] = ("n", "normalize")

    def __init__(self, n: int = 1, normalize: bool = False) -> None:
        """Initialize JalaliOffset.

        Args:
            n: Number of periods. Defaults to 1.
            normalize: Whether to normalize to midnight. Defaults to False.
        """
        self._n = n
        self._normalize = normalize

    @property
    def n(self) -> int:
        """Number of periods."""
        return self._n

    @property
    def normalize(self) -> bool:
        """Whether to normalize to midnight."""
        return self._normalize

    @property
    def name(self) -> str:
        """Return the name of the offset."""
        return f"{self._prefix}{abs(self._n)}"

    @property
    def freqstr(self) -> str:
        """Return the frequency string."""
        return self.name

    def __repr__(self) -> str:
        """String representation."""
        return f"<{self.__class__.__name__}: n={self._n}>"

    def __eq__(self, other: object) -> bool:
        """Check equality."""
        if isinstance(other, JalaliOffset):
            return type(self) is type(other) and self._n == other._n
        return False

    def __hash__(self) -> int:
        """Hash for use in sets and dicts."""
        return hash((type(self).__name__, self._n))

    def __neg__(self) -> JalaliOffset:
        """Return negated offset."""
        return type(self)(n=-self._n, normalize=self._normalize)

    def __mul__(self, other: int) -> JalaliOffset:
        """Multiply offset by integer."""
        if isinstance(other, int):
            return type(self)(n=self._n * other, normalize=self._normalize)
        return NotImplemented

    def __rmul__(self, other: int) -> JalaliOffset:
        """Right multiply offset by integer."""
        return self.__mul__(other)

    @abstractmethod
    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add offset to a JalaliTimestamp."""
        ...

    def __radd__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Right add offset to a JalaliTimestamp."""
        return self.__add__(other)

    @abstractmethod
    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract offset from a JalaliTimestamp."""
        ...

    @abstractmethod
    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next valid date.

        Args:
            dt: JalaliTimestamp to roll forward.

        Returns:
            Rolled forward JalaliTimestamp.
        """
        ...

    @abstractmethod
    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous valid date.

        Args:
            dt: JalaliTimestamp to roll back.

        Returns:
            Rolled back JalaliTimestamp.
        """
        ...

    @abstractmethod
    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on offset boundary.

        Args:
            dt: JalaliTimestamp to check.

        Returns:
            True if on offset boundary.
        """
        ...

    def _apply(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Apply offset to timestamp.

        Args:
            other: JalaliTimestamp to apply offset to.

        Returns:
            New JalaliTimestamp with offset applied.
        """
        result = self.__add__(other)
        if self._normalize:
            result = result.normalize()
        return result

freqstr property

freqstr: str

Return the frequency string.

n property

n: int

Number of periods.

name property

name: str

Return the name of the offset.

normalize property

normalize: bool

Whether to normalize to midnight.

__add__ abstractmethod

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add offset to a JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
84
85
86
87
@abstractmethod
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add offset to a JalaliTimestamp."""
    ...

__eq__

__eq__(other: object) -> bool

Check equality.

Source code in jalali_pandas/offsets/base.py
60
61
62
63
64
def __eq__(self, other: object) -> bool:
    """Check equality."""
    if isinstance(other, JalaliOffset):
        return type(self) is type(other) and self._n == other._n
    return False

__hash__

__hash__() -> int

Hash for use in sets and dicts.

Source code in jalali_pandas/offsets/base.py
66
67
68
def __hash__(self) -> int:
    """Hash for use in sets and dicts."""
    return hash((type(self).__name__, self._n))

__init__

__init__(n: int = 1, normalize: bool = False) -> None

Initialize JalaliOffset.

Parameters:

Name Type Description Default
n int

Number of periods. Defaults to 1.

1
normalize bool

Whether to normalize to midnight. Defaults to False.

False
Source code in jalali_pandas/offsets/base.py
26
27
28
29
30
31
32
33
34
def __init__(self, n: int = 1, normalize: bool = False) -> None:
    """Initialize JalaliOffset.

    Args:
        n: Number of periods. Defaults to 1.
        normalize: Whether to normalize to midnight. Defaults to False.
    """
    self._n = n
    self._normalize = normalize

__mul__

__mul__(other: int) -> JalaliOffset

Multiply offset by integer.

Source code in jalali_pandas/offsets/base.py
74
75
76
77
78
def __mul__(self, other: int) -> JalaliOffset:
    """Multiply offset by integer."""
    if isinstance(other, int):
        return type(self)(n=self._n * other, normalize=self._normalize)
    return NotImplemented

__neg__

__neg__() -> JalaliOffset

Return negated offset.

Source code in jalali_pandas/offsets/base.py
70
71
72
def __neg__(self) -> JalaliOffset:
    """Return negated offset."""
    return type(self)(n=-self._n, normalize=self._normalize)

__radd__

__radd__(other: JalaliTimestamp) -> JalaliTimestamp

Right add offset to a JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
89
90
91
def __radd__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Right add offset to a JalaliTimestamp."""
    return self.__add__(other)

__repr__

__repr__() -> str

String representation.

Source code in jalali_pandas/offsets/base.py
56
57
58
def __repr__(self) -> str:
    """String representation."""
    return f"<{self.__class__.__name__}: n={self._n}>"

__rmul__

__rmul__(other: int) -> JalaliOffset

Right multiply offset by integer.

Source code in jalali_pandas/offsets/base.py
80
81
82
def __rmul__(self, other: int) -> JalaliOffset:
    """Right multiply offset by integer."""
    return self.__mul__(other)

__sub__ abstractmethod

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract offset from a JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
93
94
95
96
@abstractmethod
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract offset from a JalaliTimestamp."""
    ...

is_on_offset abstractmethod

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on offset boundary.

Parameters:

Name Type Description Default
dt JalaliTimestamp

JalaliTimestamp to check.

required

Returns:

Type Description
bool

True if on offset boundary.

Source code in jalali_pandas/offsets/base.py
122
123
124
125
126
127
128
129
130
131
132
@abstractmethod
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on offset boundary.

    Args:
        dt: JalaliTimestamp to check.

    Returns:
        True if on offset boundary.
    """
    ...

rollback abstractmethod

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous valid date.

Parameters:

Name Type Description Default
dt JalaliTimestamp

JalaliTimestamp to roll back.

required

Returns:

Type Description
JalaliTimestamp

Rolled back JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
110
111
112
113
114
115
116
117
118
119
120
@abstractmethod
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous valid date.

    Args:
        dt: JalaliTimestamp to roll back.

    Returns:
        Rolled back JalaliTimestamp.
    """
    ...

rollforward abstractmethod

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next valid date.

Parameters:

Name Type Description Default
dt JalaliTimestamp

JalaliTimestamp to roll forward.

required

Returns:

Type Description
JalaliTimestamp

Rolled forward JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
 98
 99
100
101
102
103
104
105
106
107
108
@abstractmethod
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next valid date.

    Args:
        dt: JalaliTimestamp to roll forward.

    Returns:
        Rolled forward JalaliTimestamp.
    """
    ...

JalaliQuarterBegin

Bases: JalaliOffset

Offset to the beginning of a Jalali quarter.

Source code in jalali_pandas/offsets/quarter.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class JalaliQuarterBegin(JalaliOffset):
    """Offset to the beginning of a Jalali quarter."""

    _prefix = "JQS"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add quarters to timestamp, landing on quarter start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Calculate current quarter (0-indexed)
        current_quarter = (other.month - 1) // 3

        # Calculate target quarter
        total_quarters = other.year * 4 + current_quarter + self._n
        new_year = total_quarters // 4
        new_quarter = total_quarters % 4
        new_month = new_quarter * 3 + 1

        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=1,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract quarters from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next quarter start if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self.__add__(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to current quarter start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        quarter_start_month = ((dt.month - 1) // 3) * 3 + 1
        return JalaliTimestamp(
            year=dt.year,
            month=quarter_start_month,
            day=1,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on quarter start."""
        return dt.month in QUARTER_START_MONTHS and dt.day == 1

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add quarters to timestamp, landing on quarter start.

Source code in jalali_pandas/offsets/quarter.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add quarters to timestamp, landing on quarter start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Calculate current quarter (0-indexed)
    current_quarter = (other.month - 1) // 3

    # Calculate target quarter
    total_quarters = other.year * 4 + current_quarter + self._n
    new_year = total_quarters // 4
    new_quarter = total_quarters % 4
    new_month = new_quarter * 3 + 1

    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=1,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract quarters from timestamp.

Source code in jalali_pandas/offsets/quarter.py
52
53
54
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract quarters from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on quarter start.

Source code in jalali_pandas/offsets/quarter.py
80
81
82
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on quarter start."""
    return dt.month in QUARTER_START_MONTHS and dt.day == 1

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to current quarter start.

Source code in jalali_pandas/offsets/quarter.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to current quarter start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    quarter_start_month = ((dt.month - 1) // 3) * 3 + 1
    return JalaliTimestamp(
        year=dt.year,
        month=quarter_start_month,
        day=1,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next quarter start if not already on one.

Source code in jalali_pandas/offsets/quarter.py
56
57
58
59
60
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next quarter start if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self.__add__(dt)

JalaliQuarterEnd

Bases: JalaliOffset

Offset to the end of a Jalali quarter.

Source code in jalali_pandas/offsets/quarter.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
class JalaliQuarterEnd(JalaliOffset):
    """Offset to the end of a Jalali quarter."""

    _prefix = "JQE"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add quarters to timestamp, landing on quarter end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Calculate current quarter (0-indexed)
        current_quarter = (other.month - 1) // 3

        # Calculate target quarter
        total_quarters = other.year * 4 + current_quarter + self._n
        new_year = total_quarters // 4
        new_quarter = total_quarters % 4
        new_month = (new_quarter + 1) * 3  # End month of quarter
        new_day = days_in_month(new_year, new_month)

        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=new_day,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract quarters from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next quarter end if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self._get_quarter_end(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous quarter end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        # Go to previous quarter's end
        current_quarter = (dt.month - 1) // 3
        if current_quarter == 0:
            new_year = dt.year - 1
            new_month = 12
        else:
            new_year = dt.year
            new_month = current_quarter * 3

        new_day = days_in_month(new_year, new_month)
        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=new_day,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on quarter end."""
        if dt.month not in QUARTER_END_MONTHS:
            return False
        return dt.day == days_in_month(dt.year, dt.month)

    def _get_quarter_end(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Get the end of the current quarter."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        quarter_end_month = ((dt.month - 1) // 3 + 1) * 3
        return JalaliTimestamp(
            year=dt.year,
            month=quarter_end_month,
            day=days_in_month(dt.year, quarter_end_month),
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add quarters to timestamp, landing on quarter end.

Source code in jalali_pandas/offsets/quarter.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add quarters to timestamp, landing on quarter end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Calculate current quarter (0-indexed)
    current_quarter = (other.month - 1) // 3

    # Calculate target quarter
    total_quarters = other.year * 4 + current_quarter + self._n
    new_year = total_quarters // 4
    new_quarter = total_quarters % 4
    new_month = (new_quarter + 1) * 3  # End month of quarter
    new_day = days_in_month(new_year, new_month)

    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=new_day,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract quarters from timestamp.

Source code in jalali_pandas/offsets/quarter.py
119
120
121
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract quarters from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on quarter end.

Source code in jalali_pandas/offsets/quarter.py
156
157
158
159
160
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on quarter end."""
    if dt.month not in QUARTER_END_MONTHS:
        return False
    return dt.day == days_in_month(dt.year, dt.month)

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous quarter end.

Source code in jalali_pandas/offsets/quarter.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous quarter end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    # Go to previous quarter's end
    current_quarter = (dt.month - 1) // 3
    if current_quarter == 0:
        new_year = dt.year - 1
        new_month = 12
    else:
        new_year = dt.year
        new_month = current_quarter * 3

    new_day = days_in_month(new_year, new_month)
    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=new_day,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next quarter end if not already on one.

Source code in jalali_pandas/offsets/quarter.py
123
124
125
126
127
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next quarter end if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self._get_quarter_end(dt)

JalaliWeek

Bases: JalaliOffset

Offset to a specific day of the Jalali week.

The Jalali week starts on Saturday (weekday=0) and ends on Friday (weekday=6).

Attributes:

Name Type Description
weekday int

The target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).

Source code in jalali_pandas/offsets/week.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class JalaliWeek(JalaliOffset):
    """Offset to a specific day of the Jalali week.

    The Jalali week starts on Saturday (weekday=0) and ends on Friday (weekday=6).

    Attributes:
        weekday: The target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).
    """

    _prefix = "JW"
    _attributes = ("n", "normalize", "weekday")

    def __init__(
        self,
        n: int = 1,
        normalize: bool = False,
        weekday: int = SATURDAY,
    ) -> None:
        """Initialize JalaliWeek offset.

        Args:
            n: Number of weeks. Defaults to 1.
            normalize: Whether to normalize to midnight. Defaults to False.
            weekday: Target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).

        Raises:
            ValueError: If weekday is not in range 0-6.
        """
        super().__init__(n=n, normalize=normalize)
        if not 0 <= weekday <= 6:
            raise ValueError(f"weekday must be 0-6, got {weekday}")
        self._weekday = weekday

    @property
    def weekday(self) -> int:
        """Target weekday (0=Saturday, 6=Friday)."""
        return self._weekday

    def __repr__(self) -> str:
        """String representation."""
        return f"<{self.__class__.__name__}: n={self._n}, weekday={self._weekday}>"

    def __eq__(self, other: object) -> bool:
        """Check equality."""
        if isinstance(other, JalaliWeek):
            return (
                type(self) is type(other)
                and self._n == other._n
                and self._weekday == other._weekday
            )
        return False

    def __hash__(self) -> int:
        """Hash for use in sets and dicts."""
        return hash((type(self).__name__, self._n, self._weekday))

    def __neg__(self) -> JalaliWeek:
        """Return negated offset."""
        return JalaliWeek(n=-self._n, normalize=self._normalize, weekday=self._weekday)

    def __mul__(self, other: int) -> JalaliWeek:
        """Multiply offset by integer."""
        if isinstance(other, int):
            return JalaliWeek(
                n=self._n * other, normalize=self._normalize, weekday=self._weekday
            )
        return NotImplemented

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add weeks to timestamp, landing on target weekday."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Get current weekday (0=Saturday, 6=Friday)
        current_weekday = other.dayofweek

        # Calculate days to target weekday
        days_to_target = (self._weekday - current_weekday) % 7

        # If we're already on the target weekday and n > 0, we need to go forward
        # If n < 0, we need to go backward
        if self._n > 0:
            # Move forward n weeks, landing on target weekday
            if days_to_target == 0:
                # Already on target, move n full weeks
                total_days = self._n * 7
            else:
                # Move to next target weekday, then (n-1) more weeks
                total_days = days_to_target + (self._n - 1) * 7
        elif self._n < 0:
            # Move backward |n| weeks, landing on target weekday
            if days_to_target == 0:
                # Already on target, move |n| full weeks back
                total_days = self._n * 7
            else:
                # Move to previous target weekday, then (|n|-1) more weeks back
                days_to_prev_target = days_to_target - 7  # Negative
                total_days = days_to_prev_target + (self._n + 1) * 7
        else:
            # n == 0, just move to nearest target weekday (forward)
            total_days = days_to_target

        # Apply the offset using timedelta
        new_gregorian = other.to_gregorian() + timedelta(days=total_days)
        result = JalaliTimestamp.from_gregorian(new_gregorian)

        if self._normalize:
            result = result.normalize()

        return result

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract weeks from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next target weekday if not already on one."""
        if self.is_on_offset(dt):
            return dt

        # Calculate days to next target weekday
        current_weekday = dt.dayofweek
        days_forward = (self._weekday - current_weekday) % 7
        if days_forward == 0:
            days_forward = 7  # Move to next week if already on target

        new_gregorian = dt.to_gregorian() + timedelta(days=days_forward)
        result = type(dt).from_gregorian(new_gregorian)

        if self._normalize:
            result = result.normalize()

        return result

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous target weekday."""
        if self.is_on_offset(dt):
            return dt

        # Calculate days to previous target weekday
        current_weekday = dt.dayofweek
        days_back = (current_weekday - self._weekday) % 7
        if days_back == 0:
            days_back = 7  # Move to previous week if already on target

        new_gregorian = dt.to_gregorian() - timedelta(days=days_back)
        result = type(dt).from_gregorian(new_gregorian)

        if self._normalize:
            result = result.normalize()

        return result

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on target weekday."""
        return dt.dayofweek == self._weekday

weekday property

weekday: int

Target weekday (0=Saturday, 6=Friday).

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add weeks to timestamp, landing on target weekday.

Source code in jalali_pandas/offsets/week.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add weeks to timestamp, landing on target weekday."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Get current weekday (0=Saturday, 6=Friday)
    current_weekday = other.dayofweek

    # Calculate days to target weekday
    days_to_target = (self._weekday - current_weekday) % 7

    # If we're already on the target weekday and n > 0, we need to go forward
    # If n < 0, we need to go backward
    if self._n > 0:
        # Move forward n weeks, landing on target weekday
        if days_to_target == 0:
            # Already on target, move n full weeks
            total_days = self._n * 7
        else:
            # Move to next target weekday, then (n-1) more weeks
            total_days = days_to_target + (self._n - 1) * 7
    elif self._n < 0:
        # Move backward |n| weeks, landing on target weekday
        if days_to_target == 0:
            # Already on target, move |n| full weeks back
            total_days = self._n * 7
        else:
            # Move to previous target weekday, then (|n|-1) more weeks back
            days_to_prev_target = days_to_target - 7  # Negative
            total_days = days_to_prev_target + (self._n + 1) * 7
    else:
        # n == 0, just move to nearest target weekday (forward)
        total_days = days_to_target

    # Apply the offset using timedelta
    new_gregorian = other.to_gregorian() + timedelta(days=total_days)
    result = JalaliTimestamp.from_gregorian(new_gregorian)

    if self._normalize:
        result = result.normalize()

    return result

__eq__

__eq__(other: object) -> bool

Check equality.

Source code in jalali_pandas/offsets/week.py
65
66
67
68
69
70
71
72
73
def __eq__(self, other: object) -> bool:
    """Check equality."""
    if isinstance(other, JalaliWeek):
        return (
            type(self) is type(other)
            and self._n == other._n
            and self._weekday == other._weekday
        )
    return False

__hash__

__hash__() -> int

Hash for use in sets and dicts.

Source code in jalali_pandas/offsets/week.py
75
76
77
def __hash__(self) -> int:
    """Hash for use in sets and dicts."""
    return hash((type(self).__name__, self._n, self._weekday))

__init__

__init__(n: int = 1, normalize: bool = False, weekday: int = SATURDAY) -> None

Initialize JalaliWeek offset.

Parameters:

Name Type Description Default
n int

Number of weeks. Defaults to 1.

1
normalize bool

Whether to normalize to midnight. Defaults to False.

False
weekday int

Target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).

SATURDAY

Raises:

Type Description
ValueError

If weekday is not in range 0-6.

Source code in jalali_pandas/offsets/week.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def __init__(
    self,
    n: int = 1,
    normalize: bool = False,
    weekday: int = SATURDAY,
) -> None:
    """Initialize JalaliWeek offset.

    Args:
        n: Number of weeks. Defaults to 1.
        normalize: Whether to normalize to midnight. Defaults to False.
        weekday: Target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).

    Raises:
        ValueError: If weekday is not in range 0-6.
    """
    super().__init__(n=n, normalize=normalize)
    if not 0 <= weekday <= 6:
        raise ValueError(f"weekday must be 0-6, got {weekday}")
    self._weekday = weekday

__mul__

__mul__(other: int) -> JalaliWeek

Multiply offset by integer.

Source code in jalali_pandas/offsets/week.py
83
84
85
86
87
88
89
def __mul__(self, other: int) -> JalaliWeek:
    """Multiply offset by integer."""
    if isinstance(other, int):
        return JalaliWeek(
            n=self._n * other, normalize=self._normalize, weekday=self._weekday
        )
    return NotImplemented

__neg__

__neg__() -> JalaliWeek

Return negated offset.

Source code in jalali_pandas/offsets/week.py
79
80
81
def __neg__(self) -> JalaliWeek:
    """Return negated offset."""
    return JalaliWeek(n=-self._n, normalize=self._normalize, weekday=self._weekday)

__repr__

__repr__() -> str

String representation.

Source code in jalali_pandas/offsets/week.py
61
62
63
def __repr__(self) -> str:
    """String representation."""
    return f"<{self.__class__.__name__}: n={self._n}, weekday={self._weekday}>"

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract weeks from timestamp.

Source code in jalali_pandas/offsets/week.py
136
137
138
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract weeks from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on target weekday.

Source code in jalali_pandas/offsets/week.py
178
179
180
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on target weekday."""
    return dt.dayofweek == self._weekday

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous target weekday.

Source code in jalali_pandas/offsets/week.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous target weekday."""
    if self.is_on_offset(dt):
        return dt

    # Calculate days to previous target weekday
    current_weekday = dt.dayofweek
    days_back = (current_weekday - self._weekday) % 7
    if days_back == 0:
        days_back = 7  # Move to previous week if already on target

    new_gregorian = dt.to_gregorian() - timedelta(days=days_back)
    result = type(dt).from_gregorian(new_gregorian)

    if self._normalize:
        result = result.normalize()

    return result

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next target weekday if not already on one.

Source code in jalali_pandas/offsets/week.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next target weekday if not already on one."""
    if self.is_on_offset(dt):
        return dt

    # Calculate days to next target weekday
    current_weekday = dt.dayofweek
    days_forward = (self._weekday - current_weekday) % 7
    if days_forward == 0:
        days_forward = 7  # Move to next week if already on target

    new_gregorian = dt.to_gregorian() + timedelta(days=days_forward)
    result = type(dt).from_gregorian(new_gregorian)

    if self._normalize:
        result = result.normalize()

    return result

JalaliYearBegin

Bases: JalaliOffset

Offset to the beginning of a Jalali year (Nowruz).

Source code in jalali_pandas/offsets/year.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class JalaliYearBegin(JalaliOffset):
    """Offset to the beginning of a Jalali year (Nowruz)."""

    _prefix = "JYS"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add years to timestamp, landing on year start (Nowruz)."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        new_year = other.year + self._n

        return JalaliTimestamp(
            year=new_year,
            month=1,
            day=1,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract years from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next year start if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self.__add__(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to current year start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        return JalaliTimestamp(
            year=dt.year,
            month=1,
            day=1,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on year start (Nowruz - 1 Farvardin)."""
        return dt.month == 1 and dt.day == 1

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add years to timestamp, landing on year start (Nowruz).

Source code in jalali_pandas/offsets/year.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add years to timestamp, landing on year start (Nowruz)."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    new_year = other.year + self._n

    return JalaliTimestamp(
        year=new_year,
        month=1,
        day=1,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract years from timestamp.

Source code in jalali_pandas/offsets/year.py
40
41
42
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract years from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on year start (Nowruz - 1 Farvardin).

Source code in jalali_pandas/offsets/year.py
67
68
69
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on year start (Nowruz - 1 Farvardin)."""
    return dt.month == 1 and dt.day == 1

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to current year start.

Source code in jalali_pandas/offsets/year.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to current year start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    return JalaliTimestamp(
        year=dt.year,
        month=1,
        day=1,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next year start if not already on one.

Source code in jalali_pandas/offsets/year.py
44
45
46
47
48
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next year start if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self.__add__(dt)

JalaliYearEnd

Bases: JalaliOffset

Offset to the end of a Jalali year.

Source code in jalali_pandas/offsets/year.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
class JalaliYearEnd(JalaliOffset):
    """Offset to the end of a Jalali year."""

    _prefix = "JYE"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add years to timestamp, landing on year end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        new_year = other.year + self._n
        # Last day of Esfand (29 or 30 depending on leap year)
        new_day = 30 if is_leap_year(new_year) else 29

        return JalaliTimestamp(
            year=new_year,
            month=12,
            day=new_day,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract years from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next year end if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self._get_year_end(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous year end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        # Go to previous year's end
        new_year = dt.year - 1
        new_day = 30 if is_leap_year(new_year) else 29

        return JalaliTimestamp(
            year=new_year,
            month=12,
            day=new_day,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on year end (last day of Esfand)."""
        if dt.month != 12:
            return False
        return dt.day == days_in_month(dt.year, 12)

    def _get_year_end(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Get the end of the current year."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        return JalaliTimestamp(
            year=dt.year,
            month=12,
            day=days_in_month(dt.year, 12),
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add years to timestamp, landing on year end.

Source code in jalali_pandas/offsets/year.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add years to timestamp, landing on year end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    new_year = other.year + self._n
    # Last day of Esfand (29 or 30 depending on leap year)
    new_day = 30 if is_leap_year(new_year) else 29

    return JalaliTimestamp(
        year=new_year,
        month=12,
        day=new_day,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract years from timestamp.

Source code in jalali_pandas/offsets/year.py
100
101
102
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract years from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on year end (last day of Esfand).

Source code in jalali_pandas/offsets/year.py
131
132
133
134
135
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on year end (last day of Esfand)."""
    if dt.month != 12:
        return False
    return dt.day == days_in_month(dt.year, 12)

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous year end.

Source code in jalali_pandas/offsets/year.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous year end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    # Go to previous year's end
    new_year = dt.year - 1
    new_day = 30 if is_leap_year(new_year) else 29

    return JalaliTimestamp(
        year=new_year,
        month=12,
        day=new_day,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next year end if not already on one.

Source code in jalali_pandas/offsets/year.py
104
105
106
107
108
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next year end if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self._get_year_end(dt)

get_jalali_alias

get_jalali_alias(offset_class: type[JalaliOffset]) -> str | None

Get the frequency alias for an offset class.

Parameters:

Name Type Description Default
offset_class type[JalaliOffset]

The offset class.

required

Returns:

Type Description
str | None

The frequency alias, or None if not registered.

Source code in jalali_pandas/offsets/aliases.py
46
47
48
49
50
51
52
53
54
55
def get_jalali_alias(offset_class: type[JalaliOffset]) -> str | None:
    """Get the frequency alias for an offset class.

    Args:
        offset_class: The offset class.

    Returns:
        The frequency alias, or None if not registered.
    """
    return _JALALI_OFFSET_TO_ALIAS.get(offset_class)

get_jalali_offset

get_jalali_offset(alias: str) -> type[JalaliOffset] | None

Get the offset class for a frequency alias.

Parameters:

Name Type Description Default
alias str

The frequency alias string.

required

Returns:

Type Description
type[JalaliOffset] | None

The offset class, or None if not found.

Source code in jalali_pandas/offsets/aliases.py
34
35
36
37
38
39
40
41
42
43
def get_jalali_offset(alias: str) -> type[JalaliOffset] | None:
    """Get the offset class for a frequency alias.

    Args:
        alias: The frequency alias string.

    Returns:
        The offset class, or None if not found.
    """
    return _JALALI_OFFSET_ALIASES.get(alias)

list_jalali_aliases

list_jalali_aliases() -> dict[str, str]

List all registered Jalali frequency aliases.

Returns:

Type Description
dict[str, str]

Dictionary mapping aliases to offset class names.

Source code in jalali_pandas/offsets/aliases.py
 97
 98
 99
100
101
102
103
def list_jalali_aliases() -> dict[str, str]:
    """List all registered Jalali frequency aliases.

    Returns:
        Dictionary mapping aliases to offset class names.
    """
    return {alias: cls.__name__ for alias, cls in _JALALI_OFFSET_ALIASES.items()}

parse_jalali_frequency

parse_jalali_frequency(freq_str: str) -> JalaliOffset

Parse a frequency string into a Jalali offset instance.

Supports formats like: - "JME" -> JalaliMonthEnd(n=1) - "2JME" -> JalaliMonthEnd(n=2) - "-1JQS" -> JalaliQuarterBegin(n=-1)

Parameters:

Name Type Description Default
freq_str str

The frequency string to parse.

required

Returns:

Type Description
JalaliOffset

A Jalali offset instance.

Raises:

Type Description
ValueError

If the frequency string is not recognized.

Source code in jalali_pandas/offsets/aliases.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def parse_jalali_frequency(freq_str: str) -> JalaliOffset:
    """Parse a frequency string into a Jalali offset instance.

    Supports formats like:
    - "JME" -> JalaliMonthEnd(n=1)
    - "2JME" -> JalaliMonthEnd(n=2)
    - "-1JQS" -> JalaliQuarterBegin(n=-1)

    Args:
        freq_str: The frequency string to parse.

    Returns:
        A Jalali offset instance.

    Raises:
        ValueError: If the frequency string is not recognized.
    """
    # Pattern: optional sign, optional number, alias
    pattern = r"^(-?)(\d*)([A-Z]+)$"
    match = re.match(pattern, freq_str.strip().upper())

    if not match:
        raise ValueError(f"Cannot parse frequency string: '{freq_str}'")

    sign, num_str, alias = match.groups()

    # Get the offset class
    offset_class = get_jalali_offset(alias)
    if offset_class is None:
        raise ValueError(f"Unknown Jalali frequency alias: '{alias}'")

    # Parse the multiplier
    n = int(num_str) if num_str else 1
    if sign == "-":
        n = -n

    return offset_class(n=n)

register_jalali_alias

register_jalali_alias(alias: str, offset_class: type[JalaliOffset]) -> None

Register a frequency alias for a Jalali offset class.

Parameters:

Name Type Description Default
alias str

The frequency alias string (e.g., "JME", "JQS").

required
offset_class type[JalaliOffset]

The offset class to register.

required
Source code in jalali_pandas/offsets/aliases.py
23
24
25
26
27
28
29
30
31
def register_jalali_alias(alias: str, offset_class: type[JalaliOffset]) -> None:
    """Register a frequency alias for a Jalali offset class.

    Args:
        alias: The frequency alias string (e.g., "JME", "JQS").
        offset_class: The offset class to register.
    """
    _JALALI_OFFSET_ALIASES[alias] = offset_class
    _JALALI_OFFSET_TO_ALIAS[offset_class] = alias

Frequency alias registration for Jalali offsets.

This module provides frequency alias registration and parsing for Jalali calendar offsets, enabling string-based frequency specifications like "JME", "2JQE", etc.

get_jalali_alias

get_jalali_alias(offset_class: type[JalaliOffset]) -> str | None

Get the frequency alias for an offset class.

Parameters:

Name Type Description Default
offset_class type[JalaliOffset]

The offset class.

required

Returns:

Type Description
str | None

The frequency alias, or None if not registered.

Source code in jalali_pandas/offsets/aliases.py
46
47
48
49
50
51
52
53
54
55
def get_jalali_alias(offset_class: type[JalaliOffset]) -> str | None:
    """Get the frequency alias for an offset class.

    Args:
        offset_class: The offset class.

    Returns:
        The frequency alias, or None if not registered.
    """
    return _JALALI_OFFSET_TO_ALIAS.get(offset_class)

get_jalali_offset

get_jalali_offset(alias: str) -> type[JalaliOffset] | None

Get the offset class for a frequency alias.

Parameters:

Name Type Description Default
alias str

The frequency alias string.

required

Returns:

Type Description
type[JalaliOffset] | None

The offset class, or None if not found.

Source code in jalali_pandas/offsets/aliases.py
34
35
36
37
38
39
40
41
42
43
def get_jalali_offset(alias: str) -> type[JalaliOffset] | None:
    """Get the offset class for a frequency alias.

    Args:
        alias: The frequency alias string.

    Returns:
        The offset class, or None if not found.
    """
    return _JALALI_OFFSET_ALIASES.get(alias)

list_jalali_aliases

list_jalali_aliases() -> dict[str, str]

List all registered Jalali frequency aliases.

Returns:

Type Description
dict[str, str]

Dictionary mapping aliases to offset class names.

Source code in jalali_pandas/offsets/aliases.py
 97
 98
 99
100
101
102
103
def list_jalali_aliases() -> dict[str, str]:
    """List all registered Jalali frequency aliases.

    Returns:
        Dictionary mapping aliases to offset class names.
    """
    return {alias: cls.__name__ for alias, cls in _JALALI_OFFSET_ALIASES.items()}

parse_jalali_frequency

parse_jalali_frequency(freq_str: str) -> JalaliOffset

Parse a frequency string into a Jalali offset instance.

Supports formats like: - "JME" -> JalaliMonthEnd(n=1) - "2JME" -> JalaliMonthEnd(n=2) - "-1JQS" -> JalaliQuarterBegin(n=-1)

Parameters:

Name Type Description Default
freq_str str

The frequency string to parse.

required

Returns:

Type Description
JalaliOffset

A Jalali offset instance.

Raises:

Type Description
ValueError

If the frequency string is not recognized.

Source code in jalali_pandas/offsets/aliases.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def parse_jalali_frequency(freq_str: str) -> JalaliOffset:
    """Parse a frequency string into a Jalali offset instance.

    Supports formats like:
    - "JME" -> JalaliMonthEnd(n=1)
    - "2JME" -> JalaliMonthEnd(n=2)
    - "-1JQS" -> JalaliQuarterBegin(n=-1)

    Args:
        freq_str: The frequency string to parse.

    Returns:
        A Jalali offset instance.

    Raises:
        ValueError: If the frequency string is not recognized.
    """
    # Pattern: optional sign, optional number, alias
    pattern = r"^(-?)(\d*)([A-Z]+)$"
    match = re.match(pattern, freq_str.strip().upper())

    if not match:
        raise ValueError(f"Cannot parse frequency string: '{freq_str}'")

    sign, num_str, alias = match.groups()

    # Get the offset class
    offset_class = get_jalali_offset(alias)
    if offset_class is None:
        raise ValueError(f"Unknown Jalali frequency alias: '{alias}'")

    # Parse the multiplier
    n = int(num_str) if num_str else 1
    if sign == "-":
        n = -n

    return offset_class(n=n)

register_jalali_alias

register_jalali_alias(alias: str, offset_class: type[JalaliOffset]) -> None

Register a frequency alias for a Jalali offset class.

Parameters:

Name Type Description Default
alias str

The frequency alias string (e.g., "JME", "JQS").

required
offset_class type[JalaliOffset]

The offset class to register.

required
Source code in jalali_pandas/offsets/aliases.py
23
24
25
26
27
28
29
30
31
def register_jalali_alias(alias: str, offset_class: type[JalaliOffset]) -> None:
    """Register a frequency alias for a Jalali offset class.

    Args:
        alias: The frequency alias string (e.g., "JME", "JQS").
        offset_class: The offset class to register.
    """
    _JALALI_OFFSET_ALIASES[alias] = offset_class
    _JALALI_OFFSET_TO_ALIAS[offset_class] = alias

Base class for Jalali calendar offsets.

JalaliOffset

Bases: ABC

Abstract base class for Jalali calendar-aware offsets.

This class provides the foundation for implementing calendar-aware date offsets that respect Jalali calendar rules.

Attributes:

Name Type Description
n int

Number of periods.

normalize bool

Whether to normalize to midnight.

Source code in jalali_pandas/offsets/base.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
class JalaliOffset(ABC):
    """Abstract base class for Jalali calendar-aware offsets.

    This class provides the foundation for implementing calendar-aware
    date offsets that respect Jalali calendar rules.

    Attributes:
        n: Number of periods.
        normalize: Whether to normalize to midnight.
    """

    _prefix: str = "J"
    _attributes: tuple[str, ...] = ("n", "normalize")

    def __init__(self, n: int = 1, normalize: bool = False) -> None:
        """Initialize JalaliOffset.

        Args:
            n: Number of periods. Defaults to 1.
            normalize: Whether to normalize to midnight. Defaults to False.
        """
        self._n = n
        self._normalize = normalize

    @property
    def n(self) -> int:
        """Number of periods."""
        return self._n

    @property
    def normalize(self) -> bool:
        """Whether to normalize to midnight."""
        return self._normalize

    @property
    def name(self) -> str:
        """Return the name of the offset."""
        return f"{self._prefix}{abs(self._n)}"

    @property
    def freqstr(self) -> str:
        """Return the frequency string."""
        return self.name

    def __repr__(self) -> str:
        """String representation."""
        return f"<{self.__class__.__name__}: n={self._n}>"

    def __eq__(self, other: object) -> bool:
        """Check equality."""
        if isinstance(other, JalaliOffset):
            return type(self) is type(other) and self._n == other._n
        return False

    def __hash__(self) -> int:
        """Hash for use in sets and dicts."""
        return hash((type(self).__name__, self._n))

    def __neg__(self) -> JalaliOffset:
        """Return negated offset."""
        return type(self)(n=-self._n, normalize=self._normalize)

    def __mul__(self, other: int) -> JalaliOffset:
        """Multiply offset by integer."""
        if isinstance(other, int):
            return type(self)(n=self._n * other, normalize=self._normalize)
        return NotImplemented

    def __rmul__(self, other: int) -> JalaliOffset:
        """Right multiply offset by integer."""
        return self.__mul__(other)

    @abstractmethod
    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add offset to a JalaliTimestamp."""
        ...

    def __radd__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Right add offset to a JalaliTimestamp."""
        return self.__add__(other)

    @abstractmethod
    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract offset from a JalaliTimestamp."""
        ...

    @abstractmethod
    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next valid date.

        Args:
            dt: JalaliTimestamp to roll forward.

        Returns:
            Rolled forward JalaliTimestamp.
        """
        ...

    @abstractmethod
    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous valid date.

        Args:
            dt: JalaliTimestamp to roll back.

        Returns:
            Rolled back JalaliTimestamp.
        """
        ...

    @abstractmethod
    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on offset boundary.

        Args:
            dt: JalaliTimestamp to check.

        Returns:
            True if on offset boundary.
        """
        ...

    def _apply(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Apply offset to timestamp.

        Args:
            other: JalaliTimestamp to apply offset to.

        Returns:
            New JalaliTimestamp with offset applied.
        """
        result = self.__add__(other)
        if self._normalize:
            result = result.normalize()
        return result

freqstr property

freqstr: str

Return the frequency string.

n property

n: int

Number of periods.

name property

name: str

Return the name of the offset.

normalize property

normalize: bool

Whether to normalize to midnight.

__add__ abstractmethod

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add offset to a JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
84
85
86
87
@abstractmethod
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add offset to a JalaliTimestamp."""
    ...

__eq__

__eq__(other: object) -> bool

Check equality.

Source code in jalali_pandas/offsets/base.py
60
61
62
63
64
def __eq__(self, other: object) -> bool:
    """Check equality."""
    if isinstance(other, JalaliOffset):
        return type(self) is type(other) and self._n == other._n
    return False

__hash__

__hash__() -> int

Hash for use in sets and dicts.

Source code in jalali_pandas/offsets/base.py
66
67
68
def __hash__(self) -> int:
    """Hash for use in sets and dicts."""
    return hash((type(self).__name__, self._n))

__init__

__init__(n: int = 1, normalize: bool = False) -> None

Initialize JalaliOffset.

Parameters:

Name Type Description Default
n int

Number of periods. Defaults to 1.

1
normalize bool

Whether to normalize to midnight. Defaults to False.

False
Source code in jalali_pandas/offsets/base.py
26
27
28
29
30
31
32
33
34
def __init__(self, n: int = 1, normalize: bool = False) -> None:
    """Initialize JalaliOffset.

    Args:
        n: Number of periods. Defaults to 1.
        normalize: Whether to normalize to midnight. Defaults to False.
    """
    self._n = n
    self._normalize = normalize

__mul__

__mul__(other: int) -> JalaliOffset

Multiply offset by integer.

Source code in jalali_pandas/offsets/base.py
74
75
76
77
78
def __mul__(self, other: int) -> JalaliOffset:
    """Multiply offset by integer."""
    if isinstance(other, int):
        return type(self)(n=self._n * other, normalize=self._normalize)
    return NotImplemented

__neg__

__neg__() -> JalaliOffset

Return negated offset.

Source code in jalali_pandas/offsets/base.py
70
71
72
def __neg__(self) -> JalaliOffset:
    """Return negated offset."""
    return type(self)(n=-self._n, normalize=self._normalize)

__radd__

__radd__(other: JalaliTimestamp) -> JalaliTimestamp

Right add offset to a JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
89
90
91
def __radd__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Right add offset to a JalaliTimestamp."""
    return self.__add__(other)

__repr__

__repr__() -> str

String representation.

Source code in jalali_pandas/offsets/base.py
56
57
58
def __repr__(self) -> str:
    """String representation."""
    return f"<{self.__class__.__name__}: n={self._n}>"

__rmul__

__rmul__(other: int) -> JalaliOffset

Right multiply offset by integer.

Source code in jalali_pandas/offsets/base.py
80
81
82
def __rmul__(self, other: int) -> JalaliOffset:
    """Right multiply offset by integer."""
    return self.__mul__(other)

__sub__ abstractmethod

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract offset from a JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
93
94
95
96
@abstractmethod
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract offset from a JalaliTimestamp."""
    ...

is_on_offset abstractmethod

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on offset boundary.

Parameters:

Name Type Description Default
dt JalaliTimestamp

JalaliTimestamp to check.

required

Returns:

Type Description
bool

True if on offset boundary.

Source code in jalali_pandas/offsets/base.py
122
123
124
125
126
127
128
129
130
131
132
@abstractmethod
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on offset boundary.

    Args:
        dt: JalaliTimestamp to check.

    Returns:
        True if on offset boundary.
    """
    ...

rollback abstractmethod

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous valid date.

Parameters:

Name Type Description Default
dt JalaliTimestamp

JalaliTimestamp to roll back.

required

Returns:

Type Description
JalaliTimestamp

Rolled back JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
110
111
112
113
114
115
116
117
118
119
120
@abstractmethod
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous valid date.

    Args:
        dt: JalaliTimestamp to roll back.

    Returns:
        Rolled back JalaliTimestamp.
    """
    ...

rollforward abstractmethod

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next valid date.

Parameters:

Name Type Description Default
dt JalaliTimestamp

JalaliTimestamp to roll forward.

required

Returns:

Type Description
JalaliTimestamp

Rolled forward JalaliTimestamp.

Source code in jalali_pandas/offsets/base.py
 98
 99
100
101
102
103
104
105
106
107
108
@abstractmethod
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next valid date.

    Args:
        dt: JalaliTimestamp to roll forward.

    Returns:
        Rolled forward JalaliTimestamp.
    """
    ...

Jalali month offsets.

JalaliMonthBegin

Bases: JalaliOffset

Offset to the beginning of a Jalali month.

Source code in jalali_pandas/offsets/month.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class JalaliMonthBegin(JalaliOffset):
    """Offset to the beginning of a Jalali month."""

    _prefix = "JMS"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add months to timestamp, landing on month start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Calculate target month
        total_months = other.year * 12 + other.month - 1 + self._n
        new_year = total_months // 12
        new_month = total_months % 12 + 1

        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=1,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract months from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next month start if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self.__add__(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous month start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt
        return JalaliTimestamp(
            year=dt.year,
            month=dt.month,
            day=1,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on month start."""
        return dt.day == 1

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add months to timestamp, landing on month start.

Source code in jalali_pandas/offsets/month.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add months to timestamp, landing on month start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Calculate target month
    total_months = other.year * 12 + other.month - 1 + self._n
    new_year = total_months // 12
    new_month = total_months % 12 + 1

    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=1,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract months from timestamp.

Source code in jalali_pandas/offsets/month.py
43
44
45
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract months from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on month start.

Source code in jalali_pandas/offsets/month.py
69
70
71
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on month start."""
    return dt.day == 1

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous month start.

Source code in jalali_pandas/offsets/month.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous month start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt
    return JalaliTimestamp(
        year=dt.year,
        month=dt.month,
        day=1,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next month start if not already on one.

Source code in jalali_pandas/offsets/month.py
47
48
49
50
51
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next month start if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self.__add__(dt)

JalaliMonthEnd

Bases: JalaliOffset

Offset to the end of a Jalali month.

Source code in jalali_pandas/offsets/month.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
class JalaliMonthEnd(JalaliOffset):
    """Offset to the end of a Jalali month."""

    _prefix = "JME"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add months to timestamp, landing on month end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Calculate target month
        total_months = other.year * 12 + other.month - 1 + self._n
        new_year = total_months // 12
        new_month = total_months % 12 + 1
        new_day = days_in_month(new_year, new_month)

        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=new_day,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract months from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next month end if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self._get_month_end(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous month end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        # Go to previous month's end
        if dt.month == 1:
            new_year = dt.year - 1
            new_month = 12
        else:
            new_year = dt.year
            new_month = dt.month - 1

        new_day = days_in_month(new_year, new_month)
        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=new_day,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on month end."""
        return dt.day == days_in_month(dt.year, dt.month)

    def _get_month_end(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Get the end of the current month."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        return JalaliTimestamp(
            year=dt.year,
            month=dt.month,
            day=days_in_month(dt.year, dt.month),
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add months to timestamp, landing on month end.

Source code in jalali_pandas/offsets/month.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add months to timestamp, landing on month end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Calculate target month
    total_months = other.year * 12 + other.month - 1 + self._n
    new_year = total_months // 12
    new_month = total_months % 12 + 1
    new_day = days_in_month(new_year, new_month)

    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=new_day,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract months from timestamp.

Source code in jalali_pandas/offsets/month.py
104
105
106
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract months from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on month end.

Source code in jalali_pandas/offsets/month.py
140
141
142
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on month end."""
    return dt.day == days_in_month(dt.year, dt.month)

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous month end.

Source code in jalali_pandas/offsets/month.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous month end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    # Go to previous month's end
    if dt.month == 1:
        new_year = dt.year - 1
        new_month = 12
    else:
        new_year = dt.year
        new_month = dt.month - 1

    new_day = days_in_month(new_year, new_month)
    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=new_day,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next month end if not already on one.

Source code in jalali_pandas/offsets/month.py
108
109
110
111
112
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next month end if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self._get_month_end(dt)

Jalali quarter offsets.

JalaliQuarterBegin

Bases: JalaliOffset

Offset to the beginning of a Jalali quarter.

Source code in jalali_pandas/offsets/quarter.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class JalaliQuarterBegin(JalaliOffset):
    """Offset to the beginning of a Jalali quarter."""

    _prefix = "JQS"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add quarters to timestamp, landing on quarter start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Calculate current quarter (0-indexed)
        current_quarter = (other.month - 1) // 3

        # Calculate target quarter
        total_quarters = other.year * 4 + current_quarter + self._n
        new_year = total_quarters // 4
        new_quarter = total_quarters % 4
        new_month = new_quarter * 3 + 1

        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=1,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract quarters from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next quarter start if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self.__add__(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to current quarter start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        quarter_start_month = ((dt.month - 1) // 3) * 3 + 1
        return JalaliTimestamp(
            year=dt.year,
            month=quarter_start_month,
            day=1,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on quarter start."""
        return dt.month in QUARTER_START_MONTHS and dt.day == 1

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add quarters to timestamp, landing on quarter start.

Source code in jalali_pandas/offsets/quarter.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add quarters to timestamp, landing on quarter start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Calculate current quarter (0-indexed)
    current_quarter = (other.month - 1) // 3

    # Calculate target quarter
    total_quarters = other.year * 4 + current_quarter + self._n
    new_year = total_quarters // 4
    new_quarter = total_quarters % 4
    new_month = new_quarter * 3 + 1

    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=1,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract quarters from timestamp.

Source code in jalali_pandas/offsets/quarter.py
52
53
54
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract quarters from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on quarter start.

Source code in jalali_pandas/offsets/quarter.py
80
81
82
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on quarter start."""
    return dt.month in QUARTER_START_MONTHS and dt.day == 1

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to current quarter start.

Source code in jalali_pandas/offsets/quarter.py
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to current quarter start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    quarter_start_month = ((dt.month - 1) // 3) * 3 + 1
    return JalaliTimestamp(
        year=dt.year,
        month=quarter_start_month,
        day=1,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next quarter start if not already on one.

Source code in jalali_pandas/offsets/quarter.py
56
57
58
59
60
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next quarter start if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self.__add__(dt)

JalaliQuarterEnd

Bases: JalaliOffset

Offset to the end of a Jalali quarter.

Source code in jalali_pandas/offsets/quarter.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
class JalaliQuarterEnd(JalaliOffset):
    """Offset to the end of a Jalali quarter."""

    _prefix = "JQE"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add quarters to timestamp, landing on quarter end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Calculate current quarter (0-indexed)
        current_quarter = (other.month - 1) // 3

        # Calculate target quarter
        total_quarters = other.year * 4 + current_quarter + self._n
        new_year = total_quarters // 4
        new_quarter = total_quarters % 4
        new_month = (new_quarter + 1) * 3  # End month of quarter
        new_day = days_in_month(new_year, new_month)

        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=new_day,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract quarters from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next quarter end if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self._get_quarter_end(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous quarter end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        # Go to previous quarter's end
        current_quarter = (dt.month - 1) // 3
        if current_quarter == 0:
            new_year = dt.year - 1
            new_month = 12
        else:
            new_year = dt.year
            new_month = current_quarter * 3

        new_day = days_in_month(new_year, new_month)
        return JalaliTimestamp(
            year=new_year,
            month=new_month,
            day=new_day,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on quarter end."""
        if dt.month not in QUARTER_END_MONTHS:
            return False
        return dt.day == days_in_month(dt.year, dt.month)

    def _get_quarter_end(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Get the end of the current quarter."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        quarter_end_month = ((dt.month - 1) // 3 + 1) * 3
        return JalaliTimestamp(
            year=dt.year,
            month=quarter_end_month,
            day=days_in_month(dt.year, quarter_end_month),
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add quarters to timestamp, landing on quarter end.

Source code in jalali_pandas/offsets/quarter.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add quarters to timestamp, landing on quarter end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Calculate current quarter (0-indexed)
    current_quarter = (other.month - 1) // 3

    # Calculate target quarter
    total_quarters = other.year * 4 + current_quarter + self._n
    new_year = total_quarters // 4
    new_quarter = total_quarters % 4
    new_month = (new_quarter + 1) * 3  # End month of quarter
    new_day = days_in_month(new_year, new_month)

    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=new_day,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract quarters from timestamp.

Source code in jalali_pandas/offsets/quarter.py
119
120
121
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract quarters from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on quarter end.

Source code in jalali_pandas/offsets/quarter.py
156
157
158
159
160
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on quarter end."""
    if dt.month not in QUARTER_END_MONTHS:
        return False
    return dt.day == days_in_month(dt.year, dt.month)

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous quarter end.

Source code in jalali_pandas/offsets/quarter.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous quarter end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    # Go to previous quarter's end
    current_quarter = (dt.month - 1) // 3
    if current_quarter == 0:
        new_year = dt.year - 1
        new_month = 12
    else:
        new_year = dt.year
        new_month = current_quarter * 3

    new_day = days_in_month(new_year, new_month)
    return JalaliTimestamp(
        year=new_year,
        month=new_month,
        day=new_day,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next quarter end if not already on one.

Source code in jalali_pandas/offsets/quarter.py
123
124
125
126
127
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next quarter end if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self._get_quarter_end(dt)

Jalali year offsets.

JalaliYearBegin

Bases: JalaliOffset

Offset to the beginning of a Jalali year (Nowruz).

Source code in jalali_pandas/offsets/year.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class JalaliYearBegin(JalaliOffset):
    """Offset to the beginning of a Jalali year (Nowruz)."""

    _prefix = "JYS"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add years to timestamp, landing on year start (Nowruz)."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        new_year = other.year + self._n

        return JalaliTimestamp(
            year=new_year,
            month=1,
            day=1,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract years from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next year start if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self.__add__(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to current year start."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        return JalaliTimestamp(
            year=dt.year,
            month=1,
            day=1,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on year start (Nowruz - 1 Farvardin)."""
        return dt.month == 1 and dt.day == 1

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add years to timestamp, landing on year start (Nowruz).

Source code in jalali_pandas/offsets/year.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add years to timestamp, landing on year start (Nowruz)."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    new_year = other.year + self._n

    return JalaliTimestamp(
        year=new_year,
        month=1,
        day=1,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract years from timestamp.

Source code in jalali_pandas/offsets/year.py
40
41
42
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract years from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on year start (Nowruz - 1 Farvardin).

Source code in jalali_pandas/offsets/year.py
67
68
69
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on year start (Nowruz - 1 Farvardin)."""
    return dt.month == 1 and dt.day == 1

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to current year start.

Source code in jalali_pandas/offsets/year.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to current year start."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    return JalaliTimestamp(
        year=dt.year,
        month=1,
        day=1,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next year start if not already on one.

Source code in jalali_pandas/offsets/year.py
44
45
46
47
48
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next year start if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self.__add__(dt)

JalaliYearEnd

Bases: JalaliOffset

Offset to the end of a Jalali year.

Source code in jalali_pandas/offsets/year.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
class JalaliYearEnd(JalaliOffset):
    """Offset to the end of a Jalali year."""

    _prefix = "JYE"

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add years to timestamp, landing on year end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        new_year = other.year + self._n
        # Last day of Esfand (29 or 30 depending on leap year)
        new_day = 30 if is_leap_year(new_year) else 29

        return JalaliTimestamp(
            year=new_year,
            month=12,
            day=new_day,
            hour=other.hour if not self._normalize else 0,
            minute=other.minute if not self._normalize else 0,
            second=other.second if not self._normalize else 0,
            microsecond=other.microsecond if not self._normalize else 0,
            nanosecond=other.nanosecond if not self._normalize else 0,
            tzinfo=other.tzinfo,
        )

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract years from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next year end if not already on one."""
        if self.is_on_offset(dt):
            return dt
        return self._get_year_end(dt)

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous year end."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if self.is_on_offset(dt):
            return dt

        # Go to previous year's end
        new_year = dt.year - 1
        new_day = 30 if is_leap_year(new_year) else 29

        return JalaliTimestamp(
            year=new_year,
            month=12,
            day=new_day,
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on year end (last day of Esfand)."""
        if dt.month != 12:
            return False
        return dt.day == days_in_month(dt.year, 12)

    def _get_year_end(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Get the end of the current year."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        return JalaliTimestamp(
            year=dt.year,
            month=12,
            day=days_in_month(dt.year, 12),
            hour=dt.hour if not self._normalize else 0,
            minute=dt.minute if not self._normalize else 0,
            second=dt.second if not self._normalize else 0,
            tzinfo=dt.tzinfo,
        )

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add years to timestamp, landing on year end.

Source code in jalali_pandas/offsets/year.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add years to timestamp, landing on year end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    new_year = other.year + self._n
    # Last day of Esfand (29 or 30 depending on leap year)
    new_day = 30 if is_leap_year(new_year) else 29

    return JalaliTimestamp(
        year=new_year,
        month=12,
        day=new_day,
        hour=other.hour if not self._normalize else 0,
        minute=other.minute if not self._normalize else 0,
        second=other.second if not self._normalize else 0,
        microsecond=other.microsecond if not self._normalize else 0,
        nanosecond=other.nanosecond if not self._normalize else 0,
        tzinfo=other.tzinfo,
    )

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract years from timestamp.

Source code in jalali_pandas/offsets/year.py
100
101
102
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract years from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on year end (last day of Esfand).

Source code in jalali_pandas/offsets/year.py
131
132
133
134
135
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on year end (last day of Esfand)."""
    if dt.month != 12:
        return False
    return dt.day == days_in_month(dt.year, 12)

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous year end.

Source code in jalali_pandas/offsets/year.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous year end."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if self.is_on_offset(dt):
        return dt

    # Go to previous year's end
    new_year = dt.year - 1
    new_day = 30 if is_leap_year(new_year) else 29

    return JalaliTimestamp(
        year=new_year,
        month=12,
        day=new_day,
        hour=dt.hour if not self._normalize else 0,
        minute=dt.minute if not self._normalize else 0,
        second=dt.second if not self._normalize else 0,
        tzinfo=dt.tzinfo,
    )

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next year end if not already on one.

Source code in jalali_pandas/offsets/year.py
104
105
106
107
108
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next year end if not already on one."""
    if self.is_on_offset(dt):
        return dt
    return self._get_year_end(dt)

Jalali week offset.

JalaliWeek

Bases: JalaliOffset

Offset to a specific day of the Jalali week.

The Jalali week starts on Saturday (weekday=0) and ends on Friday (weekday=6).

Attributes:

Name Type Description
weekday int

The target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).

Source code in jalali_pandas/offsets/week.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class JalaliWeek(JalaliOffset):
    """Offset to a specific day of the Jalali week.

    The Jalali week starts on Saturday (weekday=0) and ends on Friday (weekday=6).

    Attributes:
        weekday: The target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).
    """

    _prefix = "JW"
    _attributes = ("n", "normalize", "weekday")

    def __init__(
        self,
        n: int = 1,
        normalize: bool = False,
        weekday: int = SATURDAY,
    ) -> None:
        """Initialize JalaliWeek offset.

        Args:
            n: Number of weeks. Defaults to 1.
            normalize: Whether to normalize to midnight. Defaults to False.
            weekday: Target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).

        Raises:
            ValueError: If weekday is not in range 0-6.
        """
        super().__init__(n=n, normalize=normalize)
        if not 0 <= weekday <= 6:
            raise ValueError(f"weekday must be 0-6, got {weekday}")
        self._weekday = weekday

    @property
    def weekday(self) -> int:
        """Target weekday (0=Saturday, 6=Friday)."""
        return self._weekday

    def __repr__(self) -> str:
        """String representation."""
        return f"<{self.__class__.__name__}: n={self._n}, weekday={self._weekday}>"

    def __eq__(self, other: object) -> bool:
        """Check equality."""
        if isinstance(other, JalaliWeek):
            return (
                type(self) is type(other)
                and self._n == other._n
                and self._weekday == other._weekday
            )
        return False

    def __hash__(self) -> int:
        """Hash for use in sets and dicts."""
        return hash((type(self).__name__, self._n, self._weekday))

    def __neg__(self) -> JalaliWeek:
        """Return negated offset."""
        return JalaliWeek(n=-self._n, normalize=self._normalize, weekday=self._weekday)

    def __mul__(self, other: int) -> JalaliWeek:
        """Multiply offset by integer."""
        if isinstance(other, int):
            return JalaliWeek(
                n=self._n * other, normalize=self._normalize, weekday=self._weekday
            )
        return NotImplemented

    def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Add weeks to timestamp, landing on target weekday."""
        from jalali_pandas.core.timestamp import JalaliTimestamp

        if not isinstance(other, JalaliTimestamp):
            return NotImplemented

        # Get current weekday (0=Saturday, 6=Friday)
        current_weekday = other.dayofweek

        # Calculate days to target weekday
        days_to_target = (self._weekday - current_weekday) % 7

        # If we're already on the target weekday and n > 0, we need to go forward
        # If n < 0, we need to go backward
        if self._n > 0:
            # Move forward n weeks, landing on target weekday
            if days_to_target == 0:
                # Already on target, move n full weeks
                total_days = self._n * 7
            else:
                # Move to next target weekday, then (n-1) more weeks
                total_days = days_to_target + (self._n - 1) * 7
        elif self._n < 0:
            # Move backward |n| weeks, landing on target weekday
            if days_to_target == 0:
                # Already on target, move |n| full weeks back
                total_days = self._n * 7
            else:
                # Move to previous target weekday, then (|n|-1) more weeks back
                days_to_prev_target = days_to_target - 7  # Negative
                total_days = days_to_prev_target + (self._n + 1) * 7
        else:
            # n == 0, just move to nearest target weekday (forward)
            total_days = days_to_target

        # Apply the offset using timedelta
        new_gregorian = other.to_gregorian() + timedelta(days=total_days)
        result = JalaliTimestamp.from_gregorian(new_gregorian)

        if self._normalize:
            result = result.normalize()

        return result

    def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
        """Subtract weeks from timestamp."""
        return self.__neg__().__add__(other)

    def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll forward to next target weekday if not already on one."""
        if self.is_on_offset(dt):
            return dt

        # Calculate days to next target weekday
        current_weekday = dt.dayofweek
        days_forward = (self._weekday - current_weekday) % 7
        if days_forward == 0:
            days_forward = 7  # Move to next week if already on target

        new_gregorian = dt.to_gregorian() + timedelta(days=days_forward)
        result = type(dt).from_gregorian(new_gregorian)

        if self._normalize:
            result = result.normalize()

        return result

    def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
        """Roll back to previous target weekday."""
        if self.is_on_offset(dt):
            return dt

        # Calculate days to previous target weekday
        current_weekday = dt.dayofweek
        days_back = (current_weekday - self._weekday) % 7
        if days_back == 0:
            days_back = 7  # Move to previous week if already on target

        new_gregorian = dt.to_gregorian() - timedelta(days=days_back)
        result = type(dt).from_gregorian(new_gregorian)

        if self._normalize:
            result = result.normalize()

        return result

    def is_on_offset(self, dt: JalaliTimestamp) -> bool:
        """Check if date is on target weekday."""
        return dt.dayofweek == self._weekday

weekday property

weekday: int

Target weekday (0=Saturday, 6=Friday).

__add__

__add__(other: JalaliTimestamp) -> JalaliTimestamp

Add weeks to timestamp, landing on target weekday.

Source code in jalali_pandas/offsets/week.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def __add__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Add weeks to timestamp, landing on target weekday."""
    from jalali_pandas.core.timestamp import JalaliTimestamp

    if not isinstance(other, JalaliTimestamp):
        return NotImplemented

    # Get current weekday (0=Saturday, 6=Friday)
    current_weekday = other.dayofweek

    # Calculate days to target weekday
    days_to_target = (self._weekday - current_weekday) % 7

    # If we're already on the target weekday and n > 0, we need to go forward
    # If n < 0, we need to go backward
    if self._n > 0:
        # Move forward n weeks, landing on target weekday
        if days_to_target == 0:
            # Already on target, move n full weeks
            total_days = self._n * 7
        else:
            # Move to next target weekday, then (n-1) more weeks
            total_days = days_to_target + (self._n - 1) * 7
    elif self._n < 0:
        # Move backward |n| weeks, landing on target weekday
        if days_to_target == 0:
            # Already on target, move |n| full weeks back
            total_days = self._n * 7
        else:
            # Move to previous target weekday, then (|n|-1) more weeks back
            days_to_prev_target = days_to_target - 7  # Negative
            total_days = days_to_prev_target + (self._n + 1) * 7
    else:
        # n == 0, just move to nearest target weekday (forward)
        total_days = days_to_target

    # Apply the offset using timedelta
    new_gregorian = other.to_gregorian() + timedelta(days=total_days)
    result = JalaliTimestamp.from_gregorian(new_gregorian)

    if self._normalize:
        result = result.normalize()

    return result

__eq__

__eq__(other: object) -> bool

Check equality.

Source code in jalali_pandas/offsets/week.py
65
66
67
68
69
70
71
72
73
def __eq__(self, other: object) -> bool:
    """Check equality."""
    if isinstance(other, JalaliWeek):
        return (
            type(self) is type(other)
            and self._n == other._n
            and self._weekday == other._weekday
        )
    return False

__hash__

__hash__() -> int

Hash for use in sets and dicts.

Source code in jalali_pandas/offsets/week.py
75
76
77
def __hash__(self) -> int:
    """Hash for use in sets and dicts."""
    return hash((type(self).__name__, self._n, self._weekday))

__init__

__init__(n: int = 1, normalize: bool = False, weekday: int = SATURDAY) -> None

Initialize JalaliWeek offset.

Parameters:

Name Type Description Default
n int

Number of weeks. Defaults to 1.

1
normalize bool

Whether to normalize to midnight. Defaults to False.

False
weekday int

Target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).

SATURDAY

Raises:

Type Description
ValueError

If weekday is not in range 0-6.

Source code in jalali_pandas/offsets/week.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def __init__(
    self,
    n: int = 1,
    normalize: bool = False,
    weekday: int = SATURDAY,
) -> None:
    """Initialize JalaliWeek offset.

    Args:
        n: Number of weeks. Defaults to 1.
        normalize: Whether to normalize to midnight. Defaults to False.
        weekday: Target weekday (0=Saturday, 6=Friday). Defaults to 0 (Saturday).

    Raises:
        ValueError: If weekday is not in range 0-6.
    """
    super().__init__(n=n, normalize=normalize)
    if not 0 <= weekday <= 6:
        raise ValueError(f"weekday must be 0-6, got {weekday}")
    self._weekday = weekday

__mul__

__mul__(other: int) -> JalaliWeek

Multiply offset by integer.

Source code in jalali_pandas/offsets/week.py
83
84
85
86
87
88
89
def __mul__(self, other: int) -> JalaliWeek:
    """Multiply offset by integer."""
    if isinstance(other, int):
        return JalaliWeek(
            n=self._n * other, normalize=self._normalize, weekday=self._weekday
        )
    return NotImplemented

__neg__

__neg__() -> JalaliWeek

Return negated offset.

Source code in jalali_pandas/offsets/week.py
79
80
81
def __neg__(self) -> JalaliWeek:
    """Return negated offset."""
    return JalaliWeek(n=-self._n, normalize=self._normalize, weekday=self._weekday)

__repr__

__repr__() -> str

String representation.

Source code in jalali_pandas/offsets/week.py
61
62
63
def __repr__(self) -> str:
    """String representation."""
    return f"<{self.__class__.__name__}: n={self._n}, weekday={self._weekday}>"

__sub__

__sub__(other: JalaliTimestamp) -> JalaliTimestamp

Subtract weeks from timestamp.

Source code in jalali_pandas/offsets/week.py
136
137
138
def __sub__(self, other: JalaliTimestamp) -> JalaliTimestamp:
    """Subtract weeks from timestamp."""
    return self.__neg__().__add__(other)

is_on_offset

is_on_offset(dt: JalaliTimestamp) -> bool

Check if date is on target weekday.

Source code in jalali_pandas/offsets/week.py
178
179
180
def is_on_offset(self, dt: JalaliTimestamp) -> bool:
    """Check if date is on target weekday."""
    return dt.dayofweek == self._weekday

rollback

rollback(dt: JalaliTimestamp) -> JalaliTimestamp

Roll back to previous target weekday.

Source code in jalali_pandas/offsets/week.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def rollback(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll back to previous target weekday."""
    if self.is_on_offset(dt):
        return dt

    # Calculate days to previous target weekday
    current_weekday = dt.dayofweek
    days_back = (current_weekday - self._weekday) % 7
    if days_back == 0:
        days_back = 7  # Move to previous week if already on target

    new_gregorian = dt.to_gregorian() - timedelta(days=days_back)
    result = type(dt).from_gregorian(new_gregorian)

    if self._normalize:
        result = result.normalize()

    return result

rollforward

rollforward(dt: JalaliTimestamp) -> JalaliTimestamp

Roll forward to next target weekday if not already on one.

Source code in jalali_pandas/offsets/week.py
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
def rollforward(self, dt: JalaliTimestamp) -> JalaliTimestamp:
    """Roll forward to next target weekday if not already on one."""
    if self.is_on_offset(dt):
        return dt

    # Calculate days to next target weekday
    current_weekday = dt.dayofweek
    days_forward = (self._weekday - current_weekday) % 7
    if days_forward == 0:
        days_forward = 7  # Move to next week if already on target

    new_gregorian = dt.to_gregorian() + timedelta(days=days_forward)
    result = type(dt).from_gregorian(new_gregorian)

    if self._normalize:
        result = result.normalize()

    return result