time.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # -*- coding: utf-8 -*-
  2. # MinIO Python Library for Amazon S3 Compatible Cloud Storage, (C)
  3. # 2020 MinIO, Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Time formatter for S3 APIs."""
  17. from __future__ import absolute_import, annotations
  18. import time as ctime
  19. from datetime import datetime, timezone
  20. try:
  21. from datetime import UTC # type: ignore[attr-defined]
  22. _UTC_IMPORTED = True
  23. except ImportError:
  24. _UTC_IMPORTED = False
  25. from typing import Optional
  26. _WEEK_DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
  27. _MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
  28. "Nov", "Dec"]
  29. def _to_utc(value: datetime) -> datetime:
  30. """Convert to UTC time if value is not naive."""
  31. return (
  32. value.astimezone(timezone.utc).replace(tzinfo=None)
  33. if value.tzinfo else value
  34. )
  35. def from_iso8601utc(value: Optional[str]) -> Optional[datetime]:
  36. """Parse UTC ISO-8601 formatted string to datetime."""
  37. if value is None:
  38. return None
  39. try:
  40. time = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%fZ")
  41. except ValueError:
  42. time = datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
  43. return time.replace(tzinfo=timezone.utc)
  44. def to_iso8601utc(value: Optional[datetime]) -> Optional[str]:
  45. """Format datetime into UTC ISO-8601 formatted string."""
  46. if value is None:
  47. return None
  48. value = _to_utc(value)
  49. return (
  50. value.strftime("%Y-%m-%dT%H:%M:%S.") + value.strftime("%f")[:3] + "Z"
  51. )
  52. def from_http_header(value: str) -> datetime:
  53. """Parse HTTP header date formatted string to datetime."""
  54. if len(value) != 29:
  55. raise ValueError(
  56. f"time data {value} does not match HTTP header format")
  57. if value[0:3] not in _WEEK_DAYS or value[3] != ",":
  58. raise ValueError(
  59. f"time data {value} does not match HTTP header format")
  60. weekday = _WEEK_DAYS.index(value[0:3])
  61. if value[4] != " " or value[7] != " ":
  62. raise ValueError(
  63. f"time data {value} does not match HTTP header format"
  64. )
  65. day = int(value[5:7])
  66. if value[8:11] not in _MONTHS:
  67. raise ValueError(
  68. f"time data {value} does not match HTTP header format")
  69. month = _MONTHS.index(value[8:11])
  70. time = datetime.strptime(value[11:], " %Y %H:%M:%S GMT")
  71. time = time.replace(day=day, month=month+1, tzinfo=timezone.utc)
  72. if weekday != time.weekday():
  73. raise ValueError(
  74. f"time data {value} does not match HTTP header format")
  75. return time
  76. def to_http_header(value: datetime) -> str:
  77. """Format datatime into HTTP header date formatted string."""
  78. value = _to_utc(value)
  79. weekday = _WEEK_DAYS[value.weekday()]
  80. day = value.strftime(" %d ")
  81. month = _MONTHS[value.month - 1]
  82. suffix = value.strftime(" %Y %H:%M:%S GMT")
  83. return f"{weekday},{day}{month}{suffix}"
  84. def to_amz_date(value: datetime) -> str:
  85. """Format datetime into AMZ date formatted string."""
  86. return _to_utc(value).strftime("%Y%m%dT%H%M%SZ")
  87. def utcnow() -> datetime:
  88. """Timezone-aware wrapper to datetime.utcnow()."""
  89. if _UTC_IMPORTED:
  90. return datetime.now(UTC).replace(tzinfo=timezone.utc)
  91. return datetime.utcnow().replace(tzinfo=timezone.utc)
  92. def to_signer_date(value: datetime) -> str:
  93. """Format datetime into SignatureV4 date formatted string."""
  94. return _to_utc(value).strftime("%Y%m%d")
  95. def to_float(value: datetime) -> float:
  96. """Convert datetime into float value."""
  97. return ctime.mktime(value.timetuple()) + value.microsecond * 1e-6