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

PyThaiNLP / pythainlp / 4699361508

pending completion
4699361508

push

github

GitHub
Merge pull request #789 from PyThaiNLP/4.0

22 of 22 new or added lines in 6 files covered. (100.0%)

5749 of 6246 relevant lines covered (92.04%)

0.92 hits per line

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

96.23
/pythainlp/util/strftime.py
1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2016-2023 PyThaiNLP Project
3
#
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
# you may not use this file except in compliance with the License.
6
# You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
"""
1✔
16
Thai date/time formatting.
17
"""
18
import warnings
1✔
19
from datetime import datetime
1✔
20
from string import digits
1✔
21

22
from pythainlp import thai_digits
1✔
23
from pythainlp.util.date import (
1✔
24
    thai_abbr_months,
25
    thai_abbr_weekdays,
26
    thai_full_months,
27
    thai_full_weekdays,
28
)
29

30
__all__ = [
1✔
31
    "thai_strftime",
32
]
33

34
_HA_TH_DIGITS = str.maketrans(digits, thai_digits)
1✔
35
_BE_AD_DIFFERENCE = 543
1✔
36

37
_NEED_L10N = "AaBbCcDFGgvXxYy+"  # flags that need localization
1✔
38
_EXTENSIONS = "EO-_0^#"  # extension flags
1✔
39

40

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

66

67
def _thai_strftime(dt_obj: datetime, fmt_char: str) -> str:
1✔
68
    """
69
    Conversion support for thai_strftime().
70

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

161
    return str_
1✔
162

163

164
def thai_strftime(
1✔
165
    dt_obj: datetime,
166
    fmt: str = "%-d %b %y",
167
    thaidigit: bool = False,
168
) -> str:
169
    """
170
    Convert :class:`datetime.datetime` into Thai date and time format.
171

172
    The formatting directives are similar to :func:`datatime.strrftime`.
173

174
    This function uses Thai names and Thai Buddhist Era for these directives:
175
        * **%a** - abbreviated weekday name
176
          (i.e. "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา")
177
        * **%A** - full weekday name
178
          (i.e. "วันจันทร์", "วันอังคาร", "วันเสาร์", "วันอาทิตย์")
179
        * **%b** - abbreviated month name
180
          (i.e. "ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.", "ธ.ค.")
181
        * **%B** - full month name
182
          (i.e. "มกราคม", "กุมภาพันธ์", "พฤศจิกายน", "ธันวาคม",)
183
        * **%y** - year without century (i.e. "56", "10")
184
        * **%Y** - year with century (i.e. "2556", "2410")
185
        * **%c** - date and time representation
186
          (i.e. "พ   6 ต.ค. 01:40:00 2519")
187
        * **%v** - short date representation
188
          (i.e. " 6-ม.ค.-2562", "27-ก.พ.-2555")
189

190
    Other directives will be passed to datetime.strftime()
191

192
    :Note:
193
        * The Thai Buddhist Era (BE) year is simply converted from AD
194
          by adding 543. This is certainly not accurate for years
195
          before 1941 AD, due to the change in Thai New Year's Day.
196
        * This meant to be an interrim solution, since
197
          Python standard's locale module (which relied on C's strftime())
198
          does not support "th" or "th_TH" locale yet. If supported,
199
          we can just locale.setlocale(locale.LC_TIME, "th_TH")
200
          and then use native datetime.strftime().
201

202
    We trying to make this platform-independent and support extentions
203
    as many as possible. See these links for strftime() extensions
204
    in POSIX, BSD, and GNU libc:
205

206
        * Python
207
          https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior
208
        * C http://www.cplusplus.com/reference/ctime/strftime/
209
        * GNU https://metacpan.org/pod/POSIX::strftime::GNU
210
        * Linux https://linux.die.net/man/3/strftime
211
        * OpenBSD https://man.openbsd.org/strftime.3
212
        * FreeBSD https://www.unix.com/man-page/FreeBSD/3/strftime/
213
        * macOS
214
          https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/strftime.3.html
215
        * PHP https://secure.php.net/manual/en/function.strftime.php
216
        * JavaScript's implementation https://github.com/samsonjs/strftime
217
        * strftime() quick reference http://www.strftime.net/
218

219
    :param datetime dt_obj: an instantiatetd object of
220
                            :mod:`datetime.datetime`
221
    :param str fmt: string containing date and time directives
222
    :param bool thaidigit: If `thaidigit` is set to **False** (default),
223
                           number will be represented in Arabic digit.
224
                           If it is set to **True**, it will be represented
225
                           in Thai digit.
226

227
    :return: Date and time text, with month in Thai name and year in
228
             Thai Buddhist era. The year is simply converted from AD
229
             by adding 543 (will not accurate for years before 1941 AD,
230
             due to change in Thai New Year's Day).
231
    :rtype: str
232

233
    :Example:
234
    ::
235

236
        from datetime import datetime
237
        from pythainlp.util import thai_strftime
238

239
        datetime_obj = datetime(year=2019, month=6, day=9, \\
240
            hour=5, minute=59, second=0, microsecond=0)
241

242
        print(datetime_obj)
243
        # output: 2019-06-09 05:59:00
244

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

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

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

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

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

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

263
        thai_strftime(datetime_obj, "%c")
264
        # output: 'อา  9 มิ.ย. 05:59:00 2562'
265

266
        thai_strftime(datetime_obj, "%H:%M %p")
267
        # output: '01:40 AM'
268

269
        thai_strftime(datetime_obj, "%H:%M %#p")
270
        # output: '01:40 am'
271
    """
272
    thaidate_parts = []
1✔
273

274
    i = 0
1✔
275
    fmt_len = len(fmt)
1✔
276
    while i < fmt_len:
1✔
277
        str_ = ""
1✔
278
        if fmt[i] == "%":
1✔
279
            j = i + 1
1✔
280
            if j < fmt_len:
1✔
281
                fmt_char = fmt[j]
1✔
282
                if fmt_char in _NEED_L10N:  # requires localization?
1✔
283
                    str_ = _thai_strftime(dt_obj, fmt_char)
1✔
284
                elif fmt_char in _EXTENSIONS:
1✔
285
                    fmt_char_ext = fmt_char
1✔
286
                    k = j + 1
1✔
287
                    if k < fmt_len:
1✔
288
                        fmt_char = fmt[k]
1✔
289
                        if fmt_char in _NEED_L10N:
1✔
290
                            str_ = _thai_strftime(dt_obj, fmt_char)
1✔
291
                        else:
292
                            str_ = _std_strftime(dt_obj, fmt_char)
1✔
293

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

334
                i = i + 1  # consume char after "%"
1✔
335
            else:
336
                # % char at string's end has no meaning
337
                str_ = "%"
1✔
338
        else:
339
            str_ = fmt[i]
1✔
340

341
        thaidate_parts.append(str_)
1✔
342
        i = i + 1
1✔
343

344
    thaidate_text = "".join(thaidate_parts)
1✔
345

346
    if thaidigit:
1✔
347
        thaidate_text = thaidate_text.translate(_HA_TH_DIGITS)
1✔
348

349
    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