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

558 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:26 +0000

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 

15from datetime import tzinfo as dt_tzinfo 

16from math import trunc 

17from time import struct_time 

18from typing import ( 

19 Any, 

20 ClassVar, 

21 Generator, 

22 Iterable, 

23 List, 

24 Mapping, 

25 Optional, 

26 Tuple, 

27 Union, 

28 cast, 

29 overload, 

30) 

31 

32from dateutil import tz as dateutil_tz 

33from dateutil.relativedelta import relativedelta 

34 

35from arrow import formatter, locales, parser, util 

36from arrow.constants import DEFAULT_LOCALE, DEHUMANIZE_LOCALES 

37from arrow.locales import TimeFrameLiteral 

38 

39if sys.version_info < (3, 8): # pragma: no cover 

40 from typing_extensions import Final, Literal 

41else: 

42 from typing import Final, Literal # pragma: no cover 

43 

44 

45TZ_EXPR = Union[dt_tzinfo, str] 

46 

47_T_FRAMES = Literal[ 

48 "year", 

49 "years", 

50 "month", 

51 "months", 

52 "day", 

53 "days", 

54 "hour", 

55 "hours", 

56 "minute", 

57 "minutes", 

58 "second", 

59 "seconds", 

60 "microsecond", 

61 "microseconds", 

62 "week", 

63 "weeks", 

64 "quarter", 

65 "quarters", 

66] 

67 

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

69 

70_GRANULARITY = Literal[ 

71 "auto", 

72 "second", 

73 "minute", 

74 "hour", 

75 "day", 

76 "week", 

77 "month", 

78 "quarter", 

79 "year", 

80] 

81 

82 

83class Arrow: 

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

85 

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

87 additional functionality. 

88 

89 :param year: the calendar year. 

90 :param month: the calendar month. 

91 :param day: the calendar day. 

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

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

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

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

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

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

98 

99 .. _tz-expr: 

100 

101 Recognized timezone expressions: 

102 

103 - A ``tzinfo`` object. 

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

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

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

107 

108 Usage:: 

109 

110 >>> import arrow 

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

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

113 

114 """ 

115 

116 resolution: ClassVar[timedelta] = dt_datetime.resolution 

117 min: ClassVar["Arrow"] 

118 max: ClassVar["Arrow"] 

119 

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

121 "year", 

122 "month", 

123 "day", 

124 "hour", 

125 "minute", 

126 "second", 

127 "microsecond", 

128 ] 

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

130 _MONTHS_PER_QUARTER: Final[int] = 3 

131 _SECS_PER_MINUTE: Final[int] = 60 

132 _SECS_PER_HOUR: Final[int] = 60 * 60 

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

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

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

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

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

138 

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

140 "second": 1.0, 

141 "minute": _SECS_PER_MINUTE, 

142 "hour": _SECS_PER_HOUR, 

143 "day": _SECS_PER_DAY, 

144 "week": _SECS_PER_WEEK, 

145 "month": _SECS_PER_MONTH, 

146 "quarter": _SECS_PER_QUARTER, 

147 "year": _SECS_PER_YEAR, 

148 } 

149 

150 _datetime: dt_datetime 

151 

152 def __init__( 

153 self, 

154 year: int, 

155 month: int, 

156 day: int, 

157 hour: int = 0, 

158 minute: int = 0, 

159 second: int = 0, 

160 microsecond: int = 0, 

161 tzinfo: Optional[TZ_EXPR] = None, 

162 **kwargs: Any, 

163 ) -> None: 

164 if tzinfo is None: 

165 tzinfo = dateutil_tz.tzutc() 

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

167 elif ( 

168 isinstance(tzinfo, dt_tzinfo) 

169 and hasattr(tzinfo, "localize") 

170 and hasattr(tzinfo, "zone") 

171 and tzinfo.zone 

172 ): 

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

174 elif isinstance(tzinfo, str): 

175 tzinfo = parser.TzinfoParser.parse(tzinfo) 

176 

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

178 

179 self._datetime = dt_datetime( 

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

181 ) 

182 

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

184 

185 @classmethod 

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

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

188 timezone. 

189 

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

191 

192 Usage:: 

193 

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

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

196 

197 """ 

198 

199 if tzinfo is None: 

200 tzinfo = dateutil_tz.tzlocal() 

201 

202 dt = dt_datetime.now(tzinfo) 

203 

