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

PyThaiNLP / pythainlp / 7168478731

11 Dec 2023 01:57PM UTC coverage: 87.042% (+0.6%) from 86.41%
7168478731

push

github

web-flow
Fix typo in test_khavee.py

6200 of 7123 relevant lines covered (87.04%)

0.87 hits per line

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

95.28
/pythainlp/util/strftime.py
1
# -*- coding: utf-8 -*-
2
# SPDX-FileCopyrightText: Copyright 2016-2023 PyThaiNLP Project
3
# SPDX-License-Identifier: Apache-2.0
4
"""
1✔
5
Thai date/time formatting.
6
"""
7
import warnings
1✔
8
from datetime import datetime
1✔
9
from string import digits
1✔
10

11
from pythainlp import thai_digits
1✔
12
from pythainlp.util.date import (
1✔
13
    thai_abbr_months,
14
    thai_abbr_weekdays,
15
    thai_full_months,
16
    thai_full_weekdays,
17
)
18

19
__all__ = [
1✔
20
    "thai_strftime",
21
]
22

23
_HA_TH_DIGITS = str.maketrans(digits, thai_digits)
1✔
24
_BE_AD_DIFFERENCE = 543
1✔
25

26
_NEED_L10N = "AaBbCcDFGgvXxYy+"  # flags that need localization
1✔
27
_EXTENSIONS = "EO-_0^#"  # extension flags
1✔
28

29

30
def _std_strftime(dt_obj: datetime, fmt_char: str) -> str:
1✔
31
    """
32
    Standard datetime.strftime() with normalization and exception handling.
33
    """
34
    str_ = ""
1✔
35
    try:
1✔
36
        str_ = dt_obj.strftime(f"%{fmt_char}")
1✔
37
        if not str_ or str_ == "%{}".format(fmt_char):
1✔
38
            # normalize outputs for unsupported directives
39
            # in different platforms
40
            # "%Q" may result "%Q", "Q", or "", make it "Q"
41
            str_ = fmt_char
×
42
    except ValueError as err:
×
43
        # Unsupported directives may raise ValueError on Windows,
44
        # in that case just use the fmt_char
45
        warnings.warn(
×
46
            (
47
                f"String format directive unknown/not support: %{fmt_char}"
48
                f"The system raises this ValueError: {err}"
49
            ),
50
            UserWarning,
51
        )
52
        str_ = fmt_char
×
53
    return str_
1✔
54

55

56
def _thai_strftime(dt_obj: datetime, fmt_char: str) -> str:
1✔
57
    """
58
    Conversion support for thai_strftime().
59

60
    The fmt_char should be in _NEED_L10N when calling this function.
61
    """
62
    str_ = ""
1✔
63
    if fmt_char == "A":
1✔
64
        # National representation of the full weekday name
65
        str_ = thai_full_weekdays[dt_obj.weekday()]
1✔
66
    elif fmt_char == "a":
1✔
67
        # National representation of the abbreviated weekday
68
        str_ = thai_abbr_weekdays[dt_obj.weekday()]
1✔
69
    elif fmt_char == "B":
1✔
70
        # National representation of the full month name
71
        str_ = thai_full_months[dt_obj.month - 1]
1✔
72
    elif fmt_char == "b":
1✔
73
        # National representation of the abbreviated month name
74
        str_ = thai_abbr_months[dt_obj.month - 1]
1✔
75
    elif fmt_char == "C":
1✔
76
        # Thai Buddhist century (AD+543)/100 + 1 as decimal number;
77
        str_ = str(int((dt_obj.year + _BE_AD_DIFFERENCE) / 100) + 1).zfill(2)
1✔
78
    elif fmt_char == "c":
1✔
79
        # Locale's appropriate date and time representation
80
        # Wed  6 Oct 01:40:00 1976
81
        # พ   6 ต.ค. 01:40:00 2519  <-- left-aligned weekday, right-aligned day
