Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/arrow/arrow.py: 26%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

558 statements  

1""" 

2Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime`` 

3replacement. 

4 

5""" 

6 

7 

8import calendar 

9import re 

10import sys 

11from datetime import date 

12from datetime import datetime as dt_datetime 

13from datetime import time as dt_time 

14from datetime import timedelta, timezone 

15from datetime import tzinfo as dt_tzinfo 

16from math import trunc 

17from time import struct_time 

18from typing import ( 

19 Any, 

20 ClassVar, 

21 Final, 

22 Generator, 

23 Iterable, 

24 List, 

25 Literal, 

26 Mapping, 

27 Optional, 

28 Tuple, 

29 Union, 

30 cast, 

31 overload, 

32) 

33 

34from dateutil import tz as dateutil_tz 

35from dateutil.relativedelta import relativedelta 

36 

37from arrow import formatter, locales, parser, util 

38from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES 

39from arrow.locales import TimeFrameLiteral 

40 

41TZ_EXPR = Union[dt_tzinfo, str] 

42 

43_T_FRAMES = Literal[ 

44 "year", 

45 "years", 

46 "month", 

47 "months", 

48 "day", 

49 "days", 

50 "hour", 

51 "hours", 

52 "minute", 

53 "minutes", 

54 "second", 

55 "seconds", 

56 "microsecond", 

57 "microseconds", 

58 "week", 

59 "weeks", 

60 "quarter", 

61 "quarters", 

62] 

63 

64_BOUNDS = Literal["[)", "()", "(]", "[]"] 

65 

66_GRANULARITY = Literal[ 

67 "auto", 

68 "second", 

69 "minute", 

70 "hour", 

71 "day", 

72 "week", 

73 "month", 

74 "quarter", 

75 "year", 

76] 

77 

78 

79class Arrow: 

80 """An :class:`Arrow <arrow.arrow.Arrow>` object. 

81 

82 Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing 

83 additional functionality. 

84 

85 :param year: the calendar year. 

86 :param month: the calendar month. 

87 :param day: the calendar day. 

88 :param hour: (optional) the hour. Defaults to 0. 

89 :param minute: (optional) the minute, Defaults to 0. 

90 :param second: (optional) the second, Defaults to 0. 

91 :param microsecond: (optional) the microsecond. Defaults to 0. 

92 :param tzinfo: (optional) A timezone expression. Defaults to UTC. 

93 :param fold: (optional) 0 or 1, used to disambiguate repeated wall times. Defaults to 0. 

94 

95 .. _tz-expr: 

96 

97 Recognized timezone expressions: 

98 

99 - A ``tzinfo`` object. 

100 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'. 

101 - A ``str`` in ISO 8601 style, as in '+07:00'. 

102 - A ``str``, one of the following: 'local', 'utc', 'UTC'. 

103 

104 Usage:: 

105 

106 >>> import arrow 

107 >>> arrow.Arrow(2013, 5, 5, 12, 30, 45) 

108 <Arrow [2013-05-05T12:30:45+00:00]> 

109 

110 """ 

111 

112 resolution: ClassVar[timedelta] = dt_datetime.resolution 

113 min: ClassVar["Arrow"] 

114 max: ClassVar["Arrow"] 

115 

116 _ATTRS: Final[List[str]] = [ 

117 "year", 

118 "month", 

119 "day", 

120 "hour", 

121 "minute", 

122 "second", 

123 "microsecond", 

124 ] 

125 _ATTRS_PLURAL: Final[List[str]] = [f"{a}s" for a in _ATTRS] 

126 _MONTHS_PER_QUARTER: Final[int] = 3 

127 _SECS_PER_MINUTE: Final[int] = 60 

128 _SECS_PER_HOUR: Final[int] = 60 * 60 

129 _SECS_PER_DAY: Final[int] = 60 * 60 * 24 

130 _SECS_PER_WEEK: Final[int] = 60 * 60 * 24 * 7 

131 _SECS_PER_MONTH: Final[float] = 60 * 60 * 24 * 30.5 

132 _SECS_PER_QUARTER: Final[float] = 60 * 60 * 24 * 30.5 * 3 

133 _SECS_PER_YEAR: Final[int] = 60 * 60 * 24 * 365 

134 

135 _SECS_MAP: Final[Mapping[TimeFrameLiteral, float]] = { 

136 "second": 1.0, 

137 "minute": _SECS_PER_MINUTE, 

138 "hour": _SECS_PER_HOUR, 

139 "day": _SECS_PER_DAY, 

140 "week": _SECS_PER_WEEK, 

141 "month": _SECS_PER_MONTH, 

142 "quarter": _SECS_PER_QUARTER, 

143 "year": _SECS_PER_YEAR, 

144 } 

145 

146 _datetime: dt_datetime 

147 

148 def __init__( 

149 self, 

150 year: int, 

151 month: int, 

152 day: int, 

153 hour: int = 0, 

154 minute: int = 0, 

155 second: int = 0, 

156 microsecond: int = 0, 

157 tzinfo: Optional[TZ_EXPR] = None, 

158 **kwargs: Any, 

159 ) -> None: 

160 if tzinfo is None: 

161 tzinfo = dateutil_tz.tzutc() 

162 # detect that tzinfo is a pytz object (issue #626) 

163 elif ( 

164 isinstance(tzinfo, dt_tzinfo) 

165 and hasattr(tzinfo, "localize") 

166 and hasattr(tzinfo, "zone") 

167 and tzinfo.zone 

168 ): 

169 tzinfo = parser.TzinfoParser.parse(tzinfo.zone) 

170 elif isinstance(tzinfo, str): 

171 tzinfo = parser.TzinfoParser.parse(tzinfo) 

172 

173 fold = kwargs.get("fold", 0) 

174 

175 self._datetime = dt_datetime( 

176 year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold 

177 ) 

178 

179 # factories: single object, both original and from datetime. 

180 

181 @classmethod 

182 def now(cls, tzinfo: Optional[dt_tzinfo] = None) -> "Arrow": 

183 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in the given 

184 timezone. 

185 

186 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. 

187 

188 Usage:: 

189 

190 >>> arrow.now('Asia/Baku') 

191 <Arrow [2019-01-24T20:26:31.146412+04:00]> 

192 

193 """ 

194 

195 if tzinfo is None: 

196 tzinfo = dateutil_tz.tzlocal() 

197 

198 dt = dt_datetime.now(tzinfo) 

199 

200 return cls( 

201 dt.year, 

202 dt.month, 

203 dt.day, 

204 dt.hour, 

205 dt.minute, 

206 dt.second, 

207 dt.microsecond, 

208 dt.tzinfo, 

209 fold=getattr(dt, "fold", 0), 

210 ) 

211 

212 @classmethod 

213 def utcnow(cls) -> "Arrow": 

214 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC 

215 time. 

216 

217 Usage:: 

218 

219 >>> arrow.utcnow() 

220 <Arrow [2019-01-24T16:31:40.651108+00:00]> 

221 

222 """ 

223 

224 dt = dt_datetime.now(dateutil_tz.tzutc()) 

225 

226 return cls( 

227 dt.year, 

228 dt.month, 

229 dt.day, 

230 dt.hour, 

231 dt.minute, 

232 dt.second, 

233 dt.microsecond, 

234 dt.tzinfo, 

235 fold=getattr(dt, "fold", 0), 

236 ) 

237 

238 @classmethod 

239 def fromtimestamp( 

240 cls, 

241 timestamp: Union[int, float, str], 

242 tzinfo: Optional[TZ_EXPR] = None, 

243 ) -> "Arrow": 

244 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, converted to 

245 the given timezone. 

246 

247 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. 

248 :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time. 

249 

250 """ 

251 

252 if tzinfo is None: 

253 tzinfo = dateutil_tz.tzlocal() 

254 elif isinstance(tzinfo, str): 

255 tzinfo = parser.TzinfoParser.parse(tzinfo) 

256 

257 if not util.is_timestamp(timestamp): 

258 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") 

259 

260 timestamp = util.normalize_timestamp(float(timestamp)) 

261 dt = dt_datetime.fromtimestamp(timestamp, tzinfo) 

262 

263 return cls( 

264 dt.year, 

265 dt.month, 

266 dt.day, 

267 dt.hour, 

268 dt.minute, 

269 dt.second, 

270 dt.microsecond, 

271 dt.tzinfo, 

272 fold=getattr(dt, "fold", 0), 

273 ) 

274 

275 @classmethod 

276 def utcfromtimestamp(cls, timestamp: Union[int, float, str]) -> "Arrow": 

277 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time. 

278 

279 :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either. 

280 

281 """ 