204 return cls( 

205 dt.year, 

206 dt.month, 

207 dt.day, 

208 dt.hour, 

209 dt.minute, 

210 dt.second, 

211 dt.microsecond, 

212 dt.tzinfo, 

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

214 ) 

215 

216 @classmethod 

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

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

219 time. 

220 

221 Usage:: 

222 

223 >>> arrow.utcnow() 

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

225 

226 """ 

227 

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

229 

230 return cls( 

231 dt.year, 

232 dt.month, 

233 dt.day, 

234 dt.hour, 

235 dt.minute, 

236 dt.second, 

237 dt.microsecond, 

238 dt.tzinfo, 

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

240 ) 

241 

242 @classmethod 

243 def fromtimestamp( 

244 cls, 

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

246 tzinfo: Optional[TZ_EXPR] = None, 

247 ) -> "Arrow": 

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

249 the given timezone. 

250 

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

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

253 

254 """ 

255 

256 if tzinfo is None: 

257 tzinfo = dateutil_tz.tzlocal() 

258 elif isinstance(tzinfo, str): 

259 tzinfo = parser.TzinfoParser.parse(tzinfo) 

260 

261 if not util.is_timestamp(timestamp): 

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

263 

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

265 dt = dt_datetime.fromtimestamp(timestamp, tzinfo) 

266 

267 return cls( 

268 dt.year, 

269 dt.month, 

270 dt.day, 

271 dt.hour, 

272 dt.minute, 

273 dt.second, 

274 dt.microsecond, 

275 dt.tzinfo, 

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

277 ) 

278 

279 @classmethod 

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

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

282 

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

284 

285 """ 

286 

287 if not util.is_timestamp(timestamp): 

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

289 

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

291 dt = dt_datetime.utcfromtimestamp(timestamp) 

292 

293 return cls( 

294 dt.year, 

295 dt.month, 

296 dt.day, 

297 dt.hour, 

298 dt.minute, 

299 dt.second, 

300 dt.microsecond, 

301 dateutil_tz.tzutc(), 

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

303 ) 

304 

305 @classmethod 

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

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

308 optional replacement timezone. 

309 

310 :param dt: the ``datetime`` 

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

312 timezone, or UTC if naive. 

313 

314 Usage:: 

315 

316 >>> dt 

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

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

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

320 

321 """ 

322 

323 if tzinfo is None: 

324 if dt.tzinfo is None: 

325 tzinfo = dateutil_tz.tzutc() 

326 else: 

327 tzinfo = dt.tzinfo 

328 

329 return cls( 

330 dt.year, 

331 dt.month, 

332 dt.day, 

333 dt.hour, 

334 dt.minute, 

335 dt.second, 

336 dt.microsecond, 

337 tzinfo, 

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

339 ) 

340 

341 @classmethod 

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

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

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

345 

346 :param date: the ``date`` 

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

348 

349 """ 

350 

351 if tzinfo is None: 

352 tzinfo = dateutil_tz.tzutc() 

353 

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

355 

356 @classmethod 

357 def strptime( 

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

359 ) -> "Arrow": 

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

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

362 

363 :param date_str: the date string. 

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

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

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

367 

368 Usage:: 

369 

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

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

372 

373 """ 

374 

375 dt = dt_datetime.strptime(date_str, fmt) 

376 if tzinfo is None: 

377 tzinfo = dt.tzinfo 

378 

379 return cls( 

380 dt.year, 

381 dt.month, 

382 dt.day, 

383 dt.hour, 

384 dt.minute, 

385 dt.second, 

386 dt.microsecond, 

387 tzinfo, 

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

389 ) 

390 

391 @classmethod 

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

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

394 to the Gregorian Ordinal. 

395 

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

397 

398 Usage:: 

399 

400 >>> arrow.fromordinal(737741) 

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

402 

403 """ 

404 

405 util.validate_ordinal(ordinal) 

406 dt = dt_datetime.fromordinal(ordinal) 

407 return cls( 

408 dt.year, 

409 dt.month, 

410 dt.day, 

411 dt.hour, 

412 dt.minute, 

413 dt.second, 

414 dt.microsecond, 

415 dt.tzinfo, 

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

417 ) 

418 

419 # factories: ranges and spans 

420 

421 @classmethod 

422 def range( 

423 cls, 

424 frame: _T_FRAMES, 

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

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

427 tz: Optional[TZ_EXPR] = None, 

428 limit: Optional[int] = None, 

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

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

431 points in time between two inputs. 

432 

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

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

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

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

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

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

439 

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

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

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

443 

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

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

446 same timezone and no ``tz``. 

447 

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

449 

450 Recognized datetime expressions: 

451 

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

453 - A ``datetime`` object. 

454 

455 Usage:: 

456 

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

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

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

460 ... print(repr(r)) 

461 ... 

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

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

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

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

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

467 

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

469 

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

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

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

473 ... print(repr(r)) 

474 ... 

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

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

477 

478 """ 