82
        str_ = "{:<2} {:>2} {} {} {}".format(
1✔
83
            thai_abbr_weekdays[dt_obj.weekday()],
84
            dt_obj.day,
85
            thai_abbr_months[dt_obj.month - 1],
86
            dt_obj.strftime("%H:%M:%S"),
87
            str(dt_obj.year + _BE_AD_DIFFERENCE).zfill(4),
88
        )
89
    elif fmt_char == "D":
1✔
90
        # Equivalent to ``%m/%d/%y''
91
        str_ = "{}/{}".format(
1✔
92
            dt_obj.strftime("%m/%d"),
93
            (str(dt_obj.year + _BE_AD_DIFFERENCE)[-2:]).zfill(2),
94
        )
95
    elif fmt_char == "F":
1✔
96
        # Equivalent to ``%Y-%m-%d''
97
        str_ = "{}-{}".format(
1✔
98
            str(dt_obj.year + _BE_AD_DIFFERENCE).zfill(4),
99
            dt_obj.strftime("%m-%d"),
100
        )
101
    elif fmt_char == "G":
1✔
102
        # ISO 8601 year with century representing the year that contains
103
        # the greater part of the ISO week (%V). Monday as the first day
104
        # of the week.
105
        str_ = str(int(dt_obj.strftime("%G")) + _BE_AD_DIFFERENCE).zfill(4)
1✔
106
    elif fmt_char == "g":
1✔
107
        # Same year as in ``%G'',
108
        # but as a decimal number without century (00-99).
109
        str_ = (
1✔
110
            str(int(dt_obj.strftime("%G")) + _BE_AD_DIFFERENCE)[-2:]
111
        ).zfill(2)
112
    elif fmt_char == "v":
1✔
113
        # BSD extension, ' 6-Oct-1976'
114
        str_ = "{:>2}-{}-{}".format(
1✔
115
            dt_obj.day,
116
            thai_abbr_months[dt_obj.month - 1],
117
            str(dt_obj.year + _BE_AD_DIFFERENCE).zfill(4),
118
        )
119
    elif fmt_char == "X":
1✔
120
        # Locale’s appropriate time representation.
121
        str_ = dt_obj.strftime("%H:%M:%S")
1✔
122
    elif fmt_char == "x":
1✔
123
        # Locale’s appropriate date representation.
124
        str_ = "{}/{}/{}".format(
1✔
125
            str(dt_obj.day).zfill(2),
126
            str(dt_obj.month).zfill(2),
127
            str(dt_obj.year + _BE_AD_DIFFERENCE).zfill(4),
128
        )
129
    elif fmt_char == "Y":
1✔
130
        # Year with century
131
        str_ = (str(dt_obj.year + _BE_AD_DIFFERENCE)).zfill(4)
1✔
132
    elif fmt_char == "y":
1✔
133
        # Year without century
134
        str_ = (str(dt_obj.year + _BE_AD_DIFFERENCE)[-2:]).zfill(2)
1✔
135
    elif fmt_char == "+":
1✔
136
        # National representation of the date and time
137
        # (the format is similar to that produced by date(1))
138
        # Wed  6 Oct 1976 01:40:00
139
        str_ = "{:<2} {:>2} {} {} {}".format(
1✔
140
            thai_abbr_weekdays[dt_obj.weekday()],
141
            dt_obj.day,
142
            thai_abbr_months[dt_obj.month - 1],
143
            dt_obj.year + _BE_AD_DIFFERENCE,
144
            dt_obj.strftime("%H:%M:%S"),
145
        )
146
    else:
147
        # No known localization available, use Python's default
148
        str_ = _std_strftime(dt_obj, fmt_char)
×
149

150
    return str_
1✔
151

152