282 

283 if not util.is_timestamp(timestamp): 

284 raise ValueError(f"The provided timestamp {timestamp!r} is invalid.") 

285 

286 timestamp = util.normalize_timestamp(float(timestamp)) 

287 dt = dt_datetime.utcfromtimestamp(timestamp) 

288 

289 return cls( 

290 dt.year, 

291 dt.month, 

292 dt.day, 

293 dt.hour, 

294 dt.minute, 

295 dt.second, 

296 dt.microsecond, 

297 dateutil_tz.tzutc(), 

298 fold=getattr(dt, "fold", 0), 

299 ) 

300 

301 @classmethod 

302 def fromdatetime(cls, dt: dt_datetime, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": 

303 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and 

304 optional replacement timezone. 

305 

306 :param dt: the ``datetime`` 

307 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to ``dt``'s 

308 timezone, or UTC if naive. 

309 

310 Usage:: 

311 

312 >>> dt 

313 datetime.datetime(2021, 4, 7, 13, 48, tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific')) 

314 >>> arrow.Arrow.fromdatetime(dt) 

315 <Arrow [2021-04-07T13:48:00-07:00]> 

316 

317 """ 

318 

319 if tzinfo is None: 

320 if dt.tzinfo is None: 

321 tzinfo = dateutil_tz.tzutc() 

322 else: 

323 tzinfo = dt.tzinfo 

324 

325 return cls( 

326 dt.year, 

327 dt.month, 

328 dt.day, 

329 dt.hour, 

330 dt.minute, 

331 dt.second, 

332 dt.microsecond, 

333 tzinfo, 

334 fold=getattr(dt, "fold", 0), 

335 ) 

336 

337 @classmethod 

338 def fromdate(cls, date: date, tzinfo: Optional[TZ_EXPR] = None) -> "Arrow": 

339 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional 

340 replacement timezone. All time values are set to 0. 

341 

342 :param date: the ``date`` 

343 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to UTC. 

344 

345 """ 

346 

347 if tzinfo is None: 

348 tzinfo = dateutil_tz.tzutc() 

349 

350 return cls(date.year, date.month, date.day, tzinfo=tzinfo) 

351 

352 @classmethod 

353 def strptime( 

354 cls, date_str: str, fmt: str, tzinfo: Optional[TZ_EXPR] = None 

355 ) -> "Arrow": 

356 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format, 

357 in the style of ``datetime.strptime``. Optionally replaces the parsed timezone. 

358 

359 :param date_str: the date string. 

360 :param fmt: the format string using datetime format codes. 

361 :param tzinfo: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to the parsed 

362 timezone if ``fmt`` contains a timezone directive, otherwise UTC. 

363 

364 Usage:: 

365 

366 >>> arrow.Arrow.strptime('20-01-2019 15:49:10', '%d-%m-%Y %H:%M:%S') 

367 <Arrow [2019-01-20T15:49:10+00:00]> 

368 

369 """ 

370 

371 dt = dt_datetime.strptime(date_str, fmt) 

372 if tzinfo is None: 

373 tzinfo = dt.tzinfo 

374 

375 return cls( 

376 dt.year, 

377 dt.month, 

378 dt.day, 

379 dt.hour, 

380 dt.minute, 

381 dt.second, 

382 dt.microsecond, 

383 tzinfo, 

384 fold=getattr(dt, "fold", 0), 

385 ) 

386 

387 @classmethod 

388 def fromordinal(cls, ordinal: int) -> "Arrow": 

389 """Constructs an :class:`Arrow <arrow.arrow.Arrow>` object corresponding 

390 to the Gregorian Ordinal. 

391 

392 :param ordinal: an ``int`` corresponding to a Gregorian Ordinal. 

393 

394 Usage:: 

395 

396 >>> arrow.fromordinal(737741) 

397 <Arrow [2020-11-12T00:00:00+00:00]> 

398 

399 """ 

400 

401 util.validate_ordinal(ordinal) 

402 dt = dt_datetime.fromordinal(ordinal) 

403 return cls( 

404 dt.year, 

405 dt.month, 

406 dt.day, 

407 dt.hour, 

408 dt.minute, 

409 dt.second, 

410 dt.microsecond, 

411 dt.tzinfo, 

412 fold=getattr(dt, "fold", 0), 

413 ) 

414 

415 # factories: ranges and spans 

416 

417 @classmethod 

418 def range( 

419 cls, 

420 frame: _T_FRAMES, 

421 start: Union["Arrow", dt_datetime], 

422 end: Union["Arrow", dt_datetime, None] = None, 

423 tz: Optional[TZ_EXPR] = None, 

424 limit: Optional[int] = None, 

425 ) -> Generator["Arrow", None, None]: 

426 """Returns an iterator of :class:`Arrow <arrow.arrow.Arrow>` objects, representing 

427 points in time between two inputs. 

428 

429 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). 

430 :param start: A datetime expression, the start of the range. 

431 :param end: (optional) A datetime expression, the end of the range. 

432 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to 

433 ``start``'s timezone, or UTC if ``start`` is naive. 

434 :param limit: (optional) A maximum number of tuples to return. 

435 

436 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to 

437 return the entire range. Call with ``limit`` alone to return a maximum # of results from 

438 the start. Call with both to cap a range at a maximum # of results. 

439 

440 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before 

441 iterating. As such, either call with naive objects and ``tz``, or aware objects from the 

442 same timezone and no ``tz``. 

443 

444 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond. 

445 

446 Recognized datetime expressions: 

447 

448 - An :class:`Arrow <arrow.arrow.Arrow>` object. 

449 - A ``datetime`` object. 

450 

451 Usage:: 

452 

453 >>> start = datetime(2013, 5, 5, 12, 30) 

454 >>> end = datetime(2013, 5, 5, 17, 15) 

455 >>> for r in arrow.Arrow.range('hour', start, end): 

456 ... print(repr(r)) 

457 ... 

458 <Arrow [2013-05-05T12:30:00+00:00]> 

459 <Arrow [2013-05-05T13:30:00+00:00]> 

460 <Arrow [2013-05-05T14:30:00+00:00]> 

461 <Arrow [2013-05-05T15:30:00+00:00]> 

462 <Arrow [2013-05-05T16:30:00+00:00]> 

463 

464 **NOTE**: Unlike Python's ``range``, ``end`` *may* be included in the returned iterator:: 

465 

466 >>> start = datetime(2013, 5, 5, 12, 30) 

467 >>> end = datetime(2013, 5, 5, 13, 30) 

468 >>> for r in arrow.Arrow.range('hour', start, end): 

469 ... print(repr(r)) 

470 ... 

471 <Arrow [2013-05-05T12:30:00+00:00]> 

472 <Arrow [2013-05-05T13:30:00+00:00]> 

473 

474 """ 

475 

476 _, frame_relative, relative_steps = cls._get_frames(frame) 

477 

478 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz) 

479 

480 start = cls._get_datetime(start).replace(tzinfo=tzinfo) 

481 end, limit = cls._get_iteration_params(end, limit) 

482 end = cls._get_datetime(end).replace(tzinfo=tzinfo) 

483 

484 current = cls.fromdatetime(start) 

485 original_day = start.day 

486 day_is_clipped = False 

487 i = 0 

488 

489 while current <= end and i < limit: 

490 i += 1 

491 yield current 

492 

493 values = [getattr(current, f) for f in cls._ATTRS] 

494 current = cls(*values, tzinfo=tzinfo).shift( # type: ignore[misc] 

495 **{frame_relative: relative_steps} 

496 ) 

497 

498 if frame in ["month", "quarter", "year"] and current.day < original_day: 

499 day_is_clipped = True 

500 

501 if day_is_clipped and not cls._is_last_day_of_month(current): 

502 current = current.replace(day=original_day) 

503 

504 def span( 

505 self, 

506 frame: _T_FRAMES, 

507 count: int = 1, 

508 bounds: _BOUNDS = "[)", 

509 exact: bool = False, 

510 week_start: int = 1, 

511 ) -> Tuple["Arrow", "Arrow"]: 

512 """Returns a tuple of two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan 

513 of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. 

514 

515 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). 

516 :param count: (optional) the number of frames to span. 

517 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies 