479 

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

481 

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

483 

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

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

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

487 

488 current = cls.fromdatetime(start) 

489 original_day = start.day 

490 day_is_clipped = False 

491 i = 0 

492 

493 while current <= end and i < limit: 

494 i += 1 

495 yield current 

496 

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

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

499 **{frame_relative: relative_steps} 

500 ) 

501 

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

503 day_is_clipped = True 

504 

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

506 current = current.replace(day=original_day) 

507 

508 def span( 

509 self, 

510 frame: _T_FRAMES, 

511 count: int = 1, 

512 bounds: _BOUNDS = "[)", 

513 exact: bool = False, 

514 week_start: int = 1, 

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

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

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

518 

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

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

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

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

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

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

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

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

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

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

529 Monday is 1 and Sunday is 7. 

530 

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

532 

533 Usage:: 

534 

535 >>> arrow.utcnow() 

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

537 

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

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

540 

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

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

543 

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

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

546 

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

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

549 

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

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

552 

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

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

555 

556 """ 

557 if not 1 <= week_start <= 7: 

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

559 

560 util.validate_bounds(bounds) 

561 

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

563 

564 if frame_absolute == "week": 

565 attr = "day" 

566 elif frame_absolute == "quarter": 

567 attr = "month" 

568 else: 

569 attr = frame_absolute 

570 

571 floor = self 

572 if not exact: 

573 index = self._ATTRS.index(attr) 

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

575 

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

577 

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

579 values.append(1) 

580 

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

582 

583 if frame_absolute == "week": 

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

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

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

587 elif frame_absolute == "quarter": 

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

589 

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

591 

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

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

594 

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

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

597 

598 return floor, ceil 

599 

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

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

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

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

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

605 

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

607 

608 Usage:: 

609 

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

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

612 

613 """ 

614 

615 return self.span(frame)[0] 

616 

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

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

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

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

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

622 

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

624 

625 Usage:: 

626 

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

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

629 

630 """ 

631 

632 return self.span(frame)[1] 

633 

634 @classmethod 

635 def span_range( 

636 cls, 

637 frame: _T_FRAMES, 

638 start: dt_datetime, 

639 end: dt_datetime, 

640 tz: Optional[TZ_EXPR] = None, 

641 limit: Optional[int] = None, 

642 bounds: _BOUNDS = "[)", 

643 exact: bool = False, 

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

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

646 representing a series of timespans between two inputs. 

647 

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

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

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

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

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

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

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

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

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

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

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

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

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

661 

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

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

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

665 

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

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

668 same timezone and no ``tz``. 

669 

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

671 

672 Recognized datetime expressions: 

673 

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

675 - A ``datetime`` object. 

676 

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

678 iterator of timespans. 

679 

680 Usage: 

681 

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

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

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

685 ... print(r) 

686 ... 

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

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

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

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

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

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

693 

694 """ 

695 

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

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

698 end = cls.fromdatetime(end, tzinfo) 

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

700 if not exact: 

701 for r in _range: 

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

703 

704 for r in _range: 

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

706 if ceil > end: 

707 ceil = end 

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

709 ceil += relativedelta(microseconds=-1) 

710 if floor == end: 

711 break 

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

713 break 

714 yield floor, ceil 

715 

716 @classmethod 

717 def interval( 

718 cls, 

719 frame: _T_FRAMES, 

720 start: dt_datetime, 

721 end: dt_datetime, 

722 interval: int = 1, 

723 tz: Optional[TZ_EXPR] = None, 

724 bounds: _BOUNDS = "[)", 

725 exact: bool = False, 

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

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

728 representing a series of intervals between two inputs. 

729 

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

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

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

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

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

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

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

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

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

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

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

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

742 

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

744 

745 Recognized datetime expressions: 

746 

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

748 - A ``datetime`` object. 

749 

750 Recognized timezone expressions: 

751 

752 - A ``tzinfo`` object. 

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

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

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

756 

757 Usage: 

758 

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

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

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

762 ... print(r) 

763 ... 

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

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

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

767 """ 

768 if interval < 1: 

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

770 

771 spanRange = iter( 

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

773 ) 

774 while True: 

775 try: 

776 intvlStart, intvlEnd = next(spanRange) 

777 for _ in range(interval - 1): 

778 try: 

779 _, intvlEnd = next(spanRange) 

780 except StopIteration: 

781 continue 

782 yield intvlStart, intvlEnd 

783 except StopIteration: 

784 return 

785 

786 # representations 

787 

788 def __repr__(self) -> str: 

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

790 

791 def __str__(self) -> str: 

792 return self._datetime.isoformat() 

793 

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

795 if len(formatstr) > 0: 

796 return self.format(formatstr) 

797 

798 return str(self) 

799 

800 def __hash__(self) -> int: 

801 return self._datetime.__hash__() 

802 

803 # attributes and properties 

804 

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

806 if name == "week": 

807 return self.isocalendar()[1] 

808 

809 if name == "quarter": 

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

811 

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

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

814 

815 if value is not None: 

816 return value 

817 

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

819 

820 @property 

821 def tzinfo(self) -> dt_tzinfo: 

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

823 

824 Usage:: 

825 

826 >>> arw=arrow.utcnow() 

827 >>> arw.tzinfo 

828 tzutc() 

829 

830 """ 

831 

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

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

834 

835 @property 

836 def datetime(self) -> dt_datetime: 

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

838 

839 Usage:: 

840 

841 >>> arw=arrow.utcnow() 

842 >>> arw.datetime 

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

844 

845 """ 

846 

847 return self._datetime 

848 

849 @property 

850 def naive(self) -> dt_datetime: 

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

852 object. 

853 

854 Usage:: 

855 

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

857 >>> nairobi 

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

859 >>> nairobi.naive 

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

861 

862 """ 

863 

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

865 

866 def timestamp(self) -> float: 

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

868 UTC time. 

869 

870 Usage:: 

871 

872 >>> arrow.utcnow().timestamp() 

873 1616882340.256501 

874 

875 """ 

876 

877 return self._datetime.timestamp() 

878 

879 @property 

880 def int_timestamp(self) -> int: 

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

882 UTC time. 

883 

884 Usage:: 

885 

886 >>> arrow.utcnow().int_timestamp 

887 1548260567 

888 

889 """ 

890 

891 return int(self.timestamp()) 

892 

893 @property 

894 def float_timestamp(self) -> float: 

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

896 object, in UTC time. 

897 

898 Usage:: 

899 

900 >>> arrow.utcnow().float_timestamp 

901 1548260516.830896 

902 

903 """ 

904 

905 return self.timestamp() 

906 

907 @property 

908 def fold(self) -> int: 

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

910 

911 return self._datetime.fold 

912 

913 @property 

914 def ambiguous(self) -> bool: 

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

916 timezone. 

917 

918 """ 

919 

920 return dateutil_tz.datetime_ambiguous(self._datetime) 

921 

922 @property 

923 def imaginary(self) -> bool: 

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

925 

926 return not dateutil_tz.datetime_exists(self._datetime) 

927 

928 # mutation and duplication. 

929 

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

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

932 

933 Usage: 

934 

935 >>> arw = arrow.utcnow() 

936 >>> cloned = arw.clone() 

937 

938 """ 

939 

940 return self.fromdatetime(self._datetime) 

941 

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

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

944 according to inputs. 

945 

946 Use property names to set their value absolutely:: 

947 

948 >>> import arrow 

949 >>> arw = arrow.utcnow() 

950 >>> arw 

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

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

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

954 

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

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

957 

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

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

960 

961 """ 

962 

963 absolute_kwargs = {} 

964 

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

966 if key in self._ATTRS: 

967 absolute_kwargs[key] = value 

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

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

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

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

972 

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

974 

975 tzinfo = kwargs.get("tzinfo") 

976 

977 if tzinfo is not None: 

978 tzinfo = self._get_tzinfo(tzinfo) 

979 current = current.replace(tzinfo=tzinfo) 

980 

981 fold = kwargs.get("fold") 

982 

983 if fold is not None: 

984 current = current.replace(fold=fold) 

985 

986 return self.fromdatetime(current) 

987 

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

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

990 according to inputs. 

991 

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

993 

994 >>> import arrow 

995 >>> arw = arrow.utcnow() 

996 >>> arw 

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

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

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

1000 

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

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

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

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

1005 

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

1007 

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

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

1010 

1011 While asking for a Monday: 

1012 

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

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

1015 

1016 """ 

1017 

1018 relative_kwargs = {} 

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

1020 

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

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

1023 relative_kwargs[key] = value 

1024 else: 

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

1026 raise ValueError( 

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

1028 ) 

1029 

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

1031 relative_kwargs.setdefault("months", 0) 

1032 relative_kwargs["months"] += ( 

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

1034 ) 

1035 

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

1037 

1038 if not dateutil_tz.datetime_exists(current): 

1039 current = dateutil_tz.resolve_imaginary(current) 

1040 

1041 return self.fromdatetime(current) 

1042 

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

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

1045 to the target timezone. 

1046 

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

1048 

1049 Usage:: 

1050 

1051 >>> utc = arrow.utcnow() 

1052 >>> utc 

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

1054 

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

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

1057 

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

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

1060 

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

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

1063 

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

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

1066 

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

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

1069 

1070 """ 

1071 

1072 if not isinstance(tz, dt_tzinfo): 

1073 tz = parser.TzinfoParser.parse(tz) 

1074 

1075 dt = self._datetime.astimezone(tz) 

1076 

1077 return self.__class__( 

1078 dt.year, 

1079 dt.month, 

1080 dt.day, 

1081 dt.hour, 

1082 dt.minute, 

1083 dt.second, 

1084 dt.microsecond, 

1085 dt.tzinfo, 

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

1087 ) 

1088 

1089 # string output and formatting 

1090 

1091 def format( 

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

1093 ) -> str: 

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

1095 formatted according to the provided format string. 

1096 

1097 :param fmt: the format string. 

1098 :param locale: the locale to format. 

1099 

1100 Usage:: 

1101 

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

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

1104 

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

1106 '1368071882' 

1107 

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

1109 'May 09, 2013' 

1110 

1111 >>> arrow.utcnow().format() 

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

1113 

1114 """ 

1115 

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

1117 

1118 def humanize( 

1119 self, 

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

1121 locale: str = DEFAULT_LOCALE, 

1122 only_distance: bool = False, 

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

1124 ) -> str: 

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

1126 

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

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

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

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

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

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

1133 

1134 Usage:: 

1135 

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

1137 >>> earlier.humanize() 

1138 '2 hours ago' 

1139 

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

1141 >>> later.humanize(earlier) 

1142 'in 4 hours' 

1143 

1144 """ 

1145 

1146 locale_name = locale 

1147 locale = locales.get_locale(locale) 

1148 

1149 if other is None: 

1150 utc = dt_datetime.utcnow().replace(tzinfo=dateutil_tz.tzutc()) 

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

1152 

1153 elif isinstance(other, Arrow): 

1154 dt = other._datetime 

1155 

1156 elif isinstance(other, dt_datetime): 

1157 if other.tzinfo is None: 

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

1159 else: 

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

1161 

1162 else: 

1163 raise TypeError( 

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

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

1166 ) 

1167 

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

1169 granularity = granularity[0] 

1170 

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

1172 sign = -1 if _delta < 0 else 1 

1173 delta_second = diff = abs(_delta) 

1174 

1175 try: 

1176 if granularity == "auto": 

1177 if diff < 10: 

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

1179 

1180 if diff < self._SECS_PER_MINUTE: 

1181 seconds = sign * delta_second 

1182 return locale.describe( 

1183 "seconds", seconds, only_distance=only_distance 

1184 ) 

1185 

1186 elif diff < self._SECS_PER_MINUTE * 2: 

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

1188 elif diff < self._SECS_PER_HOUR: 

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

1190 return locale.describe( 

1191 "minutes", minutes, only_distance=only_distance 

1192 ) 

1193 

1194 elif diff < self._SECS_PER_HOUR * 2: 

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

1196 elif diff < self._SECS_PER_DAY: 

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

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

1199 elif diff < self._SECS_PER_DAY * 2: 

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

1201 elif diff < self._SECS_PER_WEEK: 

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

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

1204 

1205 elif diff < self._SECS_PER_WEEK * 2: 

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

1207 elif diff < self._SECS_PER_MONTH: 

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

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

1210 

1211 elif diff < self._SECS_PER_MONTH * 2: 

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

1213 elif diff < self._SECS_PER_YEAR: 

1214 # TODO revisit for humanization during leap years 

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

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

1217 

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

1219 

1220 return locale.describe( 

1221 "months", months, only_distance=only_distance 

1222 ) 

1223 

1224 elif diff < self._SECS_PER_YEAR * 2: 

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

1226 else: 

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

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

1229 

1230 elif isinstance(granularity, str): 

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

1232 

1233 if granularity == "second": 

1234 delta = sign * float(delta_second) 

1235 if abs(delta) < 2: 

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

1237 elif granularity == "minute": 

1238 delta = sign * delta_second / self._SECS_PER_MINUTE 

1239 elif granularity == "hour": 

1240 delta = sign * delta_second / self._SECS_PER_HOUR 

1241 elif granularity == "day": 

1242 delta = sign * delta_second / self._SECS_PER_DAY 

1243 elif granularity == "week": 

1244 delta = sign * delta_second / self._SECS_PER_WEEK 

1245 elif granularity == "month": 

1246 delta = sign * delta_second / self._SECS_PER_MONTH 

1247 elif granularity == "quarter": 

1248 delta = sign * delta_second / self._SECS_PER_QUARTER 

1249 elif granularity == "year": 

1250 delta = sign * delta_second / self._SECS_PER_YEAR 

1251 else: 

1252 raise ValueError( 

1253 "Invalid level of granularity. " 

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

1255 ) 

1256 

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

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

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

1260 

1261 else: 

1262 if not granularity: 

1263 raise ValueError( 

1264 "Empty granularity list provided. " 

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

1266 ) 

1267 

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

1269 

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

1271 if _frame in granularity: 

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

1273 _delta %= self._SECS_MAP[_frame] 

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

1275 timeframes.append( 

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

1277 ) 

1278 else: 

1279 timeframes.append((_frame, value)) 

1280 return _delta 

1281 

1282 delta = float(delta_second) 

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

1284 "year", 

1285 "quarter", 

1286 "month", 

1287 "week", 

1288 "day", 

1289 "hour", 

1290 "minute", 

1291 "second", 

1292 ) 

1293 for frame in frames: 

1294 delta = gather_timeframes(delta, frame) 

1295 

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

1297 raise ValueError( 

1298 "Invalid level of granularity. " 

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

1300 ) 

1301 

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

1303 

1304 except KeyError as e: 

1305 raise ValueError( 

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

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

1308 ) 

1309 

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

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

1312 the time difference relative to the attributes of the 

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

1314 

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

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

1317 

1318 Usage:: 

1319 

1320 >>> arw = arrow.utcnow() 

1321 >>> arw 

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

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

1324 >>> earlier 

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

1326 

1327 >>> arw = arrow.utcnow() 

1328 >>> arw 

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

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

1331 >>> later 

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

1333 

1334 """ 

1335 

1336 # Create a locale object based off given local 

1337 locale_obj = locales.get_locale(locale) 

1338 

1339 # Check to see if locale is supported 

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

1341 

1342 if normalized_locale_name not in DEHUMANIZE_LOCALES: 

1343 raise ValueError( 

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

1345 ) 

1346 

1347 current_time = self.fromdatetime(self._datetime) 

1348 

1349 # Create an object containing the relative time info 

1350 time_object_info = dict.fromkeys( 

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

1352 ) 

1353 

1354 # Create an object representing if unit has been seen 

1355 unit_visited = dict.fromkeys( 

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

1357 False, 

1358 ) 

1359 

1360 # Create a regex pattern object for numbers 

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

1362 

1363 # Search input string for each time unit within locale 

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

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

1366 if isinstance(unit_object, Mapping): 

1367 strings_to_search = unit_object 

1368 else: 

1369 strings_to_search = {unit: str(unit_object)} 

1370 

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

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

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

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

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

1376 search_string = str(time_string) 

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

1378 

1379 # Create search pattern and find within string 

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

1381 match = pattern.search(input_string) 

1382 

1383 # If there is no match continue to next iteration 

1384 if not match: 

1385 continue 

1386 

1387 match_string = match.group() 

1388 num_match = num_pattern.search(match_string) 

1389 

1390 # If no number matches 

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

1392 if not num_match: 

1393 change_value = ( 

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

1395 ) 

1396 else: 

1397 change_value = int(num_match.group()) 

1398 

1399 # No time to update if now is the unit 

1400 if unit == "now": 

1401 unit_visited[unit] = True 

1402 continue 

1403 

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

1405 time_unit_to_change = str(unit) 

1406 time_unit_to_change += ( 

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

1408 ) 

1409 time_object_info[time_unit_to_change] = change_value 

1410 unit_visited[time_unit_to_change] = True 

1411 

1412 # Assert error if string does not modify any units 

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

1414 raise ValueError( 

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

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

1417 ) 

1418 

1419 # Sign logic 

1420 future_string = locale_obj.future 

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

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

1423 future_pattern_match = future_pattern.findall(input_string) 

1424 

1425 past_string = locale_obj.past 

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

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

1428 past_pattern_match = past_pattern.findall(input_string) 

1429 

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

1431 # was visited before raising a ValueError 

1432 if past_pattern_match: 

1433 sign_val = -1 

1434 elif future_pattern_match: 

1435 sign_val = 1 

1436 elif unit_visited["now"]: 

1437 sign_val = 0 

1438 else: 

1439 raise ValueError( 

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

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

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

1443 ) 

1444 

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

1446 

1447 return current_time.shift(**time_changes) 

1448 

1449 # query functions 

1450 

1451 def is_between( 

1452 self, 

1453 start: "Arrow", 

1454 end: "Arrow", 

1455 bounds: _BOUNDS = "()", 

1456 ) -> bool: 

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

1458 the start and end limits. 

1459 

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

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

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

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

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

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

1466 

1467 Usage:: 

1468 

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

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

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

1472 True 

1473 

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

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

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

1477 True 

1478 

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

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

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

1482 False 

1483 

1484 """ 

1485 

1486 util.validate_bounds(bounds) 

1487 

1488 if not isinstance(start, Arrow): 

1489 raise TypeError( 

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

1491 ) 

1492 

1493 if not isinstance(end, Arrow): 

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

1495 

1496 include_start = bounds[0] == "[" 

1497 include_end = bounds[1] == "]" 

1498 

1499 target_ts = self.float_timestamp 

1500 start_ts = start.float_timestamp 

1501 end_ts = end.float_timestamp 

1502 

1503 return ( 

1504 (start_ts <= target_ts <= end_ts) 

1505 and (include_start or start_ts < target_ts) 

1506 and (include_end or target_ts < end_ts) 

1507 ) 

1508 

1509 # datetime methods 

1510 

1511 def date(self) -> date: 

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

1513 

1514 Usage:: 

1515 

1516 >>> arrow.utcnow().date() 

1517 datetime.date(2019, 1, 23) 

1518 

1519 """ 

1520 

1521 return self._datetime.date() 

1522 

1523 def time(self) -> dt_time: 

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

1525 

1526 Usage:: 

1527 

1528 >>> arrow.utcnow().time() 

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

1530 

1531 """ 

1532 

1533 return self._datetime.time() 

1534 

1535 def timetz(self) -> dt_time: 

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

1537 tzinfo. 

1538 

1539 Usage:: 

1540 

1541 >>> arrow.utcnow().timetz() 

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

1543 

1544 """ 

1545 

1546 return self._datetime.timetz() 

1547 

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

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

1550 

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

1552 

1553 Usage:: 

1554 

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

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

1557 >>> pacific.astimezone(nyc) 

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

1559 

1560 """ 

1561 

1562 return self._datetime.astimezone(tz) 

1563 

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

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

1566 UTC time. 

1567 

1568 Usage:: 

1569 

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

1571 datetime.timedelta(-1, 57600) 

1572 

1573 """ 

1574 

1575 return self._datetime.utcoffset() 

1576 

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

1578 """Returns the daylight savings time adjustment. 

1579 

1580 Usage:: 

1581 

1582 >>> arrow.utcnow().dst() 

1583 datetime.timedelta(0) 

1584 

1585 """ 

1586 

1587 return self._datetime.dst() 

1588 

1589 def timetuple(self) -> struct_time: 

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

1591 

1592 Usage:: 

1593 

1594 >>> arrow.utcnow().timetuple() 

1595 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) 

1596 

1597 """ 

1598 

1599 return self._datetime.timetuple() 

1600 

1601 def utctimetuple(self) -> struct_time: 

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

1603 

1604 Usage:: 

1605 

1606 >>> arrow.utcnow().utctimetuple() 

1607 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) 