153
def thai_strftime(
1✔
154
    dt_obj: datetime,
155
    fmt: str = "%-d %b %y",
156
    thaidigit: bool = False,
157
) -> str:
158
    """
159
    Convert :class:`datetime.datetime` into Thai date and time format.
160

161
    The formatting directives are similar to :func:`datatime.strrftime`.
162

163
    This function uses Thai names and Thai Buddhist Era for these directives:
164
        * **%a** - abbreviated weekday name
165
          (i.e. "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา")
166
        * **%A** - full weekday name
167
          (i.e. "วันจันทร์", "วันอังคาร", "วันเสาร์", "วันอาทิตย์")
168
        * **%b** - abbreviated month name
169
          (i.e. "ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.", "ธ.ค.")
170
        * **%B** - full month name
171
          (i.e. "มกราคม", "กุมภาพันธ์", "พฤศจิกายน", "ธันวาคม",)
172
        * **%y** - year without century (i.e. "56", "10")
173
        * **%Y** - year with century (i.e. "2556", "2410")
174
        * **%c** - date and time representation
175
          (i.e. "พ   6 ต.ค. 01:40:00 2519")
176
        * **%v** - short date representation
177
          (i.e. " 6-ม.ค.-2562", "27-ก.พ.-2555")
178

179
    Other directives will be passed to datetime.strftime()
180

181
    :Note:
182
        * The Thai Buddhist Era (BE) year is simply converted from AD
183
          by adding 543. This is certainly not accurate for years
184
          before 1941 AD, due to the change in Thai New Year's Day.
185
        * This meant to be an interim solution, since
186
          Python standard's locale module (which relied on C's strftime())
187
          does not support "th" or "th_TH" locale yet. If supported,
188
          we can just locale.setlocale(locale.LC_TIME, "th_TH")
189
          and then use native datetime.strftime().
190

191
    We are trying to make this platform-independent and support extensions
192
    as many as possible. See these links for strftime() extensions
193
    in POSIX, BSD, and GNU libc:
194

195
        * Python
196
          https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
197
        * C http://www.cplusplus.com/reference/ctime/strftime/
198
        * GNU https://metacpan.org/pod/POSIX::strftime::GNU
199
        * Linux https://linux.die.net/man/3/strftime
200
        * OpenBSD https://man.openbsd.org/strftime.3
201
        * FreeBSD https://www.unix.com/man-page/FreeBSD/3/strftime/
202
        * macOS
203
          https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/strftime.3.html
204
        * PHP https://secure.php.net/manual/en/function.strftime.php
205
        * JavaScript's implementation https://github.com/samsonjs/strftime
206
        * strftime() quick reference http://www.strftime.net/
207

208
    :param datetime dt_obj: an instantiatetd object of
209
                            :mod:`datetime.datetime`
210
    :param str fmt: string containing date and time directives
211
    :param bool thaidigit: If `thaidigit` is set to **False** (default),
212
                           number will be represented in Arabic digit.
213
                           If it is set to **True**, it will be represented
214
                           in Thai digit.
215

216
    :return: Date and time text, with month in Thai name and year in
217
             Thai Buddhist era. The year is simply converted from AD
218
             by adding 543 (will not accurate for years before 1941 AD,
219
             due to change in Thai New Year's Day).
220
    :rtype: str
221

222
    :Example:
223
    ::
224

225
        from datetime import datetime
226
        from pythainlp.util import thai_strftime
227

228
        datetime_obj = datetime(year=2019, month=6, day=9, \\
229
            hour=5, minute=59, second=0, microsecond=0)
230

231
        print(datetime_obj)
232
        # output: 2019-06-09 05:59:00
233

234
        thai_strftime(datetime_obj, "%A %d %B %Y")
235
        # output: 'วันอาทิตย์ 09 มิถุนายน 2562'
236

237
        thai_strftime(datetime_obj, "%a %-d %b %y")  # no padding
238
        # output: 'อา 9 มิ.ย. 62'
239

240
        thai_strftime(datetime_obj, "%a %_d %b %y")  # space padding
241
        # output: 'อา  9 มิ.ย. 62'
242

243
        thai_strftime(datetime_obj, "%a %0d %b %y")  # zero padding
244
        # output: 'อา 09 มิ.ย. 62'
245

246
        thai_strftime(datetime_obj, "%-H นาฬิกา %-M นาที", thaidigit=True)
247
        # output: '๕ นาฬิกา ๕๙ นาที'
248

249
        thai_strftime(datetime_obj, "%D (%v)")
250
        # output: '06/09/62 ( 9-มิ.ย.-2562)'
251

252
        thai_strftime(datetime_obj, "%c")
253
        # output: 'อา  9 มิ.ย. 05:59:00 2562'
254

255
        thai_strftime(datetime_obj, "%H:%M %p")
256
        # output: '01:40 AM'
257

258
        thai_strftime(datetime_obj, "%H:%M %#p")
259
        # output: '01:40 am'
260
    """