518 whether to include or exclude the start and end values in the span. '(' excludes 

519 the start, '[' includes the start, ')' excludes the end, and ']' includes the end. 

520 If the bounds are not specified, the default bound '[)' is used. 

521 :param exact: (optional) whether to have the start of the timespan begin exactly 

522 at the time specified by ``start`` and the end of the timespan truncated 

523 so as not to extend beyond ``end``. 

524 :param week_start: (optional) only used in combination with the week timeframe. Follows isoweekday() where 

525 Monday is 1 and Sunday is 7. 

526 

527 Supported frame values: year, quarter, month, week, day, hour, minute, second. 

528 

529 Usage:: 

530 

531 >>> arrow.utcnow() 

532 <Arrow [2013-05-09T03:32:36.186203+00:00]> 

533 

534 >>> arrow.utcnow().span('hour') 

535 (<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>) 

536 

537 >>> arrow.utcnow().span('day') 

538 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>) 

539 

540 >>> arrow.utcnow().span('day', count=2) 

541 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>) 

542 

543 >>> arrow.utcnow().span('day', bounds='[]') 

544 (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T00:00:00+00:00]>) 

545 

546 >>> arrow.utcnow().span('week') 

547 (<Arrow [2021-02-22T00:00:00+00:00]>, <Arrow [2021-02-28T23:59:59.999999+00:00]>) 

548 

549 >>> arrow.utcnow().span('week', week_start=6) 

550 (<Arrow [2021-02-20T00:00:00+00:00]>, <Arrow [2021-02-26T23:59:59.999999+00:00]>) 

551 

552 """ 

553 if not 1 <= week_start <= 7: 

554 raise ValueError("week_start argument must be between 1 and 7.") 

555 

556 util.validate_bounds(bounds) 

557 

558 frame_absolute, frame_relative, relative_steps = self._get_frames(frame) 

559 

560 if frame_absolute == "week": 

561 attr = "day" 

562 elif frame_absolute == "quarter": 

563 attr = "month" 

564 else: 

565 attr = frame_absolute 

566 

567 floor = self 

568 if not exact: 

569 index = self._ATTRS.index(attr) 

570 frames = self._ATTRS[: index + 1] 

571 

572 values = [getattr(self, f) for f in frames] 

573 

574 for _ in range(3 - len(values)): 

575 values.append(1) 

576 

577 floor = self.__class__(*values, tzinfo=self.tzinfo) # type: ignore[misc] 

578 

579 if frame_absolute == "week": 

580 # if week_start is greater than self.isoweekday() go back one week by setting delta = 7 

581 delta = 7 if week_start > self.isoweekday() else 0 

582 floor = floor.shift(days=-(self.isoweekday() - week_start) - delta) 

583 elif frame_absolute == "quarter": 

584 floor = floor.shift(months=-((self.month - 1) % 3)) 

585 

586 ceil = floor.shift(**{frame_relative: count * relative_steps}) 

587 

588 if bounds[0] == "(": 

589 floor = floor.shift(microseconds=+1) 

590 

591 if bounds[1] == ")": 

592 ceil = ceil.shift(microseconds=-1) 

593 

594 return floor, ceil 

595 

596 def floor(self, frame: _T_FRAMES) -> "Arrow": 

597 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor" 

598 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. 

599 Equivalent to the first element in the 2-tuple returned by 

600 :func:`span <arrow.arrow.Arrow.span>`. 

601 

602 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). 

603 

604 Usage:: 

605 

606 >>> arrow.utcnow().floor('hour') 

607 <Arrow [2013-05-09T03:00:00+00:00]> 

608 

609 """ 

610 

611 return self.span(frame)[0] 

612 

613 def ceil(self, frame: _T_FRAMES) -> "Arrow": 

614 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling" 

615 of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe. 

616 Equivalent to the second element in the 2-tuple returned by 

617 :func:`span <arrow.arrow.Arrow.span>`. 

618 

619 :param frame: the timeframe. Can be any ``datetime`` property (day, hour, minute...). 

620 

621 Usage:: 

622 

623 >>> arrow.utcnow().ceil('hour') 

624 <Arrow [2013-05-09T03:59:59.999999+00:00]> 

625 

626 """ 

627 

628 return self.span(frame)[1] 

629 

630 @classmethod 

631 def span_range( 

632 cls, 

633 frame: _T_FRAMES, 

634 start: dt_datetime, 

635 end: dt_datetime, 

636 tz: Optional[TZ_EXPR] = None, 

637 limit: Optional[int] = None, 

638 bounds: _BOUNDS = "[)", 

639 exact: bool = False, 

640 ) -> Iterable[Tuple["Arrow", "Arrow"]]: 

641 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects, 

642 representing a series of timespans between two inputs. 

643 

644 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). 

645 :param start: A datetime expression, the start of the range. 

646 :param end: (optional) A datetime expression, the end of the range. 

647 :param tz: (optional) A :ref:`timezone expression <tz-expr>`. Defaults to 

648 ``start``'s timezone, or UTC if ``start`` is naive. 

649 :param limit: (optional) A maximum number of tuples to return. 

650 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies 

651 whether to include or exclude the start and end values in each span in the range. '(' excludes 

652 the start, '[' includes the start, ')' excludes the end, and ']' includes the end. 

653 If the bounds are not specified, the default bound '[)' is used. 

654 :param exact: (optional) whether to have the first timespan start exactly 

655 at the time specified by ``start`` and the final span truncated 

656 so as not to extend beyond ``end``. 

657 

658 **NOTE**: The ``end`` or ``limit`` must be provided. Call with ``end`` alone to 

659 return the entire range. Call with ``limit`` alone to return a maximum # of results from 

660 the start. Call with both to cap a range at a maximum # of results. 

661 

662 **NOTE**: ``tz`` internally **replaces** the timezones of both ``start`` and ``end`` before 

663 iterating. As such, either call with naive objects and ``tz``, or aware objects from the 

664 same timezone and no ``tz``. 

665 

666 Supported frame values: year, quarter, month, week, day, hour, minute, second, microsecond. 

667 

668 Recognized datetime expressions: 

669 

670 - An :class:`Arrow <arrow.arrow.Arrow>` object. 

671 - A ``datetime`` object. 

672 

673 **NOTE**: Unlike Python's ``range``, ``end`` will *always* be included in the returned 

674 iterator of timespans. 

675 

676 Usage: 

677 

678 >>> start = datetime(2013, 5, 5, 12, 30) 

679 >>> end = datetime(2013, 5, 5, 17, 15) 

680 >>> for r in arrow.Arrow.span_range('hour', start, end): 

681 ... print(r) 

682 ... 

683 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>) 

684 (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>) 

685 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>) 

686 (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>) 

687 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>) 

688 (<Arrow [2013-05-05T17:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:00]>) 

689 

690 """ 

691 

692 tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz) 

693 start = cls.fromdatetime(start, tzinfo).span(frame, exact=exact)[0] 

694 end = cls.fromdatetime(end, tzinfo) 

695 _range = cls.range(frame, start, end, tz, limit) 

696 if not exact: 

697 for r in _range: 

698 yield r.span(frame, bounds=bounds, exact=exact) 

699 

700 for r in _range: 

701 floor, ceil = r.span(frame, bounds=bounds, exact=exact) 

702 if ceil > end: 

703 ceil = end 

704 if bounds[1] == ")": 

705 ceil += relativedelta(microseconds=-1) 

706 if floor == end: 

707 break 

708 elif floor + relativedelta(microseconds=-1) == end: 

709 break 

710 yield floor, ceil 

711 

712 @classmethod 

713 def interval( 

714 cls, 

715 frame: _T_FRAMES, 

716 start: dt_datetime, 

717 end: dt_datetime, 

718 interval: int = 1, 

719 tz: Optional[TZ_EXPR] = None, 

720 bounds: _BOUNDS = "[)", 

721 exact: bool = False, 

722 ) -> Iterable[Tuple["Arrow", "Arrow"]]: 