1608 

1609 """ 

1610 

1611 return self._datetime.utctimetuple() 

1612 

1613 def toordinal(self) -> int: 

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

1615 

1616 Usage:: 

1617 

1618 >>> arrow.utcnow().toordinal() 

1619 737078 

1620 

1621 """ 

1622 

1623 return self._datetime.toordinal() 

1624 

1625 def weekday(self) -> int: 

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

1627 

1628 Usage:: 

1629 

1630 >>> arrow.utcnow().weekday() 

1631 5 

1632 

1633 """ 

1634 

1635 return self._datetime.weekday() 

1636 

1637 def isoweekday(self) -> int: 

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

1639 

1640 Usage:: 

1641 

1642 >>> arrow.utcnow().isoweekday() 

1643 6 

1644 

1645 """ 

1646 

1647 return self._datetime.isoweekday() 

1648 

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

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

1651 

1652 Usage:: 

1653 

1654 >>> arrow.utcnow().isocalendar() 

1655 (2019, 3, 6) 

1656 

1657 """ 

1658 

1659 return self._datetime.isocalendar() 

1660 

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

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

1663 

1664 Usage:: 

1665 

1666 >>> arrow.utcnow().isoformat() 

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

1668 

1669 """ 

1670 

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

1672 

1673 def ctime(self) -> str: 

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