261
    thaidate_parts = []
1✔
262

263
    i = 0
1✔
264
    fmt_len = len(fmt)
1✔
265
    while i < fmt_len:
1✔
266
        str_ = ""
1✔
267
        if fmt[i] == "%":
1✔
268
            j = i + 1
1✔
269
            if j < fmt_len:
1✔
270
                fmt_char = fmt[j]
1✔
271
                if fmt_char in _NEED_L10N:  # requires localization?
1✔
272
                    str_ = _thai_strftime(dt_obj, fmt_char)
1✔
273
                elif fmt_char in _EXTENSIONS:
1✔
274
                    fmt_char_ext = fmt_char
1✔
275
                    k = j + 1
1✔
276
                    if k < fmt_len:
1✔
277
                        fmt_char = fmt[k]
1✔
278
                        if fmt_char in _NEED_L10N:
1✔
279
                            str_ = _thai_strftime(dt_obj, fmt_char)
1✔
280
                        else:
281
                            str_ = _std_strftime(dt_obj, fmt_char)
1✔
282

283
                        if fmt_char_ext == "-":
1✔
284
                            # GNU libc extension,
285
                            # no padding
286
                            if str_[0] and str_[0] in " 0":
1✔
287
                                str_ = str_[1:]
1✔
288
                        elif fmt_char_ext == "_":
1✔
289
                            # GNU libc extension,
290
                            # explicitly specify space (" ") for padding
291
                            if str_[0] and str_[0] == "0":
1✔
292
                                str_ = " " + str_[1:]
1✔
293
                        elif fmt_char_ext == "0":
1✔
294
                            # GNU libc extension,
295
                            # explicitly specify zero ("0") for padding
296
                            if str_[0] and str_[0] == " ":
1✔
297
                                str_ = "0" + str_[1:]
1✔
298
                        elif fmt_char_ext == "^":
1✔
299
                            # GNU libc extension,
300
                            # convert to upper case
301
                            str_ = str_.upper()
1✔
302
                        elif fmt_char_ext == "#":
1✔
303
                            # GNU libc extension,
304
                            # swap case - useful for %Z
305
                            str_ = str_.swapcase()
1✔
306
                        elif fmt_char_ext == "E":
1✔
307
                            # POSIX extension,
308
                            # uses the locale's alternative representation
309
                            # Not implemented yet
310
                            pass
1✔
311
                        elif fmt_char_ext == "O":
1✔
312
                            # POSIX extension,
313
                            # uses the locale's alternative numeric symbols
314
                            str_ = str_.translate(_HA_TH_DIGITS)
1✔
315
                        i = i + 1  # consume char after format char
1✔
316
                    else:
317
                        # format char at string's end has no meaning
318
                        str_ = fmt_char_ext
1✔
319
                else:  # not in _NEED_L10N nor _EXTENSIONS
320
                    # no known localization available, use Python's default
321
                    str_ = _std_strftime(dt_obj, fmt_char)
1✔
322

323
                i = i + 1  # consume char after "%"
1✔
324
            else:
325
                # % char at string's end has no meaning
326
                str_ = "%"
1✔
327
        else:
328
            str_ = fmt[i]
1✔
329

330
        thaidate_parts.append(str_)
1✔
331
        i = i + 1
1✔
332

333
    thaidate_text = "".join(thaidate_parts)
1✔
334

335
    if thaidigit:
1✔
336
        thaidate_text = thaidate_text.translate(_HA_TH_DIGITS)
1✔
337

338
    return thaidate_text
1✔
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

© 2025 Coveralls, Inc