723 """Returns an iterator of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects, 

724 representing a series of intervals between two inputs. 

725 

726 :param frame: The timeframe. Can be any ``datetime`` property (day, hour, minute...). 

727 :param start: A datetime expression, the start of the range. 

728 :param end: (optional) A datetime expression, the end of the range. 

729 :param interval: (optional) Time interval for the given time frame. 

730 :param tz: (optional) A timezone expression. Defaults to UTC. 

731 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies 

732 whether to include or exclude the start and end values in the intervals. '(' excludes 

733 the start, '[' includes the start, ')' excludes the end, and ']' includes the end. 

734 If the bounds are not specified, the default bound '[)' is used. 

735 :param exact: (optional) whether to have the first timespan start exactly 

736 at the time specified by ``start`` and the final interval truncated 

737 so as not to extend beyond ``end``. 

738 

739 Supported frame values: year, quarter, month, week, day, hour, minute, second 

740 

741 Recognized datetime expressions: 

742 

743 - An :class:`Arrow <arrow.arrow.Arrow>` object. 

744 - A ``datetime`` object. 

745 

746 Recognized timezone expressions: 

747 

748 - A ``tzinfo`` object. 

749 - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'. 

750 - A ``str`` in ISO 8601 style, as in '+07:00'. 

751 - A ``str``, one of the following: 'local', 'utc', 'UTC'. 

752 

753 Usage: 

754 

755 >>> start = datetime(2013, 5, 5, 12, 30) 

756 >>> end = datetime(2013, 5, 5, 17, 15) 

757 >>> for r in arrow.Arrow.interval('hour', start, end, 2): 

758 ... print(r) 

759 ... 

760 (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>) 

761 (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>) 

762 (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T17:59:59.999999+00:0]>) 

763 """ 

764 if interval < 1: 

765 raise ValueError("interval has to be a positive integer") 

766 

767 spanRange = iter( 

768 cls.span_range(frame, start, end, tz, bounds=bounds, exact=exact) 

769 ) 

770 while True: 

771 try: 

772 intvlStart, intvlEnd = next(spanRange) 

773 for _ in range(interval - 1): 

774 try: 

775 _, intvlEnd = next(spanRange) 

776 except StopIteration: 

777 continue 

778 yield intvlStart, intvlEnd 

779 except StopIteration: 

780 return 

781 

782 # representations 

783 

784 def __repr__(self) -> str: 

785 return f"<{self.__class__.__name__} [{self.__str__()}]>" 

786 

787 def __str__(self) -> str: 

788 return self._datetime.isoformat() 

789 

790 def __format__(self, formatstr: str) -> str: 

791 if len(formatstr) > 0: 

792 return self.format(formatstr) 

793 

794 return str(self) 

795 

796 def __hash__(self) -> int: 

797 return self._datetime.__hash__() 

798 

799 # attributes and properties 

800 

801 def __getattr__(self, name: str) -> int: 

802 if name == "week": 

803 return self.isocalendar()[1] 

804 

805 if name == "quarter": 

806 return int((self.month - 1) / self._MONTHS_PER_QUARTER) + 1 

807 

808 if not name.startswith("_"): 

809 value: Optional[int] = getattr(self._datetime, name, None) 

810 

811 if value is not None: 

812 return value 

813 

814 return cast(int, object.__getattribute__(self, name)) 

815 

816 @property 

817 def tzinfo(self) -> dt_tzinfo: 

818 """Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object. 

819 

820 Usage:: 

821 

822 >>> arw=arrow.utcnow() 

823 >>> arw.tzinfo 

824 tzutc() 

825 

826 """ 

827 

828 # In Arrow, `_datetime` cannot be naive. 

829 return cast(dt_tzinfo, self._datetime.tzinfo) 

830 

831 @property 

832 def datetime(self) -> dt_datetime: 

833 """Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object. 

834 

835 Usage:: 

836 

837 >>> arw=arrow.utcnow() 

838 >>> arw.datetime 

839 datetime.datetime(2019, 1, 24, 16, 35, 27, 276649, tzinfo=tzutc()) 

840 

841 """ 

842 

843 return self._datetime 

844 

845 @property 

846 def naive(self) -> dt_datetime: 

847 """Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` 

848 object. 

849 

850 Usage:: 

851 

852 >>> nairobi = arrow.now('Africa/Nairobi') 

853 >>> nairobi 

854 <Arrow [2019-01-23T19:27:12.297999+03:00]> 

855 >>> nairobi.naive 

856 datetime.datetime(2019, 1, 23, 19, 27, 12, 297999) 

857 

858 """ 

859 

860 return self._datetime.replace(tzinfo=None) 

861 

862 def timestamp(self) -> float: 

863 """Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in 

864 UTC time. 

865 

866 Usage:: 

867 

868 >>> arrow.utcnow().timestamp() 

869 1616882340.256501 

870 

871 """ 

872 

873 return self._datetime.timestamp() 

874 

875 @property 

876 def int_timestamp(self) -> int: 

877 """Returns an integer timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object, in 

878 UTC time. 

879 

880 Usage:: 

881 

882 >>> arrow.utcnow().int_timestamp 

883 1548260567 

884 

885 """ 

886 

887 return int(self.timestamp()) 

888 

889 @property 

890 def float_timestamp(self) -> float: 

891 """Returns a floating-point timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` 

892 object, in UTC time. 

893 

894 Usage:: 

895 

896 >>> arrow.utcnow().float_timestamp 

897 1548260516.830896 

898 

899 """ 

900 

901 return self.timestamp() 

902 

903 @property 

904 def fold(self) -> int: 

905 """Returns the ``fold`` value of the :class:`Arrow <arrow.arrow.Arrow>` object.""" 

906 

907 return self._datetime.fold 

908 

909 @property 

910 def ambiguous(self) -> bool: 

911 """Indicates whether the :class:`Arrow <arrow.arrow.Arrow>` object is a repeated wall time in the current 

912 timezone. 

913 

914 """ 

915 

916 return dateutil_tz.datetime_ambiguous(self._datetime) 

917 

918 @property 

919 def imaginary(self) -> bool: 

920 """Indicates whether the :class: `Arrow <arrow.arrow.Arrow>` object exists in the current timezone.""" 

921 

922 return not dateutil_tz.datetime_exists(self._datetime) 

923 

924 # mutation and duplication. 

925 

926 def clone(self) -> "Arrow": 

927 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one. 

928 

929 Usage: 

930 

931 >>> arw = arrow.utcnow() 

932 >>> cloned = arw.clone() 

933 

934 """ 

935 

936 return self.fromdatetime(self._datetime) 

937 

938 def replace(self, **kwargs: Any) -> "Arrow": 

939 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated 

940 according to inputs. 

941 

942 Use property names to set their value absolutely:: 

943 

944 >>> import arrow 

945 >>> arw = arrow.utcnow() 

946 >>> arw 

947 <Arrow [2013-05-11T22:27:34.787885+00:00]> 

948 >>> arw.replace(year=2014, month=6) 

949 <Arrow [2014-06-11T22:27:34.787885+00:00]> 

950 

951 You can also replace the timezone without conversion, using a 

952 :ref:`timezone expression <tz-expr>`:: 

953 

954 >>> arw.replace(tzinfo=tz.tzlocal()) 

955 <Arrow [2013-05-11T22:27:34.787885-07:00]> 

956 

957 """ 

958 

959 absolute_kwargs = {} 

960 

961 for key, value in kwargs.items(): 

962 if key in self._ATTRS: 

963 absolute_kwargs[key] = value 

964 elif key in ["week", "quarter"]: 

965 raise ValueError(f"Setting absolute {key} is not supported.") 

966 elif key not in ["tzinfo", "fold"]: 

967 raise ValueError(f"Unknown attribute: {key!r}.") 

968 

969 current = self._datetime.replace(**absolute_kwargs) 

970 

971 tzinfo = kwargs.get("tzinfo") 

972 

973 if tzinfo is not None: 

974 tzinfo = self._get_tzinfo(tzinfo) 

975 current = current.replace(tzinfo=tzinfo) 

976 

977 fold = kwargs.get("fold") 

978 

979 if fold is not None: 

980 current = current.replace(fold=fold) 

981 

982 return self.fromdatetime(current) 

983 

984 def shift(self, **kwargs: Any) -> "Arrow": 

985 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated 

986 according to inputs. 

987 

988 Use pluralized property names to relatively shift their current value: 

989 

990 >>> import arrow 

991 >>> arw = arrow.utcnow() 

992 >>> arw 

993 <Arrow [2013-05-11T22:27:34.787885+00:00]> 

994 >>> arw.shift(years=1, months=-1) 

995 <Arrow [2014-04-11T22:27:34.787885+00:00]> 

996 

997 Day-of-the-week relative shifting can use either Python's weekday numbers 

998 (Monday = 0, Tuesday = 1 .. Sunday = 6) or using dateutil.relativedelta's 