1675 

1676 Usage:: 

1677 

1678 >>> arrow.utcnow().ctime() 

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

1680 

1681 """ 

1682 

1683 return self._datetime.ctime() 

1684 

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

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

1687 

1688 :param format: the format string. 

1689 

1690 Usage:: 

1691 

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

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

1694 

1695 """ 

1696 

1697 return self._datetime.strftime(format) 

1698 

1699 def for_json(self) -> str: 

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

1701 

1702 Usage:: 

1703 

1704 >>> arrow.utcnow().for_json() 

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

1706 

1707 """ 

1708 

1709 return self.isoformat() 

1710 

1711 # math 

1712 

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

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

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

1716 

1717 return NotImplemented 

1718 

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

1720 return self.__add__(other) 

1721 

1722 @overload 

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

1724 pass # pragma: no cover 

1725 

1726 @overload 

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

1728 pass # pragma: no cover 

1729 

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

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

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

1733 

1734 elif isinstance(other, dt_datetime): 

1735 return self._datetime - other 

1736 

1737 elif isinstance(other, Arrow): 

1738 return self._datetime - other._datetime 

1739 

1740 return NotImplemented 

1741 

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

1743 if isinstance(other, dt_datetime): 

1744 return other - self._datetime 

1745 

1746 return NotImplemented 

1747 

1748 # comparisons 

1749 

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

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

1752 return False 

1753 

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

1755 

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

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

1758 return True 

1759 

1760 return not self.__eq__(other) 

1761 

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

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

1764 return NotImplemented 

1765 

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

1767 

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

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

1770 return NotImplemented 

1771 

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

1773 

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

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

1776 return NotImplemented 

1777 

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

1779 

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

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

1782 return NotImplemented 

1783 

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

1785 

1786 # internal methods 

1787 @staticmethod 

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

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

1790 if tz_expr is None: 

1791 return dateutil_tz.tzutc() 

1792 if isinstance(tz_expr, dt_tzinfo): 

1793 return tz_expr 

1794 else: 

1795 try: 

1796 return parser.TzinfoParser.parse(tz_expr) 

1797 except parser.ParserError: 

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

1799 

1800 @classmethod 

1801 def _get_datetime( 

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

1803 ) -> dt_datetime: 

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

1805 if isinstance(expr, Arrow): 

1806 return expr.datetime 

1807 elif isinstance(expr, dt_datetime): 

1808 return expr 

1809 elif util.is_timestamp(expr): 

1810 timestamp = float(expr) 

1811 return cls.utcfromtimestamp(timestamp).datetime 

1812 else: 

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

1814 

1815 @classmethod 

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

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

1818 

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

1820 

1821 """ 

1822 if name in cls._ATTRS: 

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

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

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

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

1827 return "week", "weeks", 1 

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

1829 return "quarter", "months", 3 

1830 else: 

1831 supported = ", ".join( 

1832 [ 

1833 "year(s)", 

1834 "month(s)", 

1835 "day(s)", 

1836 "hour(s)", 

1837 "minute(s)", 

1838 "second(s)", 

1839 "microsecond(s)", 

1840 "week(s)", 

1841 "quarter(s)", 

1842 ] 

1843 ) 

1844 raise ValueError( 

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

1846 ) 

1847 

1848 @classmethod 

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

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

1851 if end is None: 

1852 if limit is None: 

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

1854 

1855 return cls.max, limit 

1856 

1857 else: 

1858 if limit is None: 

1859 return end, sys.maxsize 

1860 return end, limit 

1861 

1862 @staticmethod 

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

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

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

1866 

1867 

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

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