1#
2# Licensed to the Apache Software Foundation (ASF) under one
3# or more contributor license agreements. See the NOTICE file
4# distributed with this work for additional information
5# regarding copyright ownership. The ASF licenses this file
6# to you under the Apache License, Version 2.0 (the
7# "License"); you may not use this file except in compliance
8# with the License. You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing,
13# software distributed under the License is distributed on an
14# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15# KIND, either express or implied. See the License for the
16# specific language governing permissions and limitations
17# under the License.
18
19from __future__ import annotations
20
21import json # noqa: F401
22import time # noqa: F401
23import uuid # noqa: F401
24from datetime import datetime, timedelta
25from random import random # noqa: F401
26from typing import TYPE_CHECKING, Any
27
28import dateutil # noqa: F401
29
30import airflow.sdk.yaml as yaml # noqa: F401
31
32if TYPE_CHECKING:
33 from babel import Locale
34 from pendulum import DateTime
35
36
37def ds_add(ds: str, days: int) -> str:
38 """
39 Add or subtract days from a YYYY-MM-DD.
40
41 :param ds: anchor date in ``YYYY-MM-DD`` format to add to
42 :param days: number of days to add to the ds, you can use negative values
43
44 >>> ds_add("2015-01-01", 5)
45 '2015-01-06'
46 >>> ds_add("2015-01-06", -5)
47 '2015-01-01'
48 """
49 if not days:
50 return str(ds)
51 dt = datetime.strptime(str(ds), "%Y-%m-%d") + timedelta(days=days)
52 return dt.strftime("%Y-%m-%d")
53
54
55def ds_format(ds: str, input_format: str, output_format: str) -> str:
56 """
57 Output datetime string in a given format.
58
59 :param ds: Input string which contains a date.
60 :param input_format: Input string format (e.g., '%Y-%m-%d').
61 :param output_format: Output string format (e.g., '%Y-%m-%d').
62
63 >>> ds_format("2015-01-01", "%Y-%m-%d", "%m-%d-%y")
64 '01-01-15'
65 >>> ds_format("1/5/2015", "%m/%d/%Y", "%Y-%m-%d")
66 '2015-01-05'
67 >>> ds_format("12/07/2024", "%d/%m/%Y", "%A %d %B %Y", "en_US")
68 'Friday 12 July 2024'
69 """
70 return datetime.strptime(str(ds), input_format).strftime(output_format)
71
72
73def datetime_diff_for_humans(dt: Any, since: DateTime | None = None) -> str:
74 """
75 Return a human-readable/approximate difference between datetimes.
76
77 When only one datetime is provided, the comparison will be based on now.
78
79 :param dt: The datetime to display the diff for
80 :param since: When to display the date from. If ``None`` then the diff is
81 between ``dt`` and now.
82 """
83 import pendulum
84
85 return pendulum.instance(dt).diff_for_humans(since)
86
87
88def ds_format_locale(
89 ds: str, input_format: str, output_format: str, locale: Locale | str | None = None
90) -> str:
91 """
92 Output localized datetime string in a given Babel format.
93
94 :param ds: Input string which contains a date.
95 :param input_format: Input string format (e.g., '%Y-%m-%d').
96 :param output_format: Output string Babel format (e.g., `yyyy-MM-dd`).
97 :param locale: Locale used to format the output string (e.g., 'en_US').
98 If locale not specified, default LC_TIME will be used and if that's also not available,
99 'en_US' will be used.
100
101 >>> ds_format("2015-01-01", "%Y-%m-%d", "MM-dd-yy")
102 '01-01-15'
103 >>> ds_format("1/5/2015", "%m/%d/%Y", "yyyy-MM-dd")
104 '2015-01-05'
105 >>> ds_format("12/07/2024", "%d/%m/%Y", "EEEE dd MMMM yyyy", "en_US")
106 'Friday 12 July 2024'
107
108 .. versionadded:: 2.10.0
109 """
110 from babel import Locale
111 from babel.dates import LC_TIME, format_datetime
112
113 return format_datetime(
114 datetime.strptime(str(ds), input_format),
115 format=output_format,
116 locale=locale or LC_TIME or Locale("en_US"),
117 )