999 day instances (MO, TU .. SU). When using weekday numbers, the returned 

1000 date will always be greater than or equal to the starting date. 

1001 

1002 Using the above code (which is a Saturday) and asking it to shift to Saturday: 

1003 

1004 >>> arw.shift(weekday=5) 

1005 <Arrow [2013-05-11T22:27:34.787885+00:00]> 

1006 

1007 While asking for a Monday: 

1008 

1009 >>> arw.shift(weekday=0) 

1010 <Arrow [2013-05-13T22:27:34.787885+00:00]> 

1011 

1012 """ 

1013 

1014 relative_kwargs = {} 

1015 additional_attrs = ["weeks", "quarters", "weekday"] 

1016 

1017 for key, value in kwargs.items(): 

1018 if key in self._ATTRS_PLURAL or key in additional_attrs: 

1019 relative_kwargs[key] = value 

1020 else: 

1021 supported_attr = ", ".join(self._ATTRS_PLURAL + additional_attrs) 

1022 raise ValueError( 

1023 f"Invalid shift time frame. Please select one of the following: {supported_attr}." 

1024 ) 

1025 

1026 # core datetime does not support quarters, translate to months. 

1027 relative_kwargs.setdefault("months", 0) 

1028 relative_kwargs["months"] += ( 

1029 relative_kwargs.pop("quarters", 0) * self._MONTHS_PER_QUARTER 

1030 ) 

1031 

1032 current = self._datetime + relativedelta(**relative_kwargs) 

1033 

1034 if not dateutil_tz.datetime_exists(current): 

1035 current = dateutil_tz.resolve_imaginary(current) 

1036 

1037 return self.fromdatetime(current) 

1038 

1039 def to(self, tz: TZ_EXPR) -> "Arrow": 

1040 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted 

1041 to the target timezone. 

1042 

1043 :param tz: A :ref:`timezone expression <tz-expr>`. 

1044 

1045 Usage:: 

1046 

1047 >>> utc = arrow.utcnow() 

1048 >>> utc 

1049 <Arrow [2013-05-09T03:49:12.311072+00:00]> 

1050 

1051 >>> utc.to('US/Pacific') 

1052 <Arrow [2013-05-08T20:49:12.311072-07:00]> 

1053 

1054 >>> utc.to(tz.tzlocal()) 

1055 <Arrow [2013-05-08T20:49:12.311072-07:00]> 

1056 

1057 >>> utc.to('-07:00') 

1058 <Arrow [2013-05-08T20:49:12.311072-07:00]> 

1059 

1060 >>> utc.to('local') 

1061 <Arrow [2013-05-08T20:49:12.311072-07:00]> 

1062 

1063 >>> utc.to('local').to('utc') 

1064 <Arrow [2013-05-09T03:49:12.311072+00:00]> 

1065 

1066 """ 

1067 

1068 if not isinstance(tz, dt_tzinfo): 

1069 tz = parser.TzinfoParser.parse(tz) 

1070 

1071 dt = self._datetime.astimezone(tz) 

1072 

1073 return self.__class__( 

1074 dt.year, 

1075 dt.month, 

1076 dt.day, 

1077 dt.hour, 

1078 dt.minute, 

1079 dt.second, 

1080 dt.microsecond, 

1081 dt.tzinfo, 

1082 fold=getattr(dt, "fold", 0), 

1083 ) 

1084 

1085 # string output and formatting 

1086 

1087 def format( 

1088 self, fmt: str = "YYYY-MM-DD HH:mm:ssZZ", locale: str = DEFAULT_LOCALE 

1089 ) -> str: 

1090 """Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object, 

1091 formatted according to the provided format string. For a list of formatting values, 

1092 see :ref:`supported-tokens` 

1093 

1094 :param fmt: the format string. 

1095 :param locale: the locale to format. 

1096 

1097 Usage:: 

1098 

1099 >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ') 

1100 '2013-05-09 03:56:47 -00:00' 

1101 

1102 >>> arrow.utcnow().format('X') 

1103 '1368071882' 

1104 

1105 >>> arrow.utcnow().format('MMMM DD, YYYY') 

1106 'May 09, 2013' 

1107 

1108 >>> arrow.utcnow().format() 

1109 '2013-05-09 03:56:47 -00:00' 

1110 

1111 """ 

1112 

1113 return formatter.DateTimeFormatter(locale).format(self._datetime, fmt) 

1114 

1115 def humanize( 

1116 self, 

1117 other: Union["Arrow", dt_datetime, None] = None, 

1118 locale: str = DEFAULT_LOCALE, 

1119 only_distance: bool = False, 

1120 granularity: Union[_GRANULARITY, List[_GRANULARITY]] = "auto", 

1121 ) -> str: 

1122 """Returns a localized, humanized representation of a relative difference in time. 

1123 

1124 :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object. 

1125 Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone. 

1126 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'. 

1127 :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part. 

1128 :param granularity: (optional) defines the precision of the output. Set it to strings 'second', 'minute', 

1129 'hour', 'day', 'week', 'month' or 'year' or a list of any combination of these strings 

1130 

1131 Usage:: 

1132 

1133 >>> earlier = arrow.utcnow().shift(hours=-2) 

1134 >>> earlier.humanize() 

1135 '2 hours ago' 

1136 

1137 >>> later = earlier.shift(hours=4) 

1138 >>> later.humanize(earlier) 

1139 'in 4 hours' 

1140 

1141 """ 

1142 

1143 locale_name = locale 

1144 locale = locales.get_locale(locale) 

1145 

1146 if other is None: 

1147 utc = dt_datetime.now(timezone.utc).replace(tzinfo=dateutil_tz.tzutc()) 

1148 dt = utc.astimezone(self._datetime.tzinfo) 

1149 

1150 elif isinstance(other, Arrow): 

1151 dt = other._datetime 

1152 

1153 elif isinstance(other, dt_datetime): 

1154 if other.tzinfo is None: 

1155 dt = other.replace(tzinfo=self._datetime.tzinfo) 

1156 else: 

1157 dt = other.astimezone(self._datetime.tzinfo) 

1158 

1159 else: 

1160 raise TypeError( 

1161 f"Invalid 'other' argument of type {type(other).__name__!r}. " 

1162 "Argument must be of type None, Arrow, or datetime." 

1163 ) 

1164 

1165 if isinstance(granularity, list) and len(granularity) == 1: 

1166 granularity = granularity[0] 

1167 

1168 _delta = int(round((self._datetime - dt).total_seconds())) 

1169 sign = -1 if _delta < 0 else 1 

1170 delta_second = diff = abs(_delta) 

1171 

1172 try: 

1173 if granularity == "auto": 

1174 if diff < 10: 

1175 return locale.describe("now", only_distance=only_distance) 

1176 

1177 if diff < self._SECS_PER_MINUTE: 

1178 seconds = sign * delta_second 

1179 return locale.describe( 

1180 "seconds", seconds, only_distance=only_distance 

1181 ) 

1182 

1183 elif diff < self._SECS_PER_MINUTE * 2: 

1184 return locale.describe("minute", sign, only_distance=only_distance) 

1185 elif diff < self._SECS_PER_HOUR: 

