• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Ouranosinc / miranda / 2229536127

pending completion
2229536127

Pull #33

github

GitHub
Merge c519fcbcb into d076d8475
Pull Request #33: Support CORDEX and CMIP5/6

32 of 170 new or added lines in 15 files covered. (18.82%)

6 existing lines in 5 files now uncovered.

659 of 3229 relevant lines covered (20.41%)

0.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

24.56
/miranda/decode/_time.py
1
import logging
3✔
2
from logging import config
3✔
3
from typing import Union
3✔
4

5
import cftime
3✔
6
import pandas as pd
3✔
7
from pandas._libs.tslibs import NaTType  # noqa
3✔
8

9
from miranda.scripting import LOGGING_CONFIG
3✔
10

11
logging.config.dictConfig(LOGGING_CONFIG)
3✔
12

13
__all__ = [
3✔
14
    "date_parser",
15
    "DecoderError",
16
    "TIME_UNITS_TO_FREQUENCY",
17
    "TIME_UNITS_TO_TIMEDELTA",
18
]
19

20
TIME_UNITS_TO_FREQUENCY = {
3✔
21
    "subhrPt": "sub-hr",
22
    "hourly": "1hr",
23
    "hours": "1hr",
24
    "1hr": "1hr",
25
    "3-hourly": "3hr",
26
    "3hr": "3hr",
27
    "6-hourly": "6hr",
28
    "6hr": "6hr",
29
    "daily": "day",
30
    "days": "day",
31
    "day": "day",
32
    "weekly": "sem",
33
    "weeks": "sem",
34
    "sem": "sem",
35
    "monthly": "mon",
36
    "months": "mon",
37
    "mon": "mon",
38
    "monC": "monC",
39
    "Amon": "mon",
40
    "Omon": "mon",
41
    "yearly": "yr",
42
    "years": "yr",
43
    "annual": "yr",
44
    "yr": "yr",
45
    "yrPt": "yrPt",
46
    "decadal": "dec",
47
    "decades": "dec",
48
    "dec": "dec",
49
    "fixed": "fx",
50
    "fx": "fx",
51
}
52
TIME_UNITS_TO_TIMEDELTA = {
3✔
53
    "hourly": "1h",
54
    "hours": "1h",
55
    "1hr": "1h",
56
    "1hrCM": "1h",
57
    "1hrPt": "1h",
58
    "3hr": "3h",
59
    "3hrPt": "3h",
60
    "6-hourly": "6h",
61
    "6hr": "6h",
62
    "6hrPt": "6h",
63
    "daily": "1d",
64
    "day": "1d",
65
    "days": "1d",
66
    "weekly": "7d",
67
    "weeks": "7d",
68
    "sem": "7d",
69
    "mon": "30d",
70
    "monC": "30d",
71
    "monPt": "30d",
72
    "Amon": "30d",
73
    "QS": "90d",
74
    "qtr": "90d",
75
    "yearly": "365d",
76
    "years": "365d",
77
    "year": "365d",
78
    "yr": "365d",
79
    "yrPt": "365d",
80
}
81

82

83
class DecoderError(Exception):
3✔
84
    pass
3✔
85

86

87
def date_parser(
3✔
88
    date: str,
89
    *,
90
    end_of_period: bool = False,
91
    output_type: str = "str",
92
    strftime_format: str = "%Y-%m-%d",
93
) -> Union[str, pd.Timestamp, NaTType]:
94
    """Returns a datetime from a string.
95

96
    Parameters
97
    ----------
98
    date : str
99
      Date to be converted.
100
    end_of_period : bool
101
      If True, the date will be the end of month or year depending on what's most appropriate.
102
    output_type: {"datetime", "str"}
103
      Returned object type.
104
    strftime_format: str
105
      If output_type=='str', this sets the strftime format.
106

107
    Returns
108
    -------
109
    pd.Timestamp or str or pd.NaT
110
      Parsed date.
111

112
    Notes
113
    -----
114
    Adapted from code written by Gabriel Rondeau-Genesse (@RondeauG)
115
    """
116

117
    # Formats, ordered depending on string length
118
    formats = {
×
119
        4: ["%Y"],
120
        6: ["%Y%m"],
121
        7: ["%Y-%m"],
122
        8: ["%Y%m%d"],
123
        10: ["%Y%m%d%H", "%Y-%m-%d"],
124
        12: ["%Y%m%d%H%M"],
125
        13: ["%Y%m-%Y%m"],
126
        17: ["%Y%m%d-%Y%m%d"],
127
        19: ["%Y-%m-%dT%H:%M:%S"],
128
        21: ["%Y%m%d%H-%Y%m%d%H"],
129
        25: ["%Y%m%d%H%M-%Y%m%d%H%M"],
130
    }
131
    end_date_found = False
×
132

133
    def _parse_date(d, fmts):
×
134
        for fmt in fmts:
×
135
            try:
×
136
                s = pd.to_datetime(d, format=fmt)
×
137
                match = fmt
×
138
                break
×
139
            except ValueError:
×
140
                pass
×
141
        else:
NEW
142
            raise DecoderError(f"Can't parse date {d} with supported formats {fmts}.")
×
143
        return s, match
×
144

145
    date_format = None
×
146
    if isinstance(date, str):
×
NEW
147
        if len(date) in [13, 17, 21, 25]:
×
148
            dates = date.split("-")
×
149
            if not end_of_period:
×
150
                date = dates[0]
×
151
            else:
152
                date = dates[1]
×
153
                end_date_found = True
×
154

155
        try:
×
156
            possible_formats = formats[len(date)]
×
157
            date, date_format = _parse_date(date, possible_formats)
×
158
        except KeyError:
×
159
            # Return NaT for fixed/missing/ill-formatted date strings
160
            return pd.NaT
×
161

162
    elif isinstance(date, cftime.datetime):
×
163
        for n in range(3):
×
164
            try:
×
165
                date = pd.Timestamp.fromisoformat((date - pd.Timedelta(n)).isoformat())
×
166
            except ValueError:  # We are NOT catching OutOfBoundsDatetime.
×
167
                pass
×
168
            else:
169
                break
×
170
        else:
171
            raise DecoderError(
×
172
                f"Unable to parse cftime date {date}, even when moving back 2 days."
173
            )
174
    elif not isinstance(date, pd.Timestamp):
×
175
        date = pd.Timestamp(date)  # noqa
×
176

177
    if end_of_period and date_format and not end_date_found:
×
178
        if "m" not in date_format:
×
NEW
179
            date = date + pd.tseries.offsets.YearEnd(1)  # noqa
×
180
        elif "d" not in date_format:
×
NEW
181
            date = date + pd.tseries.offsets.MonthEnd(1)  # noqa
×
182
        # TODO: Implement sub-daily?
183

184
    if output_type == "str":
×
NEW
185
        return date.strftime(strftime_format)
×
186

187
    return date
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2024 Coveralls, Inc