1186 minutes = sign * max(delta_second // self._SECS_PER_MINUTE, 2) 

1187 return locale.describe( 

1188 "minutes", minutes, only_distance=only_distance 

1189 ) 

1190 

1191 elif diff < self._SECS_PER_HOUR * 2: 

1192 return locale.describe("hour", sign, only_distance=only_distance) 

1193 elif diff < self._SECS_PER_DAY: 

1194 hours = sign * max(delta_second // self._SECS_PER_HOUR, 2) 

1195 return locale.describe("hours", hours, only_distance=only_distance) 

1196 elif diff < self._SECS_PER_DAY * 2: 

1197 return locale.describe("day", sign, only_distance=only_distance) 

1198 elif diff < self._SECS_PER_WEEK: 

1199 days = sign * max(delta_second // self._SECS_PER_DAY, 2) 

1200 return locale.describe("days", days, only_distance=only_distance) 

1201 

1202 elif diff < self._SECS_PER_WEEK * 2: 

1203 return locale.describe("week", sign, only_distance=only_distance) 

1204 elif diff < self._SECS_PER_MONTH: 

1205 weeks = sign * max(delta_second // self._SECS_PER_WEEK, 2) 

1206 return locale.describe("weeks", weeks, only_distance=only_distance) 

1207 

1208 elif diff < self._SECS_PER_MONTH * 2: 

1209 return locale.describe("month", sign, only_distance=only_distance) 

1210 elif diff < self._SECS_PER_YEAR: 

1211 # TODO revisit for humanization during leap years 

1212 self_months = self._datetime.year * 12 + self._datetime.month 

1213 other_months = dt.year * 12 + dt.month 

1214 

1215 months = sign * max(abs(other_months - self_months), 2) 

1216 

1217 return locale.describe( 

1218 "months", months, only_distance=only_distance 

1219 ) 

1220 

1221 elif diff < self._SECS_PER_YEAR * 2: 

1222 return locale.describe("year", sign, only_distance=only_distance) 

1223 else: 

1224 years = sign * max(delta_second // self._SECS_PER_YEAR, 2) 

1225 return locale.describe("years", years, only_distance=only_distance) 

1226 

1227 elif isinstance(granularity, str): 

1228 granularity = cast(TimeFrameLiteral, granularity) # type: ignore[assignment] 

1229 

1230 if granularity == "second": 

1231 delta = sign * float(delta_second) 

1232 if abs(delta) < 2: 

1233 return locale.describe("now", only_distance=only_distance) 

1234 elif granularity == "minute": 

1235 delta = sign * delta_second / self._SECS_PER_MINUTE 

1236 elif granularity == "hour": 

1237 delta = sign * delta_second / self._SECS_PER_HOUR 

1238 elif granularity == "day": 

1239 delta = sign * delta_second / self._SECS_PER_DAY 

1240 elif granularity == "week": 

1241 delta = sign * delta_second / self._SECS_PER_WEEK 

1242 elif granularity == "month": 

1243 delta = sign * delta_second / self._SECS_PER_MONTH 

1244 elif granularity == "quarter": 

1245 delta = sign * delta_second / self._SECS_PER_QUARTER 

1246 elif granularity == "year": 

1247 delta = sign * delta_second / self._SECS_PER_YEAR 

1248 else: 

1249 raise ValueError( 

1250 "Invalid level of granularity. " 

1251 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." 

1252 ) 

1253 

1254 if trunc(abs(delta)) != 1: 

1255 granularity += "s" # type: ignore[assignment] 

1256 return locale.describe(granularity, delta, only_distance=only_distance) 

1257 

1258 else: 

1259 if not granularity: 

1260 raise ValueError( 

1261 "Empty granularity list provided. " 

1262 "Please select one or more from 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'." 

1263 ) 

1264 

1265 timeframes: List[Tuple[TimeFrameLiteral, float]] = [] 

1266 

1267 def gather_timeframes(_delta: float, _frame: TimeFrameLiteral) -> float: 

1268 if _frame in granularity: 

1269 value = sign * _delta / self._SECS_MAP[_frame] 

1270 _delta %= self._SECS_MAP[_frame] 

1271 if trunc(abs(value)) != 1: 

1272 timeframes.append( 

1273 (cast(TimeFrameLiteral, _frame + "s"), value) 

1274 ) 

1275 else: 

1276 timeframes.append((_frame, value)) 

1277 return _delta 

1278 

1279 delta = float(delta_second) 

1280 frames: Tuple[TimeFrameLiteral, ...] = ( 

1281 "year", 

1282 "quarter", 

1283 "month", 

1284 "week", 

1285 "day", 

1286 "hour", 

1287 "minute", 

1288 "second", 

1289 ) 

1290 for frame in frames: 

1291 delta = gather_timeframes(delta, frame) 

1292 

1293 if len(timeframes) < len(granularity): 

1294 raise ValueError( 

1295 "Invalid level of granularity. " 

1296 "Please select between 'second', 'minute', 'hour', 'day', 'week', 'month', 'quarter' or 'year'." 

1297 ) 

1298 

1299 return locale.describe_multi(timeframes, only_distance=only_distance) 

1300 

1301 except KeyError as e: 

1302 raise ValueError( 

1303 f"Humanization of the {e} granularity is not currently translated in the {locale_name!r} locale. " 

1304 "Please consider making a contribution to this locale." 

1305 ) 

1306 

1307 def dehumanize(self, input_string: str, locale: str = "en_us") -> "Arrow": 

1308 """Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, that represents 

1309 the time difference relative to the attributes of the 

1310 :class:`Arrow <arrow.arrow.Arrow>` object. 

1311 

1312 :param timestring: a ``str`` representing a humanized relative time. 

1313 :param locale: (optional) a ``str`` specifying a locale. Defaults to 'en-us'. 

1314 

1315 Usage:: 

1316 

1317 >>> arw = arrow.utcnow() 

1318 >>> arw 

1319 <Arrow [2021-04-20T22:27:34.787885+00:00]> 

1320 >>> earlier = arw.dehumanize("2 days ago") 

1321 >>> earlier 

1322 <Arrow [2021-04-18T22:27:34.787885+00:00]> 

1323 

1324 >>> arw = arrow.utcnow() 

1325 >>> arw 

1326 <Arrow [2021-04-20T22:27:34.787885+00:00]> 

1327 >>> later = arw.dehumanize("in a month") 

1328 >>> later 

1329 <Arrow [2021-05-18T22:27:34.787885+00:00]> 

1330 

1331 """ 

1332 

1333 # Create a locale object based off given local 

1334 locale_obj = locales.get_locale(locale) 

1335 

1336 # Check to see if locale is supported 

1337 normalized_locale_name = locale.lower().replace("_", "-") 

1338 

1339 if normalized_locale_name not in DEHUMANIZE_LOCALES: 

1340 raise ValueError( 

1341 f"Dehumanize does not currently support the {locale} locale, please consider making a contribution to add support for this locale." 

1342 ) 

1343 

1344 current_time = self.fromdatetime(self._datetime) 

1345 

1346 # Create an object containing the relative time info 

1347 time_object_info = dict.fromkeys( 

1348 ["seconds", "minutes", "hours", "days", "weeks", "months", "years"], 0 

1349 ) 

1350 

1351 # Create an object representing if unit has been seen 

1352 unit_visited = dict.fromkeys( 

1353 ["now", "seconds", "minutes", "hours", "days", "weeks", "months", "years"], 

1354 False, 

1355 ) 

1356 

1357 # Create a regex pattern object for numbers 

1358 num_pattern = re.compile(r"\d+") 

1359 

1360 # Search input string for each time unit within locale 

1361 for unit, unit_object in locale_obj.timeframes.items(): 

1362 # Need to check the type of unit_object to create the correct dictionary 

1363 if isinstance(unit_object, Mapping): 

1364 strings_to_search = unit_object 

1365 else: 

1366 strings_to_search = {unit: str(unit_object)} 

1367 

1368 # Search for any matches that exist for that locale's unit. 

1369 # Needs to cycle all through strings as some locales have strings that 

1370 # could overlap in a regex match, since input validation isn't being performed. 

1371 for time_delta, time_string in strings_to_search.items(): 

1372 # Replace {0} with regex \d representing digits 

1373 search_string = str(time_string) 

1374 search_string = search_string.format(r"\d+") 

1375 

1376 # Create search pattern and find within string 

1377 pattern = re.compile(rf"(^|\b|\d){search_string}") 

1378 match = pattern.search(input_string) 

1379 

1380 # If there is no match continue to next iteration 

1381 if not match: 

1382 continue 

1383 

1384 match_string = match.group() 

1385 num_match = num_pattern.search(match_string) 

1386 

1387 # If no number matches 

1388 # Need for absolute value as some locales have signs included in their objects 

1389 if not num_match: 

1390 change_value = ( 

1391 1 if not time_delta.isnumeric() else abs(int(time_delta)) 

1392 ) 

1393 else: 

1394 change_value = int(num_match.group()) 

1395 

1396 # No time to update if now is the unit 

1397 if unit == "now": 

1398 unit_visited[unit] = True 

1399 continue 

1400 

1401 # Add change value to the correct unit (incorporates the plurality that exists within timeframe i.e second v.s seconds) 

1402 time_unit_to_change = str(unit) 

1403 time_unit_to_change += ( 

1404 "s" if (str(time_unit_to_change)[-1] != "s") else "" 

1405 ) 

1406 time_object_info[time_unit_to_change] = change_value 

1407 unit_visited[time_unit_to_change] = True 

1408 

1409 # Assert error if string does not modify any units 

1410 if not any([True for k, v in unit_visited.items() if v]): 

1411 raise ValueError( 

1412 "Input string not valid. Note: Some locales do not support the week granularity in Arrow. " 

1413 "If you are attempting to use the week granularity on an unsupported locale, this could be the cause of this error." 

1414 ) 

1415 

1416 # Sign logic 

1417 future_string = locale_obj.future 

1418 future_string = future_string.format(".*") 

1419 future_pattern = re.compile(rf"^{future_string}$") 

1420 future_pattern_match = future_pattern.findall(input_string) 

1421 

1422 past_string = locale_obj.past 

1423 past_string = past_string.format(".*") 

1424 past_pattern = re.compile(rf"^{past_string}$") 

1425 past_pattern_match = past_pattern.findall(input_string) 

1426 

1427 # If a string contains the now unit, there will be no relative units, hence the need to check if the now unit 

1428 # was visited before raising a ValueError 

1429 if past_pattern_match: 

1430 sign_val = -1 

1431 elif future_pattern_match: 

1432 sign_val = 1 

1433 elif unit_visited["now"]: 

1434 sign_val = 0 

1435 else: 

1436 raise ValueError( 

1437 "Invalid input String. String does not contain any relative time information. " 

1438 "String should either represent a time in the future or a time in the past. " 

1439 "Ex: 'in 5 seconds' or '5 seconds ago'." 

1440 ) 

1441 

1442 time_changes = {k: sign_val * v for k, v in time_object_info.items()} 

1443 

1444 return current_time.shift(**time_changes) 

1445 

1446 # query functions 

1447 

1448 def is_between( 

1449 self, 

1450 start: "Arrow", 

1451 end: "Arrow", 

1452 bounds: _BOUNDS = "()", 

1453 ) -> bool: 

1454 """Returns a boolean denoting whether the :class:`Arrow <arrow.arrow.Arrow>` object is between 

1455 the start and end limits. 

1456 

1457 :param start: an :class:`Arrow <arrow.arrow.Arrow>` object. 

1458 :param end: an :class:`Arrow <arrow.arrow.Arrow>` object. 

1459 :param bounds: (optional) a ``str`` of either '()', '(]', '[)', or '[]' that specifies 

1460 whether to include or exclude the start and end values in the range. '(' excludes 

1461 the start, '[' includes the start, ')' excludes the end, and ']' includes the end. 

1462 If the bounds are not specified, the default bound '()' is used. 

1463 

1464 Usage:: 

1465 

1466 >>> start = arrow.get(datetime(2013, 5, 5, 12, 30, 10)) 

1467 >>> end = arrow.get(datetime(2013, 5, 5, 12, 30, 36)) 

1468 >>> arrow.get(datetime(2013, 5, 5, 12, 30, 27)).is_between(start, end) 

1469 True 

1470 

1471 >>> start = arrow.get(datetime(2013, 5, 5)) 

1472 >>> end = arrow.get(datetime(2013, 5, 8)) 

1473 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[]') 

1474 True 

1475 

1476 >>> start = arrow.get(datetime(2013, 5, 5)) 

1477 >>> end = arrow.get(datetime(2013, 5, 8)) 

1478 >>> arrow.get(datetime(2013, 5, 8)).is_between(start, end, '[)') 

1479 False 

1480 

1481 """ 

1482 

1483 util.validate_bounds(bounds) 

1484 

1485 if not isinstance(start, Arrow): 

1486 raise TypeError( 

1487 f"Cannot parse start date argument type of {type(start)!r}." 

1488 ) 

1489 

1490 if not isinstance(end, Arrow): 

1491 raise TypeError(f"Cannot parse end date argument type of {type(start)!r}.") 

1492 

1493 include_start = bounds[0] == "[" 

1494 include_end = bounds[1] == "]" 

1495 

1496 target_ts = self.float_timestamp 

1497 start_ts = start.float_timestamp 

1498 end_ts = end.float_timestamp 

1499 

1500 return ( 

1501 (start_ts <= target_ts <= end_ts) 

1502 and (include_start or start_ts < target_ts) 

1503 and (include_end or target_ts < end_ts) 

1504 ) 

1505 

1506 # datetime methods 

1507 

1508 def date(self) -> date: 

1509 """Returns a ``date`` object with the same year, month and day. 

1510 

1511 Usage:: 

1512 

1513 >>> arrow.utcnow().date() 

1514 datetime.date(2019, 1, 23) 

1515 

1516 """ 

1517 

1518 return self._datetime.date() 

1519 

1520 def time(self) -> dt_time: 

1521 """Returns a ``time`` object with the same hour, minute, second, microsecond. 

1522 

1523 Usage:: 

1524 

1525 >>> arrow.utcnow().time() 

1526 datetime.time(12, 15, 34, 68352) 

1527 

1528 """ 

1529 

1530 return self._datetime.time() 

1531 

1532 def timetz(self) -> dt_time: 

1533 """Returns a ``time`` object with the same hour, minute, second, microsecond and 

1534 tzinfo. 

1535 

1536 Usage:: 

1537 

1538 >>> arrow.utcnow().timetz() 

1539 datetime.time(12, 5, 18, 298893, tzinfo=tzutc()) 

1540 

1541 """ 

1542 

1543 return self._datetime.timetz() 

1544 

1545 def astimezone(self, tz: Optional[dt_tzinfo]) -> dt_datetime: 

1546 """Returns a ``datetime`` object, converted to the specified timezone. 

1547 

1548 :param tz: a ``tzinfo`` object. 

1549 

1550 Usage:: 

1551 

1552 >>> pacific=arrow.now('US/Pacific') 

1553 >>> nyc=arrow.now('America/New_York').tzinfo 

1554 >>> pacific.astimezone(nyc) 

1555 datetime.datetime(2019, 1, 20, 10, 24, 22, 328172, tzinfo=tzfile('/usr/share/zoneinfo/America/New_York')) 

1556 

1557 """ 

1558 

1559 return self._datetime.astimezone(tz) 

1560 

1561 def utcoffset(self) -> Optional[timedelta]: 

1562 """Returns a ``timedelta`` object representing the whole number of minutes difference from 

1563 UTC time. 

1564 

1565 Usage:: 

1566 

1567 >>> arrow.now('US/Pacific').utcoffset() 

1568 datetime.timedelta(-1, 57600) 

1569 

1570 """ 

1571 

1572 return self._datetime.utcoffset() 

1573 

1574 def dst(self) -> Optional[timedelta]: 

1575 """Returns the daylight savings time adjustment. 

1576 

1577 Usage:: 

1578 

1579 >>> arrow.utcnow().dst() 

1580 datetime.timedelta(0) 

1581 

1582 """ 

1583 

1584 return self._datetime.dst() 

1585 

1586 def timetuple(self) -> struct_time: 

1587 """Returns a ``time.struct_time``, in the current timezone. 

1588 

1589 Usage:: 

1590 

1591 >>> arrow.utcnow().timetuple() 

1592 time.struct_time(tm_year=2019, tm_mon=1, tm_mday=20, tm_hour=15, tm_min=17, tm_sec=8, tm_wday=6, tm_yday=20, tm_isdst=0) 

1593 

1594 """ 

1595 

1596 return self._datetime.timetuple() 

1597 

1598 def utctimetuple(self) -> struct_time: 

1599 """Returns a ``time.struct_time``, in UTC time. 

1600 

1601 Usage:: 

1602 

1603 >>> arrow.utcnow().utctimetuple() 

1604 time.struct_time(tm_year=2019, tm_mon=1, tm_mday=19, tm_hour=21, tm_min=41, tm_sec=7, tm_wday=5, tm_yday=19, tm_isdst=0) 

1605 

1606 """ 

1607 

1608 return self._datetime.utctimetuple() 

1609 

1610 def toordinal(self) -> int: 

1611 """Returns the proleptic Gregorian ordinal of the date. 

1612 

1613 Usage:: 

1614 

1615 >>> arrow.utcnow().toordinal() 

1616 737078 

1617 

1618 """ 

1619 

1620 return self._datetime.toordinal() 

1621 

1622 def weekday(self) -> int: 

1623 """Returns the day of the week as an integer (0-6). 

1624 

1625 Usage:: 

1626 

1627 >>> arrow.utcnow().weekday() 

1628 5 

1629 

1630 """ 

1631 

1632 return self._datetime.weekday() 

1633 

1634 def isoweekday(self) -> int: 

1635 """Returns the ISO day of the week as an integer (1-7). 

1636 

1637 Usage:: 

1638 

1639 >>> arrow.utcnow().isoweekday() 

1640 6 

1641 

1642 """ 

1643 

1644 return self._datetime.isoweekday() 

1645 

1646 def isocalendar(self) -> Tuple[int, int, int]: 

1647 """Returns a 3-tuple, (ISO year, ISO week number, ISO weekday). 

1648 

1649 Usage:: 

1650 

1651 >>> arrow.utcnow().isocalendar() 

1652 (2019, 3, 6) 

1653 

1654 """ 

1655 

1656 return self._datetime.isocalendar() 

1657 

1658 def isoformat(self, sep: str = "T", timespec: str = "auto") -> str: 

1659 """Returns an ISO 8601 formatted representation of the date and time. 

1660 

1661 Usage:: 

1662 

1663 >>> arrow.utcnow().isoformat() 

1664 '2019-01-19T18:30:52.442118+00:00' 

1665 

1666 """ 

1667 

1668 return self._datetime.isoformat(sep, timespec) 

1669 

1670 def ctime(self) -> str: 

1671 """Returns a ctime formatted representation of the date and time. 

1672 

1673 Usage:: 

1674 

1675 >>> arrow.utcnow().ctime() 

1676 'Sat Jan 19 18:26:50 2019' 

1677 

1678 """ 

1679 

1680 return self._datetime.ctime() 

1681 

1682 def strftime(self, format: str) -> str: 

1683 """Formats in the style of ``datetime.strftime``. 

1684 

1685 :param format: the format string. 

1686 

1687 Usage:: 

1688 

1689 >>> arrow.utcnow().strftime('%d-%m-%Y %H:%M:%S') 

1690 '23-01-2019 12:28:17' 

1691 

1692 """ 

1693 

1694 return self._datetime.strftime(format) 

1695 

1696 def for_json(self) -> str: 

1697 """Serializes for the ``for_json`` protocol of simplejson. 

1698 

1699 Usage:: 

1700 

1701 >>> arrow.utcnow().for_json() 

1702 '2019-01-19T18:25:36.760079+00:00' 

1703 

1704 """ 

1705 

1706 return self.isoformat() 

1707 

1708 # math 

1709 

1710 def __add__(self, other: Any) -> "Arrow": 

1711 if isinstance(other, (timedelta, relativedelta)): 

1712 return self.fromdatetime(self._datetime + other, self._datetime.tzinfo) 

1713 

1714 return NotImplemented 

1715 

1716 def __radd__(self, other: Union[timedelta, relativedelta]) -> "Arrow": 

1717 return self.__add__(other) 

1718 

1719 @overload 

1720 def __sub__(self, other: Union[timedelta, relativedelta]) -> "Arrow": 

1721 pass # pragma: no cover 

1722 

1723 @overload 

1724 def __sub__(self, other: Union[dt_datetime, "Arrow"]) -> timedelta: 

1725 pass # pragma: no cover 

1726 

1727 def __sub__(self, other: Any) -> Union[timedelta, "Arrow"]: 

1728 if isinstance(other, (timedelta, relativedelta)): 

1729 return self.fromdatetime(self._datetime - other, self._datetime.tzinfo) 

1730 

1731 elif isinstance(other, dt_datetime): 

1732 return self._datetime - other 

1733 

1734 elif isinstance(other, Arrow): 

1735 return self._datetime - other._datetime 

1736 

1737 return NotImplemented 

1738 

1739 def __rsub__(self, other: Any) -> timedelta: 

1740 if isinstance(other, dt_datetime): 

1741 return other - self._datetime 

1742 

1743 return NotImplemented 

1744 

1745 # comparisons 

1746 

1747 def __eq__(self, other: Any) -> bool: 

1748 if not isinstance(other, (Arrow, dt_datetime)): 

1749 return False 

1750 

1751 return self._datetime == self._get_datetime(other) 

1752 

1753 def __ne__(self, other: Any) -> bool: 

1754 if not isinstance(other, (Arrow, dt_datetime)): 

1755 return True 

1756 

1757 return not self.__eq__(other) 

1758 

1759 def __gt__(self, other: Any) -> bool: 

1760 if not isinstance(other, (Arrow, dt_datetime)): 

1761 return NotImplemented 

1762 

1763 return self._datetime > self._get_datetime(other) 

1764 

1765 def __ge__(self, other: Any) -> bool: 

1766 if not isinstance(other, (Arrow, dt_datetime)): 

1767 return NotImplemented 

1768 

1769 return self._datetime >= self._get_datetime(other) 

1770 

1771 def __lt__(self, other: Any) -> bool: 

1772 if not isinstance(other, (Arrow, dt_datetime)): 

1773 return NotImplemented 

1774 

1775 return self._datetime < self._get_datetime(other) 

1776 

1777 def __le__(self, other: Any) -> bool: 

1778 if not isinstance(other, (Arrow, dt_datetime)): 

1779 return NotImplemented 

1780 

1781 return self._datetime <= self._get_datetime(other) 

1782 

1783 # internal methods 

1784 @staticmethod 

1785 def _get_tzinfo(tz_expr: Optional[TZ_EXPR]) -> dt_tzinfo: 

1786 """Get normalized tzinfo object from various inputs.""" 

1787 if tz_expr is None: 

1788 return dateutil_tz.tzutc() 

1789 if isinstance(tz_expr, dt_tzinfo): 

1790 return tz_expr 

1791 else: 

1792 try: 

1793 return parser.TzinfoParser.parse(tz_expr) 

1794 except parser.ParserError: 

1795 raise ValueError(f"{tz_expr!r} not recognized as a timezone.") 

1796 

1797 @classmethod 

1798 def _get_datetime( 

1799 cls, expr: Union["Arrow", dt_datetime, int, float, str] 

1800 ) -> dt_datetime: 

1801 """Get datetime object from a specified expression.""" 

1802 if isinstance(expr, Arrow): 

1803 return expr.datetime 

1804 elif isinstance(expr, dt_datetime): 

1805 return expr 

1806 elif util.is_timestamp(expr): 

1807 timestamp = float(expr) 

1808 return cls.utcfromtimestamp(timestamp).datetime 

1809 else: 

1810 raise ValueError(f"{expr!r} not recognized as a datetime or timestamp.") 

1811 

1812 @classmethod 

1813 def _get_frames(cls, name: _T_FRAMES) -> Tuple[str, str, int]: 

1814 """Finds relevant timeframe and steps for use in range and span methods. 

1815 

1816 Returns a 3 element tuple in the form (frame, plural frame, step), for example ("day", "days", 1) 

1817 

1818 """ 

1819 if name in cls._ATTRS: 

1820 return name, f"{name}s", 1 

1821 elif name[-1] == "s" and name[:-1] in cls._ATTRS: 

1822 return name[:-1], name, 1 

1823 elif name in ["week", "weeks"]: 

1824 return "week", "weeks", 1 

1825 elif name in ["quarter", "quarters"]: 

1826 return "quarter", "months", 3 

1827 else: 

1828 supported = ", ".join( 

1829 [ 

1830 "year(s)", 

1831 "month(s)", 

1832 "day(s)", 

1833 "hour(s)", 

1834 "minute(s)", 

1835 "second(s)", 

1836 "microsecond(s)", 

1837 "week(s)", 

1838 "quarter(s)", 

1839 ] 

1840 ) 

1841 raise ValueError( 

1842 f"Range or span over frame {name} not supported. Supported frames: {supported}." 

1843 ) 

1844 

1845 @classmethod 

1846 def _get_iteration_params(cls, end: Any, limit: Optional[int]) -> Tuple[Any, int]: 

1847 """Sets default end and limit values for range method.""" 

1848 if end is None: 

1849 if limit is None: 

1850 raise ValueError("One of 'end' or 'limit' is required.") 

1851 

1852 return cls.max, limit 

1853 

1854 else: 

1855 if limit is None: 

1856 return end, sys.maxsize 

1857 return end, limit 

1858 

1859 @staticmethod 

1860 def _is_last_day_of_month(date: "Arrow") -> bool: 

1861 """Returns a boolean indicating whether the datetime is the last day of the month.""" 

1862 return date.day == calendar.monthrange(date.year, date.month)[1] 

1863 

1864 

1865Arrow.min = Arrow.fromdatetime(dt_datetime.min) 

1866Arrow.max = Arrow.fromdatetime(dt_datetime.max)