Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- rtme/CACHEDIR.TAG +1 -0
- rtme/bin/accelerate +10 -0
- rtme/bin/accelerate-config +10 -0
- rtme/bin/accelerate-estimate-memory +10 -0
- rtme/bin/accelerate-launch +10 -0
- rtme/bin/activate +116 -0
- rtme/bin/activate.bat +71 -0
- rtme/bin/activate.csh +76 -0
- rtme/bin/activate.nu +117 -0
- rtme/bin/activate.ps1 +82 -0
- rtme/bin/activate_this.py +59 -0
- rtme/bin/convert-caffe2-to-onnx +10 -0
- rtme/bin/deactivate.bat +39 -0
- rtme/bin/debugpy +10 -0
- rtme/bin/diffusers-cli +10 -0
- rtme/bin/f2py +10 -0
- rtme/bin/fonttools +10 -0
- rtme/bin/huggingface-cli +10 -0
- rtme/bin/nib-roi +10 -0
- rtme/bin/nib-stats +10 -0
- rtme/bin/pybabel +10 -0
- rtme/bin/pydoc.bat +22 -0
- rtme/lib/python3.10/site-packages/babel/__init__.py +38 -0
- rtme/lib/python3.10/site-packages/babel/core.py +1337 -0
- rtme/lib/python3.10/site-packages/babel/dates.py +1999 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/LICENSE.unicode +41 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/aa.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/aa_DJ.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/aa_ER.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/aa_ET.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ab.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ab_GE.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/af_NA.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/af_ZA.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/agq.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/agq_CM.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ak_GH.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/am_ET.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/an.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/an_ES.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ann.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ann_NG.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/apc.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/apc_SY.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ar_001.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ar_AE.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ar_BH.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ar_DJ.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ar_DZ.dat +0 -0
- rtme/lib/python3.10/site-packages/babel/locale-data/ar_EH.dat +0 -0
rtme/CACHEDIR.TAG
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Signature: 8a477f597d28d172789f06886806bc55
|
rtme/bin/accelerate
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from accelerate.commands.accelerate_cli import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/accelerate-config
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from accelerate.commands.config import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/accelerate-estimate-memory
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from accelerate.commands.estimate import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/accelerate-launch
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from accelerate.commands.launch import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/activate
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# This file must be used with "source bin/activate" *from bash*
|
| 23 |
+
# you cannot run it directly
|
| 24 |
+
|
| 25 |
+
# Get script path (only used if environment is relocatable).
|
| 26 |
+
if [ -n "${BASH_VERSION:+x}" ] ; then
|
| 27 |
+
SCRIPT_PATH="${BASH_SOURCE[0]}"
|
| 28 |
+
if [ "$SCRIPT_PATH" = "$0" ]; then
|
| 29 |
+
# Only bash has a reasonably robust check for source'dness.
|
| 30 |
+
echo "You must source this script: \$ source $0" >&2
|
| 31 |
+
exit 33
|
| 32 |
+
fi
|
| 33 |
+
elif [ -n "${ZSH_VERSION:+x}" ] ; then
|
| 34 |
+
SCRIPT_PATH="${(%):-%x}"
|
| 35 |
+
elif [ -n "${KSH_VERSION:+x}" ] ; then
|
| 36 |
+
SCRIPT_PATH="${.sh.file}"
|
| 37 |
+
fi
|
| 38 |
+
|
| 39 |
+
deactivate () {
|
| 40 |
+
unset -f pydoc >/dev/null 2>&1 || true
|
| 41 |
+
|
| 42 |
+
# reset old environment variables
|
| 43 |
+
# ! [ -z ${VAR+_} ] returns true if VAR is declared at all
|
| 44 |
+
if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then
|
| 45 |
+
PATH="$_OLD_VIRTUAL_PATH"
|
| 46 |
+
export PATH
|
| 47 |
+
unset _OLD_VIRTUAL_PATH
|
| 48 |
+
fi
|
| 49 |
+
if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then
|
| 50 |
+
PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME"
|
| 51 |
+
export PYTHONHOME
|
| 52 |
+
unset _OLD_VIRTUAL_PYTHONHOME
|
| 53 |
+
fi
|
| 54 |
+
|
| 55 |
+
# The hash command must be called to get it to forget past
|
| 56 |
+
# commands. Without forgetting past commands the $PATH changes
|
| 57 |
+
# we made may not be respected
|
| 58 |
+
hash -r 2>/dev/null
|
| 59 |
+
|
| 60 |
+
if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then
|
| 61 |
+
PS1="$_OLD_VIRTUAL_PS1"
|
| 62 |
+
export PS1
|
| 63 |
+
unset _OLD_VIRTUAL_PS1
|
| 64 |
+
fi
|
| 65 |
+
|
| 66 |
+
unset VIRTUAL_ENV
|
| 67 |
+
unset VIRTUAL_ENV_PROMPT
|
| 68 |
+
if [ ! "${1-}" = "nondestructive" ] ; then
|
| 69 |
+
# Self destruct!
|
| 70 |
+
unset -f deactivate
|
| 71 |
+
fi
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
# unset irrelevant variables
|
| 75 |
+
deactivate nondestructive
|
| 76 |
+
|
| 77 |
+
VIRTUAL_ENV='/workspace/rtme'
|
| 78 |
+
if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then
|
| 79 |
+
VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV")
|
| 80 |
+
fi
|
| 81 |
+
export VIRTUAL_ENV
|
| 82 |
+
|
| 83 |
+
_OLD_VIRTUAL_PATH="$PATH"
|
| 84 |
+
PATH="$VIRTUAL_ENV/bin:$PATH"
|
| 85 |
+
export PATH
|
| 86 |
+
|
| 87 |
+
if [ "x" != x ] ; then
|
| 88 |
+
VIRTUAL_ENV_PROMPT="() "
|
| 89 |
+
else
|
| 90 |
+
VIRTUAL_ENV_PROMPT="($(basename "$VIRTUAL_ENV")) "
|
| 91 |
+
fi
|
| 92 |
+
export VIRTUAL_ENV_PROMPT
|
| 93 |
+
|
| 94 |
+
# unset PYTHONHOME if set
|
| 95 |
+
if ! [ -z "${PYTHONHOME+_}" ] ; then
|
| 96 |
+
_OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME"
|
| 97 |
+
unset PYTHONHOME
|
| 98 |
+
fi
|
| 99 |
+
|
| 100 |
+
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then
|
| 101 |
+
_OLD_VIRTUAL_PS1="${PS1-}"
|
| 102 |
+
PS1="${VIRTUAL_ENV_PROMPT}${PS1-}"
|
| 103 |
+
export PS1
|
| 104 |
+
fi
|
| 105 |
+
|
| 106 |
+
# Make sure to unalias pydoc if it's already there
|
| 107 |
+
alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true
|
| 108 |
+
|
| 109 |
+
pydoc () {
|
| 110 |
+
python -m pydoc "$@"
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
# The hash command must be called to get it to forget past
|
| 114 |
+
# commands. Without forgetting past commands the $PATH changes
|
| 115 |
+
# we made may not be respected
|
| 116 |
+
hash -r 2>/dev/null
|
rtme/bin/activate.bat
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
@REM This file is UTF-8 encoded, so we need to update the current code page while executing it
|
| 23 |
+
@for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do @set _OLD_CODEPAGE=%%a
|
| 24 |
+
@if defined _OLD_CODEPAGE (
|
| 25 |
+
@"%SystemRoot%\System32\chcp.com" 65001 > nul
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
@for %%i in ("/workspace/rtme") do @set "VIRTUAL_ENV=%%~fi"
|
| 29 |
+
|
| 30 |
+
@set "VIRTUAL_ENV_PROMPT="
|
| 31 |
+
@if NOT DEFINED VIRTUAL_ENV_PROMPT (
|
| 32 |
+
@for %%d in ("%VIRTUAL_ENV%") do @set "VIRTUAL_ENV_PROMPT=%%~nxd"
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
@if defined _OLD_VIRTUAL_PROMPT (
|
| 36 |
+
@set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
| 37 |
+
) else (
|
| 38 |
+
@if not defined PROMPT (
|
| 39 |
+
@set "PROMPT=$P$G"
|
| 40 |
+
)
|
| 41 |
+
@if not defined VIRTUAL_ENV_DISABLE_PROMPT (
|
| 42 |
+
@set "_OLD_VIRTUAL_PROMPT=%PROMPT%"
|
| 43 |
+
)
|
| 44 |
+
)
|
| 45 |
+
@if not defined VIRTUAL_ENV_DISABLE_PROMPT (
|
| 46 |
+
@set "PROMPT=(%VIRTUAL_ENV_PROMPT%) %PROMPT%"
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
@REM Don't use () to avoid problems with them in %PATH%
|
| 50 |
+
@if defined _OLD_VIRTUAL_PYTHONHOME @goto ENDIFVHOME
|
| 51 |
+
@set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%"
|
| 52 |
+
:ENDIFVHOME
|
| 53 |
+
|
| 54 |
+
@set PYTHONHOME=
|
| 55 |
+
|
| 56 |
+
@REM if defined _OLD_VIRTUAL_PATH (
|
| 57 |
+
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH1
|
| 58 |
+
@set "PATH=%_OLD_VIRTUAL_PATH%"
|
| 59 |
+
:ENDIFVPATH1
|
| 60 |
+
@REM ) else (
|
| 61 |
+
@if defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH2
|
| 62 |
+
@set "_OLD_VIRTUAL_PATH=%PATH%"
|
| 63 |
+
:ENDIFVPATH2
|
| 64 |
+
|
| 65 |
+
@set "PATH=%VIRTUAL_ENV%\bin;%PATH%"
|
| 66 |
+
|
| 67 |
+
:END
|
| 68 |
+
@if defined _OLD_CODEPAGE (
|
| 69 |
+
@"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
|
| 70 |
+
@set _OLD_CODEPAGE=
|
| 71 |
+
)
|
rtme/bin/activate.csh
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# This file must be used with "source bin/activate.csh" *from csh*.
|
| 23 |
+
# You cannot run it directly.
|
| 24 |
+
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
| 25 |
+
|
| 26 |
+
set newline='\
|
| 27 |
+
'
|
| 28 |
+
|
| 29 |
+
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH:q" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT:q" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate && unalias pydoc'
|
| 30 |
+
|
| 31 |
+
# Unset irrelevant variables.
|
| 32 |
+
deactivate nondestructive
|
| 33 |
+
|
| 34 |
+
setenv VIRTUAL_ENV '/workspace/rtme'
|
| 35 |
+
|
| 36 |
+
set _OLD_VIRTUAL_PATH="$PATH:q"
|
| 37 |
+
setenv PATH "$VIRTUAL_ENV:q/bin:$PATH:q"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
if ('' != "") then
|
| 42 |
+
setenv VIRTUAL_ENV_PROMPT ''
|
| 43 |
+
else
|
| 44 |
+
setenv VIRTUAL_ENV_PROMPT "$VIRTUAL_ENV:t:q"
|
| 45 |
+
endif
|
| 46 |
+
|
| 47 |
+
if ( $?VIRTUAL_ENV_DISABLE_PROMPT ) then
|
| 48 |
+
if ( $VIRTUAL_ENV_DISABLE_PROMPT == "" ) then
|
| 49 |
+
set do_prompt = "1"
|
| 50 |
+
else
|
| 51 |
+
set do_prompt = "0"
|
| 52 |
+
endif
|
| 53 |
+
else
|
| 54 |
+
set do_prompt = "1"
|
| 55 |
+
endif
|
| 56 |
+
|
| 57 |
+
if ( $do_prompt == "1" ) then
|
| 58 |
+
# Could be in a non-interactive environment,
|
| 59 |
+
# in which case, $prompt is undefined and we wouldn't
|
| 60 |
+
# care about the prompt anyway.
|
| 61 |
+
if ( $?prompt ) then
|
| 62 |
+
set _OLD_VIRTUAL_PROMPT="$prompt:q"
|
| 63 |
+
if ( "$prompt:q" =~ *"$newline:q"* ) then
|
| 64 |
+
:
|
| 65 |
+
else
|
| 66 |
+
set prompt = '('"$VIRTUAL_ENV_PROMPT:q"') '"$prompt:q"
|
| 67 |
+
endif
|
| 68 |
+
endif
|
| 69 |
+
endif
|
| 70 |
+
|
| 71 |
+
unset env_name
|
| 72 |
+
unset do_prompt
|
| 73 |
+
|
| 74 |
+
alias pydoc python -m pydoc
|
| 75 |
+
|
| 76 |
+
rehash
|
rtme/bin/activate.nu
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
# virtualenv activation module
|
| 23 |
+
# Activate with `overlay use activate.nu`
|
| 24 |
+
# Deactivate with `deactivate`, as usual
|
| 25 |
+
#
|
| 26 |
+
# To customize the overlay name, you can call `overlay use activate.nu as foo`,
|
| 27 |
+
# but then simply `deactivate` won't work because it is just an alias to hide
|
| 28 |
+
# the "activate" overlay. You'd need to call `overlay hide foo` manually.
|
| 29 |
+
|
| 30 |
+
export-env {
|
| 31 |
+
def is-string [x] {
|
| 32 |
+
($x | describe) == 'string'
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
def has-env [...names] {
|
| 36 |
+
$names | each {|n|
|
| 37 |
+
$n in $env
|
| 38 |
+
} | all {|i| $i == true}
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
# Emulates a `test -z`, but better as it handles e.g 'false'
|
| 42 |
+
def is-env-true [name: string] {
|
| 43 |
+
if (has-env $name) {
|
| 44 |
+
# Try to parse 'true', '0', '1', and fail if not convertible
|
| 45 |
+
let parsed = (do -i { $env | get $name | into bool })
|
| 46 |
+
if ($parsed | describe) == 'bool' {
|
| 47 |
+
$parsed
|
| 48 |
+
} else {
|
| 49 |
+
not ($env | get -i $name | is-empty)
|
| 50 |
+
}
|
| 51 |
+
} else {
|
| 52 |
+
false
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
let virtual_env = '/workspace/rtme'
|
| 57 |
+
let bin = 'bin'
|
| 58 |
+
|
| 59 |
+
let is_windows = ($nu.os-info.family) == 'windows'
|
| 60 |
+
let path_name = (if (has-env 'Path') {
|
| 61 |
+
'Path'
|
| 62 |
+
} else {
|
| 63 |
+
'PATH'
|
| 64 |
+
}
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
let venv_path = ([$virtual_env $bin] | path join)
|
| 68 |
+
let new_path = ($env | get $path_name | prepend $venv_path)
|
| 69 |
+
|
| 70 |
+
# If there is no default prompt, then use the env name instead
|
| 71 |
+
let virtual_env_prompt = (if ('' | is-empty) {
|
| 72 |
+
($virtual_env | path basename)
|
| 73 |
+
} else {
|
| 74 |
+
''
|
| 75 |
+
})
|
| 76 |
+
|
| 77 |
+
let new_env = {
|
| 78 |
+
$path_name : $new_path
|
| 79 |
+
VIRTUAL_ENV : $virtual_env
|
| 80 |
+
VIRTUAL_ENV_PROMPT : $virtual_env_prompt
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
let new_env = (if (is-env-true 'VIRTUAL_ENV_DISABLE_PROMPT') {
|
| 84 |
+
$new_env
|
| 85 |
+
} else {
|
| 86 |
+
# Creating the new prompt for the session
|
| 87 |
+
let virtual_prefix = $'(char lparen)($virtual_env_prompt)(char rparen) '
|
| 88 |
+
|
| 89 |
+
# Back up the old prompt builder
|
| 90 |
+
let old_prompt_command = (if (has-env 'PROMPT_COMMAND') {
|
| 91 |
+
$env.PROMPT_COMMAND
|
| 92 |
+
} else {
|
| 93 |
+
''
|
| 94 |
+
})
|
| 95 |
+
|
| 96 |
+
let new_prompt = (if (has-env 'PROMPT_COMMAND') {
|
| 97 |
+
if 'closure' in ($old_prompt_command | describe) {
|
| 98 |
+
{|| $'($virtual_prefix)(do $old_prompt_command)' }
|
| 99 |
+
} else {
|
| 100 |
+
{|| $'($virtual_prefix)($old_prompt_command)' }
|
| 101 |
+
}
|
| 102 |
+
} else {
|
| 103 |
+
{|| $'($virtual_prefix)' }
|
| 104 |
+
})
|
| 105 |
+
|
| 106 |
+
$new_env | merge {
|
| 107 |
+
PROMPT_COMMAND : $new_prompt
|
| 108 |
+
VIRTUAL_PREFIX : $virtual_prefix
|
| 109 |
+
}
|
| 110 |
+
})
|
| 111 |
+
|
| 112 |
+
# Environment variables that will be loaded as the virtual env
|
| 113 |
+
load-env $new_env
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
export alias pydoc = python -m pydoc
|
| 117 |
+
export alias deactivate = overlay hide activate
|
rtme/bin/activate.ps1
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
$script:THIS_PATH = $myinvocation.mycommand.path
|
| 23 |
+
$script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent
|
| 24 |
+
|
| 25 |
+
function global:deactivate([switch] $NonDestructive) {
|
| 26 |
+
if (Test-Path variable:_OLD_VIRTUAL_PATH) {
|
| 27 |
+
$env:PATH = $variable:_OLD_VIRTUAL_PATH
|
| 28 |
+
Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
if (Test-Path function:_old_virtual_prompt) {
|
| 32 |
+
$function:prompt = $function:_old_virtual_prompt
|
| 33 |
+
Remove-Item function:\_old_virtual_prompt
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
if ($env:VIRTUAL_ENV) {
|
| 37 |
+
Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
if ($env:VIRTUAL_ENV_PROMPT) {
|
| 41 |
+
Remove-Item env:VIRTUAL_ENV_PROMPT -ErrorAction SilentlyContinue
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
if (!$NonDestructive) {
|
| 45 |
+
# Self destruct!
|
| 46 |
+
Remove-Item function:deactivate
|
| 47 |
+
Remove-Item function:pydoc
|
| 48 |
+
}
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function global:pydoc {
|
| 52 |
+
python -m pydoc $args
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
# unset irrelevant variables
|
| 56 |
+
deactivate -nondestructive
|
| 57 |
+
|
| 58 |
+
$VIRTUAL_ENV = $BASE_DIR
|
| 59 |
+
$env:VIRTUAL_ENV = $VIRTUAL_ENV
|
| 60 |
+
|
| 61 |
+
if ("" -ne "") {
|
| 62 |
+
$env:VIRTUAL_ENV_PROMPT = ""
|
| 63 |
+
}
|
| 64 |
+
else {
|
| 65 |
+
$env:VIRTUAL_ENV_PROMPT = $( Split-Path $env:VIRTUAL_ENV -Leaf )
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH
|
| 69 |
+
|
| 70 |
+
$env:PATH = "$env:VIRTUAL_ENV/bin:" + $env:PATH
|
| 71 |
+
if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
| 72 |
+
function global:_old_virtual_prompt {
|
| 73 |
+
""
|
| 74 |
+
}
|
| 75 |
+
$function:_old_virtual_prompt = $function:prompt
|
| 76 |
+
|
| 77 |
+
function global:prompt {
|
| 78 |
+
# Add the custom prefix to the existing prompt
|
| 79 |
+
$previous_prompt_value = & $function:_old_virtual_prompt
|
| 80 |
+
("(" + $env:VIRTUAL_ENV_PROMPT + ") " + $previous_prompt_value)
|
| 81 |
+
}
|
| 82 |
+
}
|
rtme/bin/activate_this.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
# a copy of this software and associated documentation files (the
|
| 5 |
+
# "Software"), to deal in the Software without restriction, including
|
| 6 |
+
# without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
# permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
# the following conditions:
|
| 10 |
+
#
|
| 11 |
+
# The above copyright notice and this permission notice shall be
|
| 12 |
+
# included in all copies or substantial portions of the Software.
|
| 13 |
+
#
|
| 14 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
"""
|
| 23 |
+
Activate virtualenv for current interpreter:
|
| 24 |
+
|
| 25 |
+
import runpy
|
| 26 |
+
runpy.run_path(this_file)
|
| 27 |
+
|
| 28 |
+
This can be used when you must use an existing Python interpreter, not the virtualenv bin/python.
|
| 29 |
+
""" # noqa: D415
|
| 30 |
+
|
| 31 |
+
from __future__ import annotations
|
| 32 |
+
|
| 33 |
+
import os
|
| 34 |
+
import site
|
| 35 |
+
import sys
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
abs_file = os.path.abspath(__file__)
|
| 39 |
+
except NameError as exc:
|
| 40 |
+
msg = "You must use import runpy; runpy.run_path(this_file)"
|
| 41 |
+
raise AssertionError(msg) from exc
|
| 42 |
+
|
| 43 |
+
bin_dir = os.path.dirname(abs_file)
|
| 44 |
+
base = bin_dir[: -len("bin") - 1] # strip away the bin part from the __file__, plus the path separator
|
| 45 |
+
|
| 46 |
+
# prepend bin to PATH (this file is inside the bin directory)
|
| 47 |
+
os.environ["PATH"] = os.pathsep.join([bin_dir, *os.environ.get("PATH", "").split(os.pathsep)])
|
| 48 |
+
os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory
|
| 49 |
+
os.environ["VIRTUAL_ENV_PROMPT"] = "" or os.path.basename(base) # noqa: SIM222
|
| 50 |
+
|
| 51 |
+
# add the virtual environments libraries to the host python import mechanism
|
| 52 |
+
prev_length = len(sys.path)
|
| 53 |
+
for lib in "../lib/python3.10/site-packages".split(os.pathsep):
|
| 54 |
+
path = os.path.realpath(os.path.join(bin_dir, lib))
|
| 55 |
+
site.addsitedir(path)
|
| 56 |
+
sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
|
| 57 |
+
|
| 58 |
+
sys.real_prefix = sys.prefix
|
| 59 |
+
sys.prefix = base
|
rtme/bin/convert-caffe2-to-onnx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from caffe2.python.onnx.bin.conversion import caffe2_to_onnx
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(caffe2_to_onnx())
|
rtme/bin/deactivate.bat
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
@set VIRTUAL_ENV=
|
| 23 |
+
@set VIRTUAL_ENV_PROMPT=
|
| 24 |
+
|
| 25 |
+
@REM Don't use () to avoid problems with them in %PATH%
|
| 26 |
+
@if not defined _OLD_VIRTUAL_PROMPT @goto ENDIFVPROMPT
|
| 27 |
+
@set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
| 28 |
+
@set _OLD_VIRTUAL_PROMPT=
|
| 29 |
+
:ENDIFVPROMPT
|
| 30 |
+
|
| 31 |
+
@if not defined _OLD_VIRTUAL_PYTHONHOME @goto ENDIFVHOME
|
| 32 |
+
@set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
|
| 33 |
+
@set _OLD_VIRTUAL_PYTHONHOME=
|
| 34 |
+
:ENDIFVHOME
|
| 35 |
+
|
| 36 |
+
@if not defined _OLD_VIRTUAL_PATH @goto ENDIFVPATH
|
| 37 |
+
@set "PATH=%_OLD_VIRTUAL_PATH%"
|
| 38 |
+
@set _OLD_VIRTUAL_PATH=
|
| 39 |
+
:ENDIFVPATH
|
rtme/bin/debugpy
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from debugpy.server.cli import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/diffusers-cli
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from diffusers.commands.diffusers_cli import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/f2py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from numpy.f2py.f2py2e import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/fonttools
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from fontTools.__main__ import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/huggingface-cli
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from huggingface_hub.commands.huggingface_cli import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/nib-roi
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from nibabel.cmdline.roi import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/nib-stats
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from nibabel.cmdline.stats import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/pybabel
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/rtme/bin/python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import sys
|
| 4 |
+
from babel.messages.frontend import main
|
| 5 |
+
if __name__ == "__main__":
|
| 6 |
+
if sys.argv[0].endswith("-script.pyw"):
|
| 7 |
+
sys.argv[0] = sys.argv[0][:-11]
|
| 8 |
+
elif sys.argv[0].endswith(".exe"):
|
| 9 |
+
sys.argv[0] = sys.argv[0][:-4]
|
| 10 |
+
sys.exit(main())
|
rtme/bin/pydoc.bat
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@REM Copyright (c) 2020-202x The virtualenv developers
|
| 2 |
+
@REM
|
| 3 |
+
@REM Permission is hereby granted, free of charge, to any person obtaining
|
| 4 |
+
@REM a copy of this software and associated documentation files (the
|
| 5 |
+
@REM "Software"), to deal in the Software without restriction, including
|
| 6 |
+
@REM without limitation the rights to use, copy, modify, merge, publish,
|
| 7 |
+
@REM distribute, sublicense, and/or sell copies of the Software, and to
|
| 8 |
+
@REM permit persons to whom the Software is furnished to do so, subject to
|
| 9 |
+
@REM the following conditions:
|
| 10 |
+
@REM
|
| 11 |
+
@REM The above copyright notice and this permission notice shall be
|
| 12 |
+
@REM included in all copies or substantial portions of the Software.
|
| 13 |
+
@REM
|
| 14 |
+
@REM THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 15 |
+
@REM EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 16 |
+
@REM MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
| 17 |
+
@REM NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
| 18 |
+
@REM LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
| 19 |
+
@REM OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 20 |
+
@REM WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 21 |
+
|
| 22 |
+
python.exe -m pydoc %*
|
rtme/lib/python3.10/site-packages/babel/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
babel
|
| 3 |
+
~~~~~
|
| 4 |
+
|
| 5 |
+
Integrated collection of utilities that assist in internationalizing and
|
| 6 |
+
localizing applications.
|
| 7 |
+
|
| 8 |
+
This package is basically composed of two major parts:
|
| 9 |
+
|
| 10 |
+
* tools to build and work with ``gettext`` message catalogs
|
| 11 |
+
* a Python interface to the CLDR (Common Locale Data Repository), providing
|
| 12 |
+
access to various locale display names, localized number and date
|
| 13 |
+
formatting, etc.
|
| 14 |
+
|
| 15 |
+
:copyright: (c) 2013-2025 by the Babel Team.
|
| 16 |
+
:license: BSD, see LICENSE for more details.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from babel.core import (
|
| 20 |
+
Locale,
|
| 21 |
+
UnknownLocaleError,
|
| 22 |
+
default_locale,
|
| 23 |
+
get_locale_identifier,
|
| 24 |
+
negotiate_locale,
|
| 25 |
+
parse_locale,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
__version__ = '2.17.0'
|
| 29 |
+
|
| 30 |
+
__all__ = [
|
| 31 |
+
'Locale',
|
| 32 |
+
'UnknownLocaleError',
|
| 33 |
+
'__version__',
|
| 34 |
+
'default_locale',
|
| 35 |
+
'get_locale_identifier',
|
| 36 |
+
'negotiate_locale',
|
| 37 |
+
'parse_locale',
|
| 38 |
+
]
|
rtme/lib/python3.10/site-packages/babel/core.py
ADDED
|
@@ -0,0 +1,1337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
babel.core
|
| 3 |
+
~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Core locale representation and locale data access.
|
| 6 |
+
|
| 7 |
+
:copyright: (c) 2013-2025 by the Babel Team.
|
| 8 |
+
:license: BSD, see LICENSE for more details.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import os
|
| 14 |
+
import pickle
|
| 15 |
+
from collections.abc import Iterable, Mapping
|
| 16 |
+
from typing import TYPE_CHECKING, Any, Literal
|
| 17 |
+
|
| 18 |
+
from babel import localedata
|
| 19 |
+
from babel.plural import PluralRule
|
| 20 |
+
|
| 21 |
+
__all__ = [
|
| 22 |
+
'Locale',
|
| 23 |
+
'UnknownLocaleError',
|
| 24 |
+
'default_locale',
|
| 25 |
+
'get_global',
|
| 26 |
+
'get_locale_identifier',
|
| 27 |
+
'negotiate_locale',
|
| 28 |
+
'parse_locale',
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
if TYPE_CHECKING:
|
| 32 |
+
from typing_extensions import TypeAlias
|
| 33 |
+
|
| 34 |
+
_GLOBAL_KEY: TypeAlias = Literal[
|
| 35 |
+
"all_currencies",
|
| 36 |
+
"currency_fractions",
|
| 37 |
+
"language_aliases",
|
| 38 |
+
"likely_subtags",
|
| 39 |
+
"meta_zones",
|
| 40 |
+
"parent_exceptions",
|
| 41 |
+
"script_aliases",
|
| 42 |
+
"territory_aliases",
|
| 43 |
+
"territory_currencies",
|
| 44 |
+
"territory_languages",
|
| 45 |
+
"territory_zones",
|
| 46 |
+
"variant_aliases",
|
| 47 |
+
"windows_zone_mapping",
|
| 48 |
+
"zone_aliases",
|
| 49 |
+
"zone_territories",
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
+
_global_data: Mapping[_GLOBAL_KEY, Mapping[str, Any]] | None
|
| 53 |
+
|
| 54 |
+
_global_data = None
|
| 55 |
+
_default_plural_rule = PluralRule({})
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _raise_no_data_error():
|
| 59 |
+
raise RuntimeError('The babel data files are not available. '
|
| 60 |
+
'This usually happens because you are using '
|
| 61 |
+
'a source checkout from Babel and you did '
|
| 62 |
+
'not build the data files. Just make sure '
|
| 63 |
+
'to run "python setup.py import_cldr" before '
|
| 64 |
+
'installing the library.')
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def get_global(key: _GLOBAL_KEY) -> Mapping[str, Any]:
|
| 68 |
+
"""Return the dictionary for the given key in the global data.
|
| 69 |
+
|
| 70 |
+
The global data is stored in the ``babel/global.dat`` file and contains
|
| 71 |
+
information independent of individual locales.
|
| 72 |
+
|
| 73 |
+
>>> get_global('zone_aliases')['UTC']
|
| 74 |
+
u'Etc/UTC'
|
| 75 |
+
>>> get_global('zone_territories')['Europe/Berlin']
|
| 76 |
+
u'DE'
|
| 77 |
+
|
| 78 |
+
The keys available are:
|
| 79 |
+
|
| 80 |
+
- ``all_currencies``
|
| 81 |
+
- ``currency_fractions``
|
| 82 |
+
- ``language_aliases``
|
| 83 |
+
- ``likely_subtags``
|
| 84 |
+
- ``parent_exceptions``
|
| 85 |
+
- ``script_aliases``
|
| 86 |
+
- ``territory_aliases``
|
| 87 |
+
- ``territory_currencies``
|
| 88 |
+
- ``territory_languages``
|
| 89 |
+
- ``territory_zones``
|
| 90 |
+
- ``variant_aliases``
|
| 91 |
+
- ``windows_zone_mapping``
|
| 92 |
+
- ``zone_aliases``
|
| 93 |
+
- ``zone_territories``
|
| 94 |
+
|
| 95 |
+
.. note:: The internal structure of the data may change between versions.
|
| 96 |
+
|
| 97 |
+
.. versionadded:: 0.9
|
| 98 |
+
|
| 99 |
+
:param key: the data key
|
| 100 |
+
"""
|
| 101 |
+
global _global_data
|
| 102 |
+
if _global_data is None:
|
| 103 |
+
dirname = os.path.join(os.path.dirname(__file__))
|
| 104 |
+
filename = os.path.join(dirname, 'global.dat')
|
| 105 |
+
if not os.path.isfile(filename):
|
| 106 |
+
_raise_no_data_error()
|
| 107 |
+
with open(filename, 'rb') as fileobj:
|
| 108 |
+
_global_data = pickle.load(fileobj)
|
| 109 |
+
assert _global_data is not None
|
| 110 |
+
return _global_data.get(key, {})
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
LOCALE_ALIASES = {
|
| 114 |
+
'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ',
|
| 115 |
+
'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES',
|
| 116 |
+
'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES',
|
| 117 |
+
'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT',
|
| 118 |
+
'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV',
|
| 119 |
+
'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL',
|
| 120 |
+
'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI',
|
| 121 |
+
'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA',
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class UnknownLocaleError(Exception):
|
| 126 |
+
"""Exception thrown when a locale is requested for which no locale data
|
| 127 |
+
is available.
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
def __init__(self, identifier: str) -> None:
|
| 131 |
+
"""Create the exception.
|
| 132 |
+
|
| 133 |
+
:param identifier: the identifier string of the unsupported locale
|
| 134 |
+
"""
|
| 135 |
+
Exception.__init__(self, f"unknown locale {identifier!r}")
|
| 136 |
+
|
| 137 |
+
#: The identifier of the locale that could not be found.
|
| 138 |
+
self.identifier = identifier
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
class Locale:
|
| 142 |
+
"""Representation of a specific locale.
|
| 143 |
+
|
| 144 |
+
>>> locale = Locale('en', 'US')
|
| 145 |
+
>>> repr(locale)
|
| 146 |
+
"Locale('en', territory='US')"
|
| 147 |
+
>>> locale.display_name
|
| 148 |
+
u'English (United States)'
|
| 149 |
+
|
| 150 |
+
A `Locale` object can also be instantiated from a raw locale string:
|
| 151 |
+
|
| 152 |
+
>>> locale = Locale.parse('en-US', sep='-')
|
| 153 |
+
>>> repr(locale)
|
| 154 |
+
"Locale('en', territory='US')"
|
| 155 |
+
|
| 156 |
+
`Locale` objects provide access to a collection of locale data, such as
|
| 157 |
+
territory and language names, number and date format patterns, and more:
|
| 158 |
+
|
| 159 |
+
>>> locale.number_symbols['latn']['decimal']
|
| 160 |
+
u'.'
|
| 161 |
+
|
| 162 |
+
If a locale is requested for which no locale data is available, an
|
| 163 |
+
`UnknownLocaleError` is raised:
|
| 164 |
+
|
| 165 |
+
>>> Locale.parse('en_XX')
|
| 166 |
+
Traceback (most recent call last):
|
| 167 |
+
...
|
| 168 |
+
UnknownLocaleError: unknown locale 'en_XX'
|
| 169 |
+
|
| 170 |
+
For more information see :rfc:`3066`.
|
| 171 |
+
"""
|
| 172 |
+
|
| 173 |
+
def __init__(
|
| 174 |
+
self,
|
| 175 |
+
language: str,
|
| 176 |
+
territory: str | None = None,
|
| 177 |
+
script: str | None = None,
|
| 178 |
+
variant: str | None = None,
|
| 179 |
+
modifier: str | None = None,
|
| 180 |
+
) -> None:
|
| 181 |
+
"""Initialize the locale object from the given identifier components.
|
| 182 |
+
|
| 183 |
+
>>> locale = Locale('en', 'US')
|
| 184 |
+
>>> locale.language
|
| 185 |
+
'en'
|
| 186 |
+
>>> locale.territory
|
| 187 |
+
'US'
|
| 188 |
+
|
| 189 |
+
:param language: the language code
|
| 190 |
+
:param territory: the territory (country or region) code
|
| 191 |
+
:param script: the script code
|
| 192 |
+
:param variant: the variant code
|
| 193 |
+
:param modifier: a modifier (following the '@' symbol, sometimes called '@variant')
|
| 194 |
+
:raise `UnknownLocaleError`: if no locale data is available for the
|
| 195 |
+
requested locale
|
| 196 |
+
"""
|
| 197 |
+
#: the language code
|
| 198 |
+
self.language = language
|
| 199 |
+
#: the territory (country or region) code
|
| 200 |
+
self.territory = territory
|
| 201 |
+
#: the script code
|
| 202 |
+
self.script = script
|
| 203 |
+
#: the variant code
|
| 204 |
+
self.variant = variant
|
| 205 |
+
#: the modifier
|
| 206 |
+
self.modifier = modifier
|
| 207 |
+
self.__data: localedata.LocaleDataDict | None = None
|
| 208 |
+
|
| 209 |
+
identifier = str(self)
|
| 210 |
+
identifier_without_modifier = identifier.partition('@')[0]
|
| 211 |
+
if localedata.exists(identifier):
|
| 212 |
+
self.__data_identifier = identifier
|
| 213 |
+
elif localedata.exists(identifier_without_modifier):
|
| 214 |
+
self.__data_identifier = identifier_without_modifier
|
| 215 |
+
else:
|
| 216 |
+
raise UnknownLocaleError(identifier)
|
| 217 |
+
|
| 218 |
+
@classmethod
|
| 219 |
+
def default(cls, category: str | None = None, aliases: Mapping[str, str] = LOCALE_ALIASES) -> Locale:
|
| 220 |
+
"""Return the system default locale for the specified category.
|
| 221 |
+
|
| 222 |
+
>>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
|
| 223 |
+
... os.environ[name] = ''
|
| 224 |
+
>>> os.environ['LANG'] = 'fr_FR.UTF-8'
|
| 225 |
+
>>> Locale.default('LC_MESSAGES')
|
| 226 |
+
Locale('fr', territory='FR')
|
| 227 |
+
|
| 228 |
+
The following fallbacks to the variable are always considered:
|
| 229 |
+
|
| 230 |
+
- ``LANGUAGE``
|
| 231 |
+
- ``LC_ALL``
|
| 232 |
+
- ``LC_CTYPE``
|
| 233 |
+
- ``LANG``
|
| 234 |
+
|
| 235 |
+
:param category: one of the ``LC_XXX`` environment variable names
|
| 236 |
+
:param aliases: a dictionary of aliases for locale identifiers
|
| 237 |
+
"""
|
| 238 |
+
# XXX: use likely subtag expansion here instead of the
|
| 239 |
+
# aliases dictionary.
|
| 240 |
+
locale_string = default_locale(category, aliases=aliases)
|
| 241 |
+
return cls.parse(locale_string)
|
| 242 |
+
|
| 243 |
+
@classmethod
|
| 244 |
+
def negotiate(
|
| 245 |
+
cls,
|
| 246 |
+
preferred: Iterable[str],
|
| 247 |
+
available: Iterable[str],
|
| 248 |
+
sep: str = '_',
|
| 249 |
+
aliases: Mapping[str, str] = LOCALE_ALIASES,
|
| 250 |
+
) -> Locale | None:
|
| 251 |
+
"""Find the best match between available and requested locale strings.
|
| 252 |
+
|
| 253 |
+
>>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
|
| 254 |
+
Locale('de', territory='DE')
|
| 255 |
+
>>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
|
| 256 |
+
Locale('de')
|
| 257 |
+
>>> Locale.negotiate(['de_DE', 'de'], ['en_US'])
|
| 258 |
+
|
| 259 |
+
You can specify the character used in the locale identifiers to separate
|
| 260 |
+
the different components. This separator is applied to both lists. Also,
|
| 261 |
+
case is ignored in the comparison:
|
| 262 |
+
|
| 263 |
+
>>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
|
| 264 |
+
Locale('de', territory='DE')
|
| 265 |
+
|
| 266 |
+
:param preferred: the list of locale identifiers preferred by the user
|
| 267 |
+
:param available: the list of locale identifiers available
|
| 268 |
+
:param aliases: a dictionary of aliases for locale identifiers
|
| 269 |
+
:param sep: separator for parsing; e.g. Windows tends to use '-' instead of '_'.
|
| 270 |
+
"""
|
| 271 |
+
identifier = negotiate_locale(preferred, available, sep=sep,
|
| 272 |
+
aliases=aliases)
|
| 273 |
+
if identifier:
|
| 274 |
+
return Locale.parse(identifier, sep=sep)
|
| 275 |
+
return None
|
| 276 |
+
|
| 277 |
+
@classmethod
|
| 278 |
+
def parse(
|
| 279 |
+
cls,
|
| 280 |
+
identifier: Locale | str | None,
|
| 281 |
+
sep: str = '_',
|
| 282 |
+
resolve_likely_subtags: bool = True,
|
| 283 |
+
) -> Locale:
|
| 284 |
+
"""Create a `Locale` instance for the given locale identifier.
|
| 285 |
+
|
| 286 |
+
>>> l = Locale.parse('de-DE', sep='-')
|
| 287 |
+
>>> l.display_name
|
| 288 |
+
u'Deutsch (Deutschland)'
|
| 289 |
+
|
| 290 |
+
If the `identifier` parameter is not a string, but actually a `Locale`
|
| 291 |
+
object, that object is returned:
|
| 292 |
+
|
| 293 |
+
>>> Locale.parse(l)
|
| 294 |
+
Locale('de', territory='DE')
|
| 295 |
+
|
| 296 |
+
If the `identifier` parameter is neither of these, such as `None`
|
| 297 |
+
or an empty string, e.g. because a default locale identifier
|
| 298 |
+
could not be determined, a `TypeError` is raised:
|
| 299 |
+
|
| 300 |
+
>>> Locale.parse(None)
|
| 301 |
+
Traceback (most recent call last):
|
| 302 |
+
...
|
| 303 |
+
TypeError: ...
|
| 304 |
+
|
| 305 |
+
This also can perform resolving of likely subtags which it does
|
| 306 |
+
by default. This is for instance useful to figure out the most
|
| 307 |
+
likely locale for a territory you can use ``'und'`` as the
|
| 308 |
+
language tag:
|
| 309 |
+
|
| 310 |
+
>>> Locale.parse('und_AT')
|
| 311 |
+
Locale('de', territory='AT')
|
| 312 |
+
|
| 313 |
+
Modifiers are optional, and always at the end, separated by "@":
|
| 314 |
+
|
| 315 |
+
>>> Locale.parse('de_AT@euro')
|
| 316 |
+
Locale('de', territory='AT', modifier='euro')
|
| 317 |
+
|
| 318 |
+
:param identifier: the locale identifier string
|
| 319 |
+
:param sep: optional component separator
|
| 320 |
+
:param resolve_likely_subtags: if this is specified then a locale will
|
| 321 |
+
have its likely subtag resolved if the
|
| 322 |
+
locale otherwise does not exist. For
|
| 323 |
+
instance ``zh_TW`` by itself is not a
|
| 324 |
+
locale that exists but Babel can
|
| 325 |
+
automatically expand it to the full
|
| 326 |
+
form of ``zh_hant_TW``. Note that this
|
| 327 |
+
expansion is only taking place if no
|
| 328 |
+
locale exists otherwise. For instance
|
| 329 |
+
there is a locale ``en`` that can exist
|
| 330 |
+
by itself.
|
| 331 |
+
:raise `ValueError`: if the string does not appear to be a valid locale
|
| 332 |
+
identifier
|
| 333 |
+
:raise `UnknownLocaleError`: if no locale data is available for the
|
| 334 |
+
requested locale
|
| 335 |
+
:raise `TypeError`: if the identifier is not a string or a `Locale`
|
| 336 |
+
:raise `ValueError`: if the identifier is not a valid string
|
| 337 |
+
"""
|
| 338 |
+
if isinstance(identifier, Locale):
|
| 339 |
+
return identifier
|
| 340 |
+
|
| 341 |
+
if not identifier:
|
| 342 |
+
msg = (
|
| 343 |
+
f"Empty locale identifier value: {identifier!r}\n\n"
|
| 344 |
+
f"If you didn't explicitly pass an empty value to a Babel function, "
|
| 345 |
+
f"this could be caused by there being no suitable locale environment "
|
| 346 |
+
f"variables for the API you tried to use.",
|
| 347 |
+
)
|
| 348 |
+
if isinstance(identifier, str):
|
| 349 |
+
raise ValueError(msg) # `parse_locale` would raise a ValueError, so let's do that here
|
| 350 |
+
raise TypeError(msg)
|
| 351 |
+
|
| 352 |
+
if not isinstance(identifier, str):
|
| 353 |
+
raise TypeError(f"Unexpected value for identifier: {identifier!r}")
|
| 354 |
+
|
| 355 |
+
parts = parse_locale(identifier, sep=sep)
|
| 356 |
+
input_id = get_locale_identifier(parts)
|
| 357 |
+
|
| 358 |
+
def _try_load(parts):
|
| 359 |
+
try:
|
| 360 |
+
return cls(*parts)
|
| 361 |
+
except UnknownLocaleError:
|
| 362 |
+
return None
|
| 363 |
+
|
| 364 |
+
def _try_load_reducing(parts):
|
| 365 |
+
# Success on first hit, return it.
|
| 366 |
+
locale = _try_load(parts)
|
| 367 |
+
if locale is not None:
|
| 368 |
+
return locale
|
| 369 |
+
|
| 370 |
+
# Now try without script and variant
|
| 371 |
+
locale = _try_load(parts[:2])
|
| 372 |
+
if locale is not None:
|
| 373 |
+
return locale
|
| 374 |
+
|
| 375 |
+
locale = _try_load(parts)
|
| 376 |
+
if locale is not None:
|
| 377 |
+
return locale
|
| 378 |
+
if not resolve_likely_subtags:
|
| 379 |
+
raise UnknownLocaleError(input_id)
|
| 380 |
+
|
| 381 |
+
# From here onwards is some very bad likely subtag resolving. This
|
| 382 |
+
# whole logic is not entirely correct but good enough (tm) for the
|
| 383 |
+
# time being. This has been added so that zh_TW does not cause
|
| 384 |
+
# errors for people when they upgrade. Later we should properly
|
| 385 |
+
# implement ICU like fuzzy locale objects and provide a way to
|
| 386 |
+
# maximize and minimize locale tags.
|
| 387 |
+
|
| 388 |
+
if len(parts) == 5:
|
| 389 |
+
language, territory, script, variant, modifier = parts
|
| 390 |
+
else:
|
| 391 |
+
language, territory, script, variant = parts
|
| 392 |
+
modifier = None
|
| 393 |
+
language = get_global('language_aliases').get(language, language)
|
| 394 |
+
territory = get_global('territory_aliases').get(territory or '', (territory,))[0]
|
| 395 |
+
script = get_global('script_aliases').get(script or '', script)
|
| 396 |
+
variant = get_global('variant_aliases').get(variant or '', variant)
|
| 397 |
+
|
| 398 |
+
if territory == 'ZZ':
|
| 399 |
+
territory = None
|
| 400 |
+
if script == 'Zzzz':
|
| 401 |
+
script = None
|
| 402 |
+
|
| 403 |
+
parts = language, territory, script, variant, modifier
|
| 404 |
+
|
| 405 |
+
# First match: try the whole identifier
|
| 406 |
+
new_id = get_locale_identifier(parts)
|
| 407 |
+
likely_subtag = get_global('likely_subtags').get(new_id)
|
| 408 |
+
if likely_subtag is not None:
|
| 409 |
+
locale = _try_load_reducing(parse_locale(likely_subtag))
|
| 410 |
+
if locale is not None:
|
| 411 |
+
return locale
|
| 412 |
+
|
| 413 |
+
# If we did not find anything so far, try again with a
|
| 414 |
+
# simplified identifier that is just the language
|
| 415 |
+
likely_subtag = get_global('likely_subtags').get(language)
|
| 416 |
+
if likely_subtag is not None:
|
| 417 |
+
parts2 = parse_locale(likely_subtag)
|
| 418 |
+
if len(parts2) == 5:
|
| 419 |
+
language2, _, script2, variant2, modifier2 = parts2
|
| 420 |
+
else:
|
| 421 |
+
language2, _, script2, variant2 = parts2
|
| 422 |
+
modifier2 = None
|
| 423 |
+
locale = _try_load_reducing((language2, territory, script2, variant2, modifier2))
|
| 424 |
+
if locale is not None:
|
| 425 |
+
return locale
|
| 426 |
+
|
| 427 |
+
raise UnknownLocaleError(input_id)
|
| 428 |
+
|
| 429 |
+
def __eq__(self, other: object) -> bool:
|
| 430 |
+
for key in ('language', 'territory', 'script', 'variant', 'modifier'):
|
| 431 |
+
if not hasattr(other, key):
|
| 432 |
+
return False
|
| 433 |
+
return (
|
| 434 |
+
self.language == getattr(other, 'language') and # noqa: B009
|
| 435 |
+
self.territory == getattr(other, 'territory') and # noqa: B009
|
| 436 |
+
self.script == getattr(other, 'script') and # noqa: B009
|
| 437 |
+
self.variant == getattr(other, 'variant') and # noqa: B009
|
| 438 |
+
self.modifier == getattr(other, 'modifier') # noqa: B009
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
def __ne__(self, other: object) -> bool:
|
| 442 |
+
return not self.__eq__(other)
|
| 443 |
+
|
| 444 |
+
def __hash__(self) -> int:
|
| 445 |
+
return hash((self.language, self.territory, self.script,
|
| 446 |
+
self.variant, self.modifier))
|
| 447 |
+
|
| 448 |
+
def __repr__(self) -> str:
|
| 449 |
+
parameters = ['']
|
| 450 |
+
for key in ('territory', 'script', 'variant', 'modifier'):
|
| 451 |
+
value = getattr(self, key)
|
| 452 |
+
if value is not None:
|
| 453 |
+
parameters.append(f"{key}={value!r}")
|
| 454 |
+
return f"Locale({self.language!r}{', '.join(parameters)})"
|
| 455 |
+
|
| 456 |
+
def __str__(self) -> str:
|
| 457 |
+
return get_locale_identifier((self.language, self.territory,
|
| 458 |
+
self.script, self.variant,
|
| 459 |
+
self.modifier))
|
| 460 |
+
|
| 461 |
+
@property
|
| 462 |
+
def _data(self) -> localedata.LocaleDataDict:
|
| 463 |
+
if self.__data is None:
|
| 464 |
+
self.__data = localedata.LocaleDataDict(localedata.load(self.__data_identifier))
|
| 465 |
+
return self.__data
|
| 466 |
+
|
| 467 |
+
def get_display_name(self, locale: Locale | str | None = None) -> str | None:
|
| 468 |
+
"""Return the display name of the locale using the given locale.
|
| 469 |
+
|
| 470 |
+
The display name will include the language, territory, script, and
|
| 471 |
+
variant, if those are specified.
|
| 472 |
+
|
| 473 |
+
>>> Locale('zh', 'CN', script='Hans').get_display_name('en')
|
| 474 |
+
u'Chinese (Simplified, China)'
|
| 475 |
+
|
| 476 |
+
Modifiers are currently passed through verbatim:
|
| 477 |
+
|
| 478 |
+
>>> Locale('it', 'IT', modifier='euro').get_display_name('en')
|
| 479 |
+
u'Italian (Italy, euro)'
|
| 480 |
+
|
| 481 |
+
:param locale: the locale to use
|
| 482 |
+
"""
|
| 483 |
+
if locale is None:
|
| 484 |
+
locale = self
|
| 485 |
+
locale = Locale.parse(locale)
|
| 486 |
+
retval = locale.languages.get(self.language)
|
| 487 |
+
if retval and (self.territory or self.script or self.variant):
|
| 488 |
+
details = []
|
| 489 |
+
if self.script:
|
| 490 |
+
details.append(locale.scripts.get(self.script))
|
| 491 |
+
if self.territory:
|
| 492 |
+
details.append(locale.territories.get(self.territory))
|
| 493 |
+
if self.variant:
|
| 494 |
+
details.append(locale.variants.get(self.variant))
|
| 495 |
+
if self.modifier:
|
| 496 |
+
details.append(self.modifier)
|
| 497 |
+
detail_string = ', '.join(atom for atom in details if atom)
|
| 498 |
+
if detail_string:
|
| 499 |
+
retval += f" ({detail_string})"
|
| 500 |
+
return retval
|
| 501 |
+
|
| 502 |
+
display_name = property(get_display_name, doc="""\
|
| 503 |
+
The localized display name of the locale.
|
| 504 |
+
|
| 505 |
+
>>> Locale('en').display_name
|
| 506 |
+
u'English'
|
| 507 |
+
>>> Locale('en', 'US').display_name
|
| 508 |
+
u'English (United States)'
|
| 509 |
+
>>> Locale('sv').display_name
|
| 510 |
+
u'svenska'
|
| 511 |
+
|
| 512 |
+
:type: `unicode`
|
| 513 |
+
""")
|
| 514 |
+
|
| 515 |
+
def get_language_name(self, locale: Locale | str | None = None) -> str | None:
|
| 516 |
+
"""Return the language of this locale in the given locale.
|
| 517 |
+
|
| 518 |
+
>>> Locale('zh', 'CN', script='Hans').get_language_name('de')
|
| 519 |
+
u'Chinesisch'
|
| 520 |
+
|
| 521 |
+
.. versionadded:: 1.0
|
| 522 |
+
|
| 523 |
+
:param locale: the locale to use
|
| 524 |
+
"""
|
| 525 |
+
if locale is None:
|
| 526 |
+
locale = self
|
| 527 |
+
locale = Locale.parse(locale)
|
| 528 |
+
return locale.languages.get(self.language)
|
| 529 |
+
|
| 530 |
+
language_name = property(get_language_name, doc="""\
|
| 531 |
+
The localized language name of the locale.
|
| 532 |
+
|
| 533 |
+
>>> Locale('en', 'US').language_name
|
| 534 |
+
u'English'
|
| 535 |
+
""")
|
| 536 |
+
|
| 537 |
+
def get_territory_name(self, locale: Locale | str | None = None) -> str | None:
|
| 538 |
+
"""Return the territory name in the given locale."""
|
| 539 |
+
if locale is None:
|
| 540 |
+
locale = self
|
| 541 |
+
locale = Locale.parse(locale)
|
| 542 |
+
return locale.territories.get(self.territory or '')
|
| 543 |
+
|
| 544 |
+
territory_name = property(get_territory_name, doc="""\
|
| 545 |
+
The localized territory name of the locale if available.
|
| 546 |
+
|
| 547 |
+
>>> Locale('de', 'DE').territory_name
|
| 548 |
+
u'Deutschland'
|
| 549 |
+
""")
|
| 550 |
+
|
| 551 |
+
def get_script_name(self, locale: Locale | str | None = None) -> str | None:
|
| 552 |
+
"""Return the script name in the given locale."""
|
| 553 |
+
if locale is None:
|
| 554 |
+
locale = self
|
| 555 |
+
locale = Locale.parse(locale)
|
| 556 |
+
return locale.scripts.get(self.script or '')
|
| 557 |
+
|
| 558 |
+
script_name = property(get_script_name, doc="""\
|
| 559 |
+
The localized script name of the locale if available.
|
| 560 |
+
|
| 561 |
+
>>> Locale('sr', 'ME', script='Latn').script_name
|
| 562 |
+
u'latinica'
|
| 563 |
+
""")
|
| 564 |
+
|
| 565 |
+
@property
|
| 566 |
+
def english_name(self) -> str | None:
|
| 567 |
+
"""The english display name of the locale.
|
| 568 |
+
|
| 569 |
+
>>> Locale('de').english_name
|
| 570 |
+
u'German'
|
| 571 |
+
>>> Locale('de', 'DE').english_name
|
| 572 |
+
u'German (Germany)'
|
| 573 |
+
|
| 574 |
+
:type: `unicode`"""
|
| 575 |
+
return self.get_display_name(Locale('en'))
|
| 576 |
+
|
| 577 |
+
# { General Locale Display Names
|
| 578 |
+
|
| 579 |
+
@property
|
| 580 |
+
def languages(self) -> localedata.LocaleDataDict:
|
| 581 |
+
"""Mapping of language codes to translated language names.
|
| 582 |
+
|
| 583 |
+
>>> Locale('de', 'DE').languages['ja']
|
| 584 |
+
u'Japanisch'
|
| 585 |
+
|
| 586 |
+
See `ISO 639 <https://www.loc.gov/standards/iso639-2/>`_ for
|
| 587 |
+
more information.
|
| 588 |
+
"""
|
| 589 |
+
return self._data['languages']
|
| 590 |
+
|
| 591 |
+
@property
|
| 592 |
+
def scripts(self) -> localedata.LocaleDataDict:
|
| 593 |
+
"""Mapping of script codes to translated script names.
|
| 594 |
+
|
| 595 |
+
>>> Locale('en', 'US').scripts['Hira']
|
| 596 |
+
u'Hiragana'
|
| 597 |
+
|
| 598 |
+
See `ISO 15924 <https://www.unicode.org/iso15924/>`_
|
| 599 |
+
for more information.
|
| 600 |
+
"""
|
| 601 |
+
return self._data['scripts']
|
| 602 |
+
|
| 603 |
+
@property
|
| 604 |
+
def territories(self) -> localedata.LocaleDataDict:
|
| 605 |
+
"""Mapping of script codes to translated script names.
|
| 606 |
+
|
| 607 |
+
>>> Locale('es', 'CO').territories['DE']
|
| 608 |
+
u'Alemania'
|
| 609 |
+
|
| 610 |
+
See `ISO 3166 <https://en.wikipedia.org/wiki/ISO_3166>`_
|
| 611 |
+
for more information.
|
| 612 |
+
"""
|
| 613 |
+
return self._data['territories']
|
| 614 |
+
|
| 615 |
+
@property
|
| 616 |
+
def variants(self) -> localedata.LocaleDataDict:
|
| 617 |
+
"""Mapping of script codes to translated script names.
|
| 618 |
+
|
| 619 |
+
>>> Locale('de', 'DE').variants['1901']
|
| 620 |
+
u'Alte deutsche Rechtschreibung'
|
| 621 |
+
"""
|
| 622 |
+
return self._data['variants']
|
| 623 |
+
|
| 624 |
+
# { Number Formatting
|
| 625 |
+
|
| 626 |
+
@property
|
| 627 |
+
def currencies(self) -> localedata.LocaleDataDict:
|
| 628 |
+
"""Mapping of currency codes to translated currency names. This
|
| 629 |
+
only returns the generic form of the currency name, not the count
|
| 630 |
+
specific one. If an actual number is requested use the
|
| 631 |
+
:func:`babel.numbers.get_currency_name` function.
|
| 632 |
+
|
| 633 |
+
>>> Locale('en').currencies['COP']
|
| 634 |
+
u'Colombian Peso'
|
| 635 |
+
>>> Locale('de', 'DE').currencies['COP']
|
| 636 |
+
u'Kolumbianischer Peso'
|
| 637 |
+
"""
|
| 638 |
+
return self._data['currency_names']
|
| 639 |
+
|
| 640 |
+
@property
|
| 641 |
+
def currency_symbols(self) -> localedata.LocaleDataDict:
|
| 642 |
+
"""Mapping of currency codes to symbols.
|
| 643 |
+
|
| 644 |
+
>>> Locale('en', 'US').currency_symbols['USD']
|
| 645 |
+
u'$'
|
| 646 |
+
>>> Locale('es', 'CO').currency_symbols['USD']
|
| 647 |
+
u'US$'
|
| 648 |
+
"""
|
| 649 |
+
return self._data['currency_symbols']
|
| 650 |
+
|
| 651 |
+
@property
|
| 652 |
+
def number_symbols(self) -> localedata.LocaleDataDict:
|
| 653 |
+
"""Symbols used in number formatting by number system.
|
| 654 |
+
|
| 655 |
+
.. note:: The format of the value returned may change between
|
| 656 |
+
Babel versions.
|
| 657 |
+
|
| 658 |
+
>>> Locale('fr', 'FR').number_symbols["latn"]['decimal']
|
| 659 |
+
u','
|
| 660 |
+
>>> Locale('fa', 'IR').number_symbols["arabext"]['decimal']
|
| 661 |
+
u'٫'
|
| 662 |
+
>>> Locale('fa', 'IR').number_symbols["latn"]['decimal']
|
| 663 |
+
u'.'
|
| 664 |
+
"""
|
| 665 |
+
return self._data['number_symbols']
|
| 666 |
+
|
| 667 |
+
@property
|
| 668 |
+
def other_numbering_systems(self) -> localedata.LocaleDataDict:
|
| 669 |
+
"""
|
| 670 |
+
Mapping of other numbering systems available for the locale.
|
| 671 |
+
See: https://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
|
| 672 |
+
|
| 673 |
+
>>> Locale('el', 'GR').other_numbering_systems['traditional']
|
| 674 |
+
u'grek'
|
| 675 |
+
|
| 676 |
+
.. note:: The format of the value returned may change between
|
| 677 |
+
Babel versions.
|
| 678 |
+
"""
|
| 679 |
+
return self._data['numbering_systems']
|
| 680 |
+
|
| 681 |
+
@property
|
| 682 |
+
def default_numbering_system(self) -> str:
|
| 683 |
+
"""The default numbering system used by the locale.
|
| 684 |
+
>>> Locale('el', 'GR').default_numbering_system
|
| 685 |
+
u'latn'
|
| 686 |
+
"""
|
| 687 |
+
return self._data['default_numbering_system']
|
| 688 |
+
|
| 689 |
+
@property
|
| 690 |
+
def decimal_formats(self) -> localedata.LocaleDataDict:
|
| 691 |
+
"""Locale patterns for decimal number formatting.
|
| 692 |
+
|
| 693 |
+
.. note:: The format of the value returned may change between
|
| 694 |
+
Babel versions.
|
| 695 |
+
|
| 696 |
+
>>> Locale('en', 'US').decimal_formats[None]
|
| 697 |
+
<NumberPattern u'#,##0.###'>
|
| 698 |
+
"""
|
| 699 |
+
return self._data['decimal_formats']
|
| 700 |
+
|
| 701 |
+
@property
|
| 702 |
+
def compact_decimal_formats(self) -> localedata.LocaleDataDict:
|
| 703 |
+
"""Locale patterns for compact decimal number formatting.
|
| 704 |
+
|
| 705 |
+
.. note:: The format of the value returned may change between
|
| 706 |
+
Babel versions.
|
| 707 |
+
|
| 708 |
+
>>> Locale('en', 'US').compact_decimal_formats["short"]["one"]["1000"]
|
| 709 |
+
<NumberPattern u'0K'>
|
| 710 |
+
"""
|
| 711 |
+
return self._data['compact_decimal_formats']
|
| 712 |
+
|
| 713 |
+
@property
|
| 714 |
+
def currency_formats(self) -> localedata.LocaleDataDict:
|
| 715 |
+
"""Locale patterns for currency number formatting.
|
| 716 |
+
|
| 717 |
+
.. note:: The format of the value returned may change between
|
| 718 |
+
Babel versions.
|
| 719 |
+
|
| 720 |
+
>>> Locale('en', 'US').currency_formats['standard']
|
| 721 |
+
<NumberPattern u'\\xa4#,##0.00'>
|
| 722 |
+
>>> Locale('en', 'US').currency_formats['accounting']
|
| 723 |
+
<NumberPattern u'\\xa4#,##0.00;(\\xa4#,##0.00)'>
|
| 724 |
+
"""
|
| 725 |
+
return self._data['currency_formats']
|
| 726 |
+
|
| 727 |
+
@property
|
| 728 |
+
def compact_currency_formats(self) -> localedata.LocaleDataDict:
|
| 729 |
+
"""Locale patterns for compact currency number formatting.
|
| 730 |
+
|
| 731 |
+
.. note:: The format of the value returned may change between
|
| 732 |
+
Babel versions.
|
| 733 |
+
|
| 734 |
+
>>> Locale('en', 'US').compact_currency_formats["short"]["one"]["1000"]
|
| 735 |
+
<NumberPattern u'¤0K'>
|
| 736 |
+
"""
|
| 737 |
+
return self._data['compact_currency_formats']
|
| 738 |
+
|
| 739 |
+
@property
|
| 740 |
+
def percent_formats(self) -> localedata.LocaleDataDict:
|
| 741 |
+
"""Locale patterns for percent number formatting.
|
| 742 |
+
|
| 743 |
+
.. note:: The format of the value returned may change between
|
| 744 |
+
Babel versions.
|
| 745 |
+
|
| 746 |
+
>>> Locale('en', 'US').percent_formats[None]
|
| 747 |
+
<NumberPattern u'#,##0%'>
|
| 748 |
+
"""
|
| 749 |
+
return self._data['percent_formats']
|
| 750 |
+
|
| 751 |
+
@property
|
| 752 |
+
def scientific_formats(self) -> localedata.LocaleDataDict:
|
| 753 |
+
"""Locale patterns for scientific number formatting.
|
| 754 |
+
|
| 755 |
+
.. note:: The format of the value returned may change between
|
| 756 |
+
Babel versions.
|
| 757 |
+
|
| 758 |
+
>>> Locale('en', 'US').scientific_formats[None]
|
| 759 |
+
<NumberPattern u'#E0'>
|
| 760 |
+
"""
|
| 761 |
+
return self._data['scientific_formats']
|
| 762 |
+
|
| 763 |
+
# { Calendar Information and Date Formatting
|
| 764 |
+
|
| 765 |
+
@property
|
| 766 |
+
def periods(self) -> localedata.LocaleDataDict:
|
| 767 |
+
"""Locale display names for day periods (AM/PM).
|
| 768 |
+
|
| 769 |
+
>>> Locale('en', 'US').periods['am']
|
| 770 |
+
u'AM'
|
| 771 |
+
"""
|
| 772 |
+
try:
|
| 773 |
+
return self._data['day_periods']['stand-alone']['wide']
|
| 774 |
+
except KeyError:
|
| 775 |
+
return localedata.LocaleDataDict({}) # pragma: no cover
|
| 776 |
+
|
| 777 |
+
@property
|
| 778 |
+
def day_periods(self) -> localedata.LocaleDataDict:
|
| 779 |
+
"""Locale display names for various day periods (not necessarily only AM/PM).
|
| 780 |
+
|
| 781 |
+
These are not meant to be used without the relevant `day_period_rules`.
|
| 782 |
+
"""
|
| 783 |
+
return self._data['day_periods']
|
| 784 |
+
|
| 785 |
+
@property
|
| 786 |
+
def day_period_rules(self) -> localedata.LocaleDataDict:
|
| 787 |
+
"""Day period rules for the locale. Used by `get_period_id`.
|
| 788 |
+
"""
|
| 789 |
+
return self._data.get('day_period_rules', localedata.LocaleDataDict({}))
|
| 790 |
+
|
| 791 |
+
@property
|
| 792 |
+
def days(self) -> localedata.LocaleDataDict:
|
| 793 |
+
"""Locale display names for weekdays.
|
| 794 |
+
|
| 795 |
+
>>> Locale('de', 'DE').days['format']['wide'][3]
|
| 796 |
+
u'Donnerstag'
|
| 797 |
+
"""
|
| 798 |
+
return self._data['days']
|
| 799 |
+
|
| 800 |
+
@property
|
| 801 |
+
def months(self) -> localedata.LocaleDataDict:
|
| 802 |
+
"""Locale display names for months.
|
| 803 |
+
|
| 804 |
+
>>> Locale('de', 'DE').months['format']['wide'][10]
|
| 805 |
+
u'Oktober'
|
| 806 |
+
"""
|
| 807 |
+
return self._data['months']
|
| 808 |
+
|
| 809 |
+
@property
|
| 810 |
+
def quarters(self) -> localedata.LocaleDataDict:
|
| 811 |
+
"""Locale display names for quarters.
|
| 812 |
+
|
| 813 |
+
>>> Locale('de', 'DE').quarters['format']['wide'][1]
|
| 814 |
+
u'1. Quartal'
|
| 815 |
+
"""
|
| 816 |
+
return self._data['quarters']
|
| 817 |
+
|
| 818 |
+
@property
|
| 819 |
+
def eras(self) -> localedata.LocaleDataDict:
|
| 820 |
+
"""Locale display names for eras.
|
| 821 |
+
|
| 822 |
+
.. note:: The format of the value returned may change between
|
| 823 |
+
Babel versions.
|
| 824 |
+
|
| 825 |
+
>>> Locale('en', 'US').eras['wide'][1]
|
| 826 |
+
u'Anno Domini'
|
| 827 |
+
>>> Locale('en', 'US').eras['abbreviated'][0]
|
| 828 |
+
u'BC'
|
| 829 |
+
"""
|
| 830 |
+
return self._data['eras']
|
| 831 |
+
|
| 832 |
+
@property
|
| 833 |
+
def time_zones(self) -> localedata.LocaleDataDict:
|
| 834 |
+
"""Locale display names for time zones.
|
| 835 |
+
|
| 836 |
+
.. note:: The format of the value returned may change between
|
| 837 |
+
Babel versions.
|
| 838 |
+
|
| 839 |
+
>>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
|
| 840 |
+
u'British Summer Time'
|
| 841 |
+
>>> Locale('en', 'US').time_zones['America/St_Johns']['city']
|
| 842 |
+
u'St. John\u2019s'
|
| 843 |
+
"""
|
| 844 |
+
return self._data['time_zones']
|
| 845 |
+
|
| 846 |
+
@property
|
| 847 |
+
def meta_zones(self) -> localedata.LocaleDataDict:
|
| 848 |
+
"""Locale display names for meta time zones.
|
| 849 |
+
|
| 850 |
+
Meta time zones are basically groups of different Olson time zones that
|
| 851 |
+
have the same GMT offset and daylight savings time.
|
| 852 |
+
|
| 853 |
+
.. note:: The format of the value returned may change between
|
| 854 |
+
Babel versions.
|
| 855 |
+
|
| 856 |
+
>>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
|
| 857 |
+
u'Central European Summer Time'
|
| 858 |
+
|
| 859 |
+
.. versionadded:: 0.9
|
| 860 |
+
"""
|
| 861 |
+
return self._data['meta_zones']
|
| 862 |
+
|
| 863 |
+
@property
|
| 864 |
+
def zone_formats(self) -> localedata.LocaleDataDict:
|
| 865 |
+
"""Patterns related to the formatting of time zones.
|
| 866 |
+
|
| 867 |
+
.. note:: The format of the value returned may change between
|
| 868 |
+
Babel versions.
|
| 869 |
+
|
| 870 |
+
>>> Locale('en', 'US').zone_formats['fallback']
|
| 871 |
+
u'%(1)s (%(0)s)'
|
| 872 |
+
>>> Locale('pt', 'BR').zone_formats['region']
|
| 873 |
+
u'Hor\\xe1rio %s'
|
| 874 |
+
|
| 875 |
+
.. versionadded:: 0.9
|
| 876 |
+
"""
|
| 877 |
+
return self._data['zone_formats']
|
| 878 |
+
|
| 879 |
+
@property
|
| 880 |
+
def first_week_day(self) -> int:
|
| 881 |
+
"""The first day of a week, with 0 being Monday.
|
| 882 |
+
|
| 883 |
+
>>> Locale('de', 'DE').first_week_day
|
| 884 |
+
0
|
| 885 |
+
>>> Locale('en', 'US').first_week_day
|
| 886 |
+
6
|
| 887 |
+
"""
|
| 888 |
+
return self._data['week_data']['first_day']
|
| 889 |
+
|
| 890 |
+
@property
|
| 891 |
+
def weekend_start(self) -> int:
|
| 892 |
+
"""The day the weekend starts, with 0 being Monday.
|
| 893 |
+
|
| 894 |
+
>>> Locale('de', 'DE').weekend_start
|
| 895 |
+
5
|
| 896 |
+
"""
|
| 897 |
+
return self._data['week_data']['weekend_start']
|
| 898 |
+
|
| 899 |
+
@property
|
| 900 |
+
def weekend_end(self) -> int:
|
| 901 |
+
"""The day the weekend ends, with 0 being Monday.
|
| 902 |
+
|
| 903 |
+
>>> Locale('de', 'DE').weekend_end
|
| 904 |
+
6
|
| 905 |
+
"""
|
| 906 |
+
return self._data['week_data']['weekend_end']
|
| 907 |
+
|
| 908 |
+
@property
|
| 909 |
+
def min_week_days(self) -> int:
|
| 910 |
+
"""The minimum number of days in a week so that the week is counted as
|
| 911 |
+
the first week of a year or month.
|
| 912 |
+
|
| 913 |
+
>>> Locale('de', 'DE').min_week_days
|
| 914 |
+
4
|
| 915 |
+
"""
|
| 916 |
+
return self._data['week_data']['min_days']
|
| 917 |
+
|
| 918 |
+
@property
|
| 919 |
+
def date_formats(self) -> localedata.LocaleDataDict:
|
| 920 |
+
"""Locale patterns for date formatting.
|
| 921 |
+
|
| 922 |
+
.. note:: The format of the value returned may change between
|
| 923 |
+
Babel versions.
|
| 924 |
+
|
| 925 |
+
>>> Locale('en', 'US').date_formats['short']
|
| 926 |
+
<DateTimePattern u'M/d/yy'>
|
| 927 |
+
>>> Locale('fr', 'FR').date_formats['long']
|
| 928 |
+
<DateTimePattern u'd MMMM y'>
|
| 929 |
+
"""
|
| 930 |
+
return self._data['date_formats']
|
| 931 |
+
|
| 932 |
+
@property
|
| 933 |
+
def time_formats(self) -> localedata.LocaleDataDict:
|
| 934 |
+
"""Locale patterns for time formatting.
|
| 935 |
+
|
| 936 |
+
.. note:: The format of the value returned may change between
|
| 937 |
+
Babel versions.
|
| 938 |
+
|
| 939 |
+
>>> Locale('en', 'US').time_formats['short']
|
| 940 |
+
<DateTimePattern u'h:mm\u202fa'>
|
| 941 |
+
>>> Locale('fr', 'FR').time_formats['long']
|
| 942 |
+
<DateTimePattern u'HH:mm:ss z'>
|
| 943 |
+
"""
|
| 944 |
+
return self._data['time_formats']
|
| 945 |
+
|
| 946 |
+
@property
|
| 947 |
+
def datetime_formats(self) -> localedata.LocaleDataDict:
|
| 948 |
+
"""Locale patterns for datetime formatting.
|
| 949 |
+
|
| 950 |
+
.. note:: The format of the value returned may change between
|
| 951 |
+
Babel versions.
|
| 952 |
+
|
| 953 |
+
>>> Locale('en').datetime_formats['full']
|
| 954 |
+
u'{1}, {0}'
|
| 955 |
+
>>> Locale('th').datetime_formats['medium']
|
| 956 |
+
u'{1} {0}'
|
| 957 |
+
"""
|
| 958 |
+
return self._data['datetime_formats']
|
| 959 |
+
|
| 960 |
+
@property
|
| 961 |
+
def datetime_skeletons(self) -> localedata.LocaleDataDict:
|
| 962 |
+
"""Locale patterns for formatting parts of a datetime.
|
| 963 |
+
|
| 964 |
+
>>> Locale('en').datetime_skeletons['MEd']
|
| 965 |
+
<DateTimePattern u'E, M/d'>
|
| 966 |
+
>>> Locale('fr').datetime_skeletons['MEd']
|
| 967 |
+
<DateTimePattern u'E dd/MM'>
|
| 968 |
+
>>> Locale('fr').datetime_skeletons['H']
|
| 969 |
+
<DateTimePattern u"HH 'h'">
|
| 970 |
+
"""
|
| 971 |
+
return self._data['datetime_skeletons']
|
| 972 |
+
|
| 973 |
+
@property
|
| 974 |
+
def interval_formats(self) -> localedata.LocaleDataDict:
|
| 975 |
+
"""Locale patterns for interval formatting.
|
| 976 |
+
|
| 977 |
+
.. note:: The format of the value returned may change between
|
| 978 |
+
Babel versions.
|
| 979 |
+
|
| 980 |
+
How to format date intervals in Finnish when the day is the
|
| 981 |
+
smallest changing component:
|
| 982 |
+
|
| 983 |
+
>>> Locale('fi_FI').interval_formats['MEd']['d']
|
| 984 |
+
[u'E d.\u2009\u2013\u2009', u'E d.M.']
|
| 985 |
+
|
| 986 |
+
.. seealso::
|
| 987 |
+
|
| 988 |
+
The primary API to use this data is :py:func:`babel.dates.format_interval`.
|
| 989 |
+
|
| 990 |
+
|
| 991 |
+
:rtype: dict[str, dict[str, list[str]]]
|
| 992 |
+
"""
|
| 993 |
+
return self._data['interval_formats']
|
| 994 |
+
|
| 995 |
+
@property
|
| 996 |
+
def plural_form(self) -> PluralRule:
|
| 997 |
+
"""Plural rules for the locale.
|
| 998 |
+
|
| 999 |
+
>>> Locale('en').plural_form(1)
|
| 1000 |
+
'one'
|
| 1001 |
+
>>> Locale('en').plural_form(0)
|
| 1002 |
+
'other'
|
| 1003 |
+
>>> Locale('fr').plural_form(0)
|
| 1004 |
+
'one'
|
| 1005 |
+
>>> Locale('ru').plural_form(100)
|
| 1006 |
+
'many'
|
| 1007 |
+
"""
|
| 1008 |
+
return self._data.get('plural_form', _default_plural_rule)
|
| 1009 |
+
|
| 1010 |
+
@property
|
| 1011 |
+
def list_patterns(self) -> localedata.LocaleDataDict:
|
| 1012 |
+
"""Patterns for generating lists
|
| 1013 |
+
|
| 1014 |
+
.. note:: The format of the value returned may change between
|
| 1015 |
+
Babel versions.
|
| 1016 |
+
|
| 1017 |
+
>>> Locale('en').list_patterns['standard']['start']
|
| 1018 |
+
u'{0}, {1}'
|
| 1019 |
+
>>> Locale('en').list_patterns['standard']['end']
|
| 1020 |
+
u'{0}, and {1}'
|
| 1021 |
+
>>> Locale('en_GB').list_patterns['standard']['end']
|
| 1022 |
+
u'{0} and {1}'
|
| 1023 |
+
"""
|
| 1024 |
+
return self._data['list_patterns']
|
| 1025 |
+
|
| 1026 |
+
@property
|
| 1027 |
+
def ordinal_form(self) -> PluralRule:
|
| 1028 |
+
"""Plural rules for the locale.
|
| 1029 |
+
|
| 1030 |
+
>>> Locale('en').ordinal_form(1)
|
| 1031 |
+
'one'
|
| 1032 |
+
>>> Locale('en').ordinal_form(2)
|
| 1033 |
+
'two'
|
| 1034 |
+
>>> Locale('en').ordinal_form(3)
|
| 1035 |
+
'few'
|
| 1036 |
+
>>> Locale('fr').ordinal_form(2)
|
| 1037 |
+
'other'
|
| 1038 |
+
>>> Locale('ru').ordinal_form(100)
|
| 1039 |
+
'other'
|
| 1040 |
+
"""
|
| 1041 |
+
return self._data.get('ordinal_form', _default_plural_rule)
|
| 1042 |
+
|
| 1043 |
+
@property
|
| 1044 |
+
def measurement_systems(self) -> localedata.LocaleDataDict:
|
| 1045 |
+
"""Localized names for various measurement systems.
|
| 1046 |
+
|
| 1047 |
+
>>> Locale('fr', 'FR').measurement_systems['US']
|
| 1048 |
+
u'am\\xe9ricain'
|
| 1049 |
+
>>> Locale('en', 'US').measurement_systems['US']
|
| 1050 |
+
u'US'
|
| 1051 |
+
|
| 1052 |
+
"""
|
| 1053 |
+
return self._data['measurement_systems']
|
| 1054 |
+
|
| 1055 |
+
@property
|
| 1056 |
+
def character_order(self) -> str:
|
| 1057 |
+
"""The text direction for the language.
|
| 1058 |
+
|
| 1059 |
+
>>> Locale('de', 'DE').character_order
|
| 1060 |
+
'left-to-right'
|
| 1061 |
+
>>> Locale('ar', 'SA').character_order
|
| 1062 |
+
'right-to-left'
|
| 1063 |
+
"""
|
| 1064 |
+
return self._data['character_order']
|
| 1065 |
+
|
| 1066 |
+
@property
|
| 1067 |
+
def text_direction(self) -> str:
|
| 1068 |
+
"""The text direction for the language in CSS short-hand form.
|
| 1069 |
+
|
| 1070 |
+
>>> Locale('de', 'DE').text_direction
|
| 1071 |
+
'ltr'
|
| 1072 |
+
>>> Locale('ar', 'SA').text_direction
|
| 1073 |
+
'rtl'
|
| 1074 |
+
"""
|
| 1075 |
+
return ''.join(word[0] for word in self.character_order.split('-'))
|
| 1076 |
+
|
| 1077 |
+
@property
|
| 1078 |
+
def unit_display_names(self) -> localedata.LocaleDataDict:
|
| 1079 |
+
"""Display names for units of measurement.
|
| 1080 |
+
|
| 1081 |
+
.. seealso::
|
| 1082 |
+
|
| 1083 |
+
You may want to use :py:func:`babel.units.get_unit_name` instead.
|
| 1084 |
+
|
| 1085 |
+
.. note:: The format of the value returned may change between
|
| 1086 |
+
Babel versions.
|
| 1087 |
+
|
| 1088 |
+
"""
|
| 1089 |
+
return self._data['unit_display_names']
|
| 1090 |
+
|
| 1091 |
+
|
| 1092 |
+
def default_locale(
|
| 1093 |
+
category: str | tuple[str, ...] | list[str] | None = None,
|
| 1094 |
+
aliases: Mapping[str, str] = LOCALE_ALIASES,
|
| 1095 |
+
) -> str | None:
|
| 1096 |
+
"""Returns the system default locale for a given category, based on
|
| 1097 |
+
environment variables.
|
| 1098 |
+
|
| 1099 |
+
>>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
|
| 1100 |
+
... os.environ[name] = ''
|
| 1101 |
+
>>> os.environ['LANG'] = 'fr_FR.UTF-8'
|
| 1102 |
+
>>> default_locale('LC_MESSAGES')
|
| 1103 |
+
'fr_FR'
|
| 1104 |
+
|
| 1105 |
+
The "C" or "POSIX" pseudo-locales are treated as aliases for the
|
| 1106 |
+
"en_US_POSIX" locale:
|
| 1107 |
+
|
| 1108 |
+
>>> os.environ['LC_MESSAGES'] = 'POSIX'
|
| 1109 |
+
>>> default_locale('LC_MESSAGES')
|
| 1110 |
+
'en_US_POSIX'
|
| 1111 |
+
|
| 1112 |
+
The following fallbacks to the variable are always considered:
|
| 1113 |
+
|
| 1114 |
+
- ``LANGUAGE``
|
| 1115 |
+
- ``LC_ALL``
|
| 1116 |
+
- ``LC_CTYPE``
|
| 1117 |
+
- ``LANG``
|
| 1118 |
+
|
| 1119 |
+
:param category: one or more of the ``LC_XXX`` environment variable names
|
| 1120 |
+
:param aliases: a dictionary of aliases for locale identifiers
|
| 1121 |
+
"""
|
| 1122 |
+
|
| 1123 |
+
varnames = ('LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
|
| 1124 |
+
if category:
|
| 1125 |
+
if isinstance(category, str):
|
| 1126 |
+
varnames = (category, *varnames)
|
| 1127 |
+
elif isinstance(category, (list, tuple)):
|
| 1128 |
+
varnames = (*category, *varnames)
|
| 1129 |
+
else:
|
| 1130 |
+
raise TypeError(f"Invalid type for category: {category!r}")
|
| 1131 |
+
|
| 1132 |
+
for name in varnames:
|
| 1133 |
+
if not name:
|
| 1134 |
+
continue
|
| 1135 |
+
locale = os.getenv(name)
|
| 1136 |
+
if locale:
|
| 1137 |
+
if name == 'LANGUAGE' and ':' in locale:
|
| 1138 |
+
# the LANGUAGE variable may contain a colon-separated list of
|
| 1139 |
+
# language codes; we just pick the language on the list
|
| 1140 |
+
locale = locale.split(':')[0]
|
| 1141 |
+
if locale.split('.')[0] in ('C', 'POSIX'):
|
| 1142 |
+
locale = 'en_US_POSIX'
|
| 1143 |
+
elif aliases and locale in aliases:
|
| 1144 |
+
locale = aliases[locale]
|
| 1145 |
+
try:
|
| 1146 |
+
return get_locale_identifier(parse_locale(locale))
|
| 1147 |
+
except ValueError:
|
| 1148 |
+
pass
|
| 1149 |
+
return None
|
| 1150 |
+
|
| 1151 |
+
|
| 1152 |
+
def negotiate_locale(preferred: Iterable[str], available: Iterable[str], sep: str = '_', aliases: Mapping[str, str] = LOCALE_ALIASES) -> str | None:
|
| 1153 |
+
"""Find the best match between available and requested locale strings.
|
| 1154 |
+
|
| 1155 |
+
>>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
|
| 1156 |
+
'de_DE'
|
| 1157 |
+
>>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
|
| 1158 |
+
'de'
|
| 1159 |
+
|
| 1160 |
+
Case is ignored by the algorithm, the result uses the case of the preferred
|
| 1161 |
+
locale identifier:
|
| 1162 |
+
|
| 1163 |
+
>>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
|
| 1164 |
+
'de_DE'
|
| 1165 |
+
|
| 1166 |
+
>>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
|
| 1167 |
+
'de_DE'
|
| 1168 |
+
|
| 1169 |
+
By default, some web browsers unfortunately do not include the territory
|
| 1170 |
+
in the locale identifier for many locales, and some don't even allow the
|
| 1171 |
+
user to easily add the territory. So while you may prefer using qualified
|
| 1172 |
+
locale identifiers in your web-application, they would not normally match
|
| 1173 |
+
the language-only locale sent by such browsers. To workaround that, this
|
| 1174 |
+
function uses a default mapping of commonly used language-only locale
|
| 1175 |
+
identifiers to identifiers including the territory:
|
| 1176 |
+
|
| 1177 |
+
>>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
|
| 1178 |
+
'ja_JP'
|
| 1179 |
+
|
| 1180 |
+
Some browsers even use an incorrect or outdated language code, such as "no"
|
| 1181 |
+
for Norwegian, where the correct locale identifier would actually be "nb_NO"
|
| 1182 |
+
(Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
|
| 1183 |
+
such cases, too:
|
| 1184 |
+
|
| 1185 |
+
>>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
|
| 1186 |
+
'nb_NO'
|
| 1187 |
+
|
| 1188 |
+
You can override this default mapping by passing a different `aliases`
|
| 1189 |
+
dictionary to this function, or you can bypass the behavior althogher by
|
| 1190 |
+
setting the `aliases` parameter to `None`.
|
| 1191 |
+
|
| 1192 |
+
:param preferred: the list of locale strings preferred by the user
|
| 1193 |
+
:param available: the list of locale strings available
|
| 1194 |
+
:param sep: character that separates the different parts of the locale
|
| 1195 |
+
strings
|
| 1196 |
+
:param aliases: a dictionary of aliases for locale identifiers
|
| 1197 |
+
"""
|
| 1198 |
+
available = [a.lower() for a in available if a]
|
| 1199 |
+
for locale in preferred:
|
| 1200 |
+
ll = locale.lower()
|
| 1201 |
+
if ll in available:
|
| 1202 |
+
return locale
|
| 1203 |
+
if aliases:
|
| 1204 |
+
alias = aliases.get(ll)
|
| 1205 |
+
if alias:
|
| 1206 |
+
alias = alias.replace('_', sep)
|
| 1207 |
+
if alias.lower() in available:
|
| 1208 |
+
return alias
|
| 1209 |
+
parts = locale.split(sep)
|
| 1210 |
+
if len(parts) > 1 and parts[0].lower() in available:
|
| 1211 |
+
return parts[0]
|
| 1212 |
+
return None
|
| 1213 |
+
|
| 1214 |
+
|
| 1215 |
+
def parse_locale(
|
| 1216 |
+
identifier: str,
|
| 1217 |
+
sep: str = '_',
|
| 1218 |
+
) -> tuple[str, str | None, str | None, str | None] | tuple[str, str | None, str | None, str | None, str | None]:
|
| 1219 |
+
"""Parse a locale identifier into a tuple of the form ``(language,
|
| 1220 |
+
territory, script, variant, modifier)``.
|
| 1221 |
+
|
| 1222 |
+
>>> parse_locale('zh_CN')
|
| 1223 |
+
('zh', 'CN', None, None)
|
| 1224 |
+
>>> parse_locale('zh_Hans_CN')
|
| 1225 |
+
('zh', 'CN', 'Hans', None)
|
| 1226 |
+
>>> parse_locale('ca_es_valencia')
|
| 1227 |
+
('ca', 'ES', None, 'VALENCIA')
|
| 1228 |
+
>>> parse_locale('en_150')
|
| 1229 |
+
('en', '150', None, None)
|
| 1230 |
+
>>> parse_locale('en_us_posix')
|
| 1231 |
+
('en', 'US', None, 'POSIX')
|
| 1232 |
+
>>> parse_locale('it_IT@euro')
|
| 1233 |
+
('it', 'IT', None, None, 'euro')
|
| 1234 |
+
>>> parse_locale('it_IT@custom')
|
| 1235 |
+
('it', 'IT', None, None, 'custom')
|
| 1236 |
+
>>> parse_locale('it_IT@')
|
| 1237 |
+
('it', 'IT', None, None)
|
| 1238 |
+
|
| 1239 |
+
The default component separator is "_", but a different separator can be
|
| 1240 |
+
specified using the `sep` parameter.
|
| 1241 |
+
|
| 1242 |
+
The optional modifier is always separated with "@" and at the end:
|
| 1243 |
+
|
| 1244 |
+
>>> parse_locale('zh-CN', sep='-')
|
| 1245 |
+
('zh', 'CN', None, None)
|
| 1246 |
+
>>> parse_locale('zh-CN@custom', sep='-')
|
| 1247 |
+
('zh', 'CN', None, None, 'custom')
|
| 1248 |
+
|
| 1249 |
+
If the identifier cannot be parsed into a locale, a `ValueError` exception
|
| 1250 |
+
is raised:
|
| 1251 |
+
|
| 1252 |
+
>>> parse_locale('not_a_LOCALE_String')
|
| 1253 |
+
Traceback (most recent call last):
|
| 1254 |
+
...
|
| 1255 |
+
ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
|
| 1256 |
+
|
| 1257 |
+
Encoding information is removed from the identifier, while modifiers are
|
| 1258 |
+
kept:
|
| 1259 |
+
|
| 1260 |
+
>>> parse_locale('en_US.UTF-8')
|
| 1261 |
+
('en', 'US', None, None)
|
| 1262 |
+
>>> parse_locale('de_DE.iso885915@euro')
|
| 1263 |
+
('de', 'DE', None, None, 'euro')
|
| 1264 |
+
|
| 1265 |
+
See :rfc:`4646` for more information.
|
| 1266 |
+
|
| 1267 |
+
:param identifier: the locale identifier string
|
| 1268 |
+
:param sep: character that separates the different components of the locale
|
| 1269 |
+
identifier
|
| 1270 |
+
:raise `ValueError`: if the string does not appear to be a valid locale
|
| 1271 |
+
identifier
|
| 1272 |
+
"""
|
| 1273 |
+
if not identifier:
|
| 1274 |
+
raise ValueError("empty locale identifier")
|
| 1275 |
+
identifier, _, modifier = identifier.partition('@')
|
| 1276 |
+
if '.' in identifier:
|
| 1277 |
+
# this is probably the charset/encoding, which we don't care about
|
| 1278 |
+
identifier = identifier.split('.', 1)[0]
|
| 1279 |
+
|
| 1280 |
+
parts = identifier.split(sep)
|
| 1281 |
+
lang = parts.pop(0).lower()
|
| 1282 |
+
if not lang.isalpha():
|
| 1283 |
+
raise ValueError(f"expected only letters, got {lang!r}")
|
| 1284 |
+
|
| 1285 |
+
script = territory = variant = None
|
| 1286 |
+
if parts and len(parts[0]) == 4 and parts[0].isalpha():
|
| 1287 |
+
script = parts.pop(0).title()
|
| 1288 |
+
|
| 1289 |
+
if parts:
|
| 1290 |
+
if len(parts[0]) == 2 and parts[0].isalpha():
|
| 1291 |
+
territory = parts.pop(0).upper()
|
| 1292 |
+
elif len(parts[0]) == 3 and parts[0].isdigit():
|
| 1293 |
+
territory = parts.pop(0)
|
| 1294 |
+
|
| 1295 |
+
if parts and (
|
| 1296 |
+
len(parts[0]) == 4 and parts[0][0].isdigit() or
|
| 1297 |
+
len(parts[0]) >= 5 and parts[0][0].isalpha()
|
| 1298 |
+
):
|
| 1299 |
+
variant = parts.pop().upper()
|
| 1300 |
+
|
| 1301 |
+
if parts:
|
| 1302 |
+
raise ValueError(f"{identifier!r} is not a valid locale identifier")
|
| 1303 |
+
|
| 1304 |
+
# TODO(3.0): always return a 5-tuple
|
| 1305 |
+
if modifier:
|
| 1306 |
+
return lang, territory, script, variant, modifier
|
| 1307 |
+
else:
|
| 1308 |
+
return lang, territory, script, variant
|
| 1309 |
+
|
| 1310 |
+
|
| 1311 |
+
def get_locale_identifier(
|
| 1312 |
+
tup: tuple[str]
|
| 1313 |
+
| tuple[str, str | None]
|
| 1314 |
+
| tuple[str, str | None, str | None]
|
| 1315 |
+
| tuple[str, str | None, str | None, str | None]
|
| 1316 |
+
| tuple[str, str | None, str | None, str | None, str | None],
|
| 1317 |
+
sep: str = "_",
|
| 1318 |
+
) -> str:
|
| 1319 |
+
"""The reverse of :func:`parse_locale`. It creates a locale identifier out
|
| 1320 |
+
of a ``(language, territory, script, variant, modifier)`` tuple. Items can be set to
|
| 1321 |
+
``None`` and trailing ``None``\\s can also be left out of the tuple.
|
| 1322 |
+
|
| 1323 |
+
>>> get_locale_identifier(('de', 'DE', None, '1999', 'custom'))
|
| 1324 |
+
'de_DE_1999@custom'
|
| 1325 |
+
>>> get_locale_identifier(('fi', None, None, None, 'custom'))
|
| 1326 |
+
'fi@custom'
|
| 1327 |
+
|
| 1328 |
+
|
| 1329 |
+
.. versionadded:: 1.0
|
| 1330 |
+
|
| 1331 |
+
:param tup: the tuple as returned by :func:`parse_locale`.
|
| 1332 |
+
:param sep: the separator for the identifier.
|
| 1333 |
+
"""
|
| 1334 |
+
tup = tuple(tup[:5]) # type: ignore # length should be no more than 5
|
| 1335 |
+
lang, territory, script, variant, modifier = tup + (None,) * (5 - len(tup))
|
| 1336 |
+
ret = sep.join(filter(None, (lang, script, territory, variant)))
|
| 1337 |
+
return f'{ret}@{modifier}' if modifier else ret
|
rtme/lib/python3.10/site-packages/babel/dates.py
ADDED
|
@@ -0,0 +1,1999 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
babel.dates
|
| 3 |
+
~~~~~~~~~~~
|
| 4 |
+
|
| 5 |
+
Locale dependent formatting and parsing of dates and times.
|
| 6 |
+
|
| 7 |
+
The default locale for the functions in this module is determined by the
|
| 8 |
+
following environment variables, in that order:
|
| 9 |
+
|
| 10 |
+
* ``LC_TIME``,
|
| 11 |
+
* ``LC_ALL``, and
|
| 12 |
+
* ``LANG``
|
| 13 |
+
|
| 14 |
+
:copyright: (c) 2013-2025 by the Babel Team.
|
| 15 |
+
:license: BSD, see LICENSE for more details.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import math
|
| 21 |
+
import re
|
| 22 |
+
import warnings
|
| 23 |
+
from functools import lru_cache
|
| 24 |
+
from typing import TYPE_CHECKING, Literal, SupportsInt
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
import pytz
|
| 28 |
+
except ModuleNotFoundError:
|
| 29 |
+
pytz = None
|
| 30 |
+
import zoneinfo
|
| 31 |
+
|
| 32 |
+
import datetime
|
| 33 |
+
from collections.abc import Iterable
|
| 34 |
+
|
| 35 |
+
from babel import localtime
|
| 36 |
+
from babel.core import Locale, default_locale, get_global
|
| 37 |
+
from babel.localedata import LocaleDataDict
|
| 38 |
+
|
| 39 |
+
if TYPE_CHECKING:
|
| 40 |
+
from typing_extensions import TypeAlias
|
| 41 |
+
_Instant: TypeAlias = datetime.date | datetime.time | float | None
|
| 42 |
+
_PredefinedTimeFormat: TypeAlias = Literal['full', 'long', 'medium', 'short']
|
| 43 |
+
_Context: TypeAlias = Literal['format', 'stand-alone']
|
| 44 |
+
_DtOrTzinfo: TypeAlias = datetime.datetime | datetime.tzinfo | str | int | datetime.time | None
|
| 45 |
+
|
| 46 |
+
# "If a given short metazone form is known NOT to be understood in a given
|
| 47 |
+
# locale and the parent locale has this value such that it would normally
|
| 48 |
+
# be inherited, the inheritance of this value can be explicitly disabled by
|
| 49 |
+
# use of the 'no inheritance marker' as the value, which is 3 simultaneous [sic]
|
| 50 |
+
# empty set characters ( U+2205 )."
|
| 51 |
+
# - https://www.unicode.org/reports/tr35/tr35-dates.html#Metazone_Names
|
| 52 |
+
|
| 53 |
+
NO_INHERITANCE_MARKER = '\u2205\u2205\u2205'
|
| 54 |
+
|
| 55 |
+
UTC = datetime.timezone.utc
|
| 56 |
+
LOCALTZ = localtime.LOCALTZ
|
| 57 |
+
|
| 58 |
+
LC_TIME = default_locale('LC_TIME')
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _localize(tz: datetime.tzinfo, dt: datetime.datetime) -> datetime.datetime:
|
| 62 |
+
# Support localizing with both pytz and zoneinfo tzinfos
|
| 63 |
+
# nothing to do
|
| 64 |
+
if dt.tzinfo is tz:
|
| 65 |
+
return dt
|
| 66 |
+
|
| 67 |
+
if hasattr(tz, 'localize'): # pytz
|
| 68 |
+
return tz.localize(dt)
|
| 69 |
+
|
| 70 |
+
if dt.tzinfo is None:
|
| 71 |
+
# convert naive to localized
|
| 72 |
+
return dt.replace(tzinfo=tz)
|
| 73 |
+
|
| 74 |
+
# convert timezones
|
| 75 |
+
return dt.astimezone(tz)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _get_dt_and_tzinfo(dt_or_tzinfo: _DtOrTzinfo) -> tuple[datetime.datetime | None, datetime.tzinfo]:
|
| 79 |
+
"""
|
| 80 |
+
Parse a `dt_or_tzinfo` value into a datetime and a tzinfo.
|
| 81 |
+
|
| 82 |
+
See the docs for this function's callers for semantics.
|
| 83 |
+
|
| 84 |
+
:rtype: tuple[datetime, tzinfo]
|
| 85 |
+
"""
|
| 86 |
+
if dt_or_tzinfo is None:
|
| 87 |
+
dt = datetime.datetime.now()
|
| 88 |
+
tzinfo = LOCALTZ
|
| 89 |
+
elif isinstance(dt_or_tzinfo, str):
|
| 90 |
+
dt = None
|
| 91 |
+
tzinfo = get_timezone(dt_or_tzinfo)
|
| 92 |
+
elif isinstance(dt_or_tzinfo, int):
|
| 93 |
+
dt = None
|
| 94 |
+
tzinfo = UTC
|
| 95 |
+
elif isinstance(dt_or_tzinfo, (datetime.datetime, datetime.time)):
|
| 96 |
+
dt = _get_datetime(dt_or_tzinfo)
|
| 97 |
+
tzinfo = dt.tzinfo if dt.tzinfo is not None else UTC
|
| 98 |
+
else:
|
| 99 |
+
dt = None
|
| 100 |
+
tzinfo = dt_or_tzinfo
|
| 101 |
+
return dt, tzinfo
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _get_tz_name(dt_or_tzinfo: _DtOrTzinfo) -> str:
|
| 105 |
+
"""
|
| 106 |
+
Get the timezone name out of a time, datetime, or tzinfo object.
|
| 107 |
+
|
| 108 |
+
:rtype: str
|
| 109 |
+
"""
|
| 110 |
+
dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)
|
| 111 |
+
if hasattr(tzinfo, 'zone'): # pytz object
|
| 112 |
+
return tzinfo.zone
|
| 113 |
+
elif hasattr(tzinfo, 'key') and tzinfo.key is not None: # ZoneInfo object
|
| 114 |
+
return tzinfo.key
|
| 115 |
+
else:
|
| 116 |
+
return tzinfo.tzname(dt or datetime.datetime.now(UTC))
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _get_datetime(instant: _Instant) -> datetime.datetime:
|
| 120 |
+
"""
|
| 121 |
+
Get a datetime out of an "instant" (date, time, datetime, number).
|
| 122 |
+
|
| 123 |
+
.. warning:: The return values of this function may depend on the system clock.
|
| 124 |
+
|
| 125 |
+
If the instant is None, the current moment is used.
|
| 126 |
+
If the instant is a time, it's augmented with today's date.
|
| 127 |
+
|
| 128 |
+
Dates are converted to naive datetimes with midnight as the time component.
|
| 129 |
+
|
| 130 |
+
>>> from datetime import date, datetime
|
| 131 |
+
>>> _get_datetime(date(2015, 1, 1))
|
| 132 |
+
datetime.datetime(2015, 1, 1, 0, 0)
|
| 133 |
+
|
| 134 |
+
UNIX timestamps are converted to datetimes.
|
| 135 |
+
|
| 136 |
+
>>> _get_datetime(1400000000)
|
| 137 |
+
datetime.datetime(2014, 5, 13, 16, 53, 20)
|
| 138 |
+
|
| 139 |
+
Other values are passed through as-is.
|
| 140 |
+
|
| 141 |
+
>>> x = datetime(2015, 1, 1)
|
| 142 |
+
>>> _get_datetime(x) is x
|
| 143 |
+
True
|
| 144 |
+
|
| 145 |
+
:param instant: date, time, datetime, integer, float or None
|
| 146 |
+
:type instant: date|time|datetime|int|float|None
|
| 147 |
+
:return: a datetime
|
| 148 |
+
:rtype: datetime
|
| 149 |
+
"""
|
| 150 |
+
if instant is None:
|
| 151 |
+
return datetime.datetime.now(UTC).replace(tzinfo=None)
|
| 152 |
+
elif isinstance(instant, (int, float)):
|
| 153 |
+
return datetime.datetime.fromtimestamp(instant, UTC).replace(tzinfo=None)
|
| 154 |
+
elif isinstance(instant, datetime.time):
|
| 155 |
+
return datetime.datetime.combine(datetime.date.today(), instant)
|
| 156 |
+
elif isinstance(instant, datetime.date) and not isinstance(instant, datetime.datetime):
|
| 157 |
+
return datetime.datetime.combine(instant, datetime.time())
|
| 158 |
+
# TODO (3.x): Add an assertion/type check for this fallthrough branch:
|
| 159 |
+
return instant
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def _ensure_datetime_tzinfo(dt: datetime.datetime, tzinfo: datetime.tzinfo | None = None) -> datetime.datetime:
|
| 163 |
+
"""
|
| 164 |
+
Ensure the datetime passed has an attached tzinfo.
|
| 165 |
+
|
| 166 |
+
If the datetime is tz-naive to begin with, UTC is attached.
|
| 167 |
+
|
| 168 |
+
If a tzinfo is passed in, the datetime is normalized to that timezone.
|
| 169 |
+
|
| 170 |
+
>>> from datetime import datetime
|
| 171 |
+
>>> _get_tz_name(_ensure_datetime_tzinfo(datetime(2015, 1, 1)))
|
| 172 |
+
'UTC'
|
| 173 |
+
|
| 174 |
+
>>> tz = get_timezone("Europe/Stockholm")
|
| 175 |
+
>>> _ensure_datetime_tzinfo(datetime(2015, 1, 1, 13, 15, tzinfo=UTC), tzinfo=tz).hour
|
| 176 |
+
14
|
| 177 |
+
|
| 178 |
+
:param datetime: Datetime to augment.
|
| 179 |
+
:param tzinfo: optional tzinfo
|
| 180 |
+
:return: datetime with tzinfo
|
| 181 |
+
:rtype: datetime
|
| 182 |
+
"""
|
| 183 |
+
if dt.tzinfo is None:
|
| 184 |
+
dt = dt.replace(tzinfo=UTC)
|
| 185 |
+
if tzinfo is not None:
|
| 186 |
+
dt = dt.astimezone(get_timezone(tzinfo))
|
| 187 |
+
if hasattr(tzinfo, 'normalize'): # pytz
|
| 188 |
+
dt = tzinfo.normalize(dt)
|
| 189 |
+
return dt
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def _get_time(
|
| 193 |
+
time: datetime.time | datetime.datetime | None,
|
| 194 |
+
tzinfo: datetime.tzinfo | None = None,
|
| 195 |
+
) -> datetime.time:
|
| 196 |
+
"""
|
| 197 |
+
Get a timezoned time from a given instant.
|
| 198 |
+
|
| 199 |
+
.. warning:: The return values of this function may depend on the system clock.
|
| 200 |
+
|
| 201 |
+
:param time: time, datetime or None
|
| 202 |
+
:rtype: time
|
| 203 |
+
"""
|
| 204 |
+
if time is None:
|
| 205 |
+
time = datetime.datetime.now(UTC)
|
| 206 |
+
elif isinstance(time, (int, float)):
|
| 207 |
+
time = datetime.datetime.fromtimestamp(time, UTC)
|
| 208 |
+
|
| 209 |
+
if time.tzinfo is None:
|
| 210 |
+
time = time.replace(tzinfo=UTC)
|
| 211 |
+
|
| 212 |
+
if isinstance(time, datetime.datetime):
|
| 213 |
+
if tzinfo is not None:
|
| 214 |
+
time = time.astimezone(tzinfo)
|
| 215 |
+
if hasattr(tzinfo, 'normalize'): # pytz
|
| 216 |
+
time = tzinfo.normalize(time)
|
| 217 |
+
time = time.timetz()
|
| 218 |
+
elif tzinfo is not None:
|
| 219 |
+
time = time.replace(tzinfo=tzinfo)
|
| 220 |
+
return time
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def get_timezone(zone: str | datetime.tzinfo | None = None) -> datetime.tzinfo:
|
| 224 |
+
"""Looks up a timezone by name and returns it. The timezone object
|
| 225 |
+
returned comes from ``pytz`` or ``zoneinfo``, whichever is available.
|
| 226 |
+
It corresponds to the `tzinfo` interface and can be used with all of
|
| 227 |
+
the functions of Babel that operate with dates.
|
| 228 |
+
|
| 229 |
+
If a timezone is not known a :exc:`LookupError` is raised. If `zone`
|
| 230 |
+
is ``None`` a local zone object is returned.
|
| 231 |
+
|
| 232 |
+
:param zone: the name of the timezone to look up. If a timezone object
|
| 233 |
+
itself is passed in, it's returned unchanged.
|
| 234 |
+
"""
|
| 235 |
+
if zone is None:
|
| 236 |
+
return LOCALTZ
|
| 237 |
+
if not isinstance(zone, str):
|
| 238 |
+
return zone
|
| 239 |
+
|
| 240 |
+
if pytz:
|
| 241 |
+
try:
|
| 242 |
+
return pytz.timezone(zone)
|
| 243 |
+
except pytz.UnknownTimeZoneError as e:
|
| 244 |
+
exc = e
|
| 245 |
+
else:
|
| 246 |
+
assert zoneinfo
|
| 247 |
+
try:
|
| 248 |
+
return zoneinfo.ZoneInfo(zone)
|
| 249 |
+
except zoneinfo.ZoneInfoNotFoundError as e:
|
| 250 |
+
exc = e
|
| 251 |
+
|
| 252 |
+
raise LookupError(f"Unknown timezone {zone}") from exc
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def get_period_names(
|
| 256 |
+
width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
|
| 257 |
+
context: _Context = 'stand-alone',
|
| 258 |
+
locale: Locale | str | None = None,
|
| 259 |
+
) -> LocaleDataDict:
|
| 260 |
+
"""Return the names for day periods (AM/PM) used by the locale.
|
| 261 |
+
|
| 262 |
+
>>> get_period_names(locale='en_US')['am']
|
| 263 |
+
u'AM'
|
| 264 |
+
|
| 265 |
+
:param width: the width to use, one of "abbreviated", "narrow", or "wide"
|
| 266 |
+
:param context: the context, either "format" or "stand-alone"
|
| 267 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 268 |
+
"""
|
| 269 |
+
return Locale.parse(locale or LC_TIME).day_periods[context][width]
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def get_day_names(
|
| 273 |
+
width: Literal['abbreviated', 'narrow', 'short', 'wide'] = 'wide',
|
| 274 |
+
context: _Context = 'format',
|
| 275 |
+
locale: Locale | str | None = None,
|
| 276 |
+
) -> LocaleDataDict:
|
| 277 |
+
"""Return the day names used by the locale for the specified format.
|
| 278 |
+
|
| 279 |
+
>>> get_day_names('wide', locale='en_US')[1]
|
| 280 |
+
u'Tuesday'
|
| 281 |
+
>>> get_day_names('short', locale='en_US')[1]
|
| 282 |
+
u'Tu'
|
| 283 |
+
>>> get_day_names('abbreviated', locale='es')[1]
|
| 284 |
+
u'mar'
|
| 285 |
+
>>> get_day_names('narrow', context='stand-alone', locale='de_DE')[1]
|
| 286 |
+
u'D'
|
| 287 |
+
|
| 288 |
+
:param width: the width to use, one of "wide", "abbreviated", "short" or "narrow"
|
| 289 |
+
:param context: the context, either "format" or "stand-alone"
|
| 290 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 291 |
+
"""
|
| 292 |
+
return Locale.parse(locale or LC_TIME).days[context][width]
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
def get_month_names(
|
| 296 |
+
width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
|
| 297 |
+
context: _Context = 'format',
|
| 298 |
+
locale: Locale | str | None = None,
|
| 299 |
+
) -> LocaleDataDict:
|
| 300 |
+
"""Return the month names used by the locale for the specified format.
|
| 301 |
+
|
| 302 |
+
>>> get_month_names('wide', locale='en_US')[1]
|
| 303 |
+
u'January'
|
| 304 |
+
>>> get_month_names('abbreviated', locale='es')[1]
|
| 305 |
+
u'ene'
|
| 306 |
+
>>> get_month_names('narrow', context='stand-alone', locale='de_DE')[1]
|
| 307 |
+
u'J'
|
| 308 |
+
|
| 309 |
+
:param width: the width to use, one of "wide", "abbreviated", or "narrow"
|
| 310 |
+
:param context: the context, either "format" or "stand-alone"
|
| 311 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 312 |
+
"""
|
| 313 |
+
return Locale.parse(locale or LC_TIME).months[context][width]
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def get_quarter_names(
|
| 317 |
+
width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
|
| 318 |
+
context: _Context = 'format',
|
| 319 |
+
locale: Locale | str | None = None,
|
| 320 |
+
) -> LocaleDataDict:
|
| 321 |
+
"""Return the quarter names used by the locale for the specified format.
|
| 322 |
+
|
| 323 |
+
>>> get_quarter_names('wide', locale='en_US')[1]
|
| 324 |
+
u'1st quarter'
|
| 325 |
+
>>> get_quarter_names('abbreviated', locale='de_DE')[1]
|
| 326 |
+
u'Q1'
|
| 327 |
+
>>> get_quarter_names('narrow', locale='de_DE')[1]
|
| 328 |
+
u'1'
|
| 329 |
+
|
| 330 |
+
:param width: the width to use, one of "wide", "abbreviated", or "narrow"
|
| 331 |
+
:param context: the context, either "format" or "stand-alone"
|
| 332 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 333 |
+
"""
|
| 334 |
+
return Locale.parse(locale or LC_TIME).quarters[context][width]
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def get_era_names(
|
| 338 |
+
width: Literal['abbreviated', 'narrow', 'wide'] = 'wide',
|
| 339 |
+
locale: Locale | str | None = None,
|
| 340 |
+
) -> LocaleDataDict:
|
| 341 |
+
"""Return the era names used by the locale for the specified format.
|
| 342 |
+
|
| 343 |
+
>>> get_era_names('wide', locale='en_US')[1]
|
| 344 |
+
u'Anno Domini'
|
| 345 |
+
>>> get_era_names('abbreviated', locale='de_DE')[1]
|
| 346 |
+
u'n. Chr.'
|
| 347 |
+
|
| 348 |
+
:param width: the width to use, either "wide", "abbreviated", or "narrow"
|
| 349 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 350 |
+
"""
|
| 351 |
+
return Locale.parse(locale or LC_TIME).eras[width]
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def get_date_format(
|
| 355 |
+
format: _PredefinedTimeFormat = 'medium',
|
| 356 |
+
locale: Locale | str | None = None,
|
| 357 |
+
) -> DateTimePattern:
|
| 358 |
+
"""Return the date formatting patterns used by the locale for the specified
|
| 359 |
+
format.
|
| 360 |
+
|
| 361 |
+
>>> get_date_format(locale='en_US')
|
| 362 |
+
<DateTimePattern u'MMM d, y'>
|
| 363 |
+
>>> get_date_format('full', locale='de_DE')
|
| 364 |
+
<DateTimePattern u'EEEE, d. MMMM y'>
|
| 365 |
+
|
| 366 |
+
:param format: the format to use, one of "full", "long", "medium", or
|
| 367 |
+
"short"
|
| 368 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 369 |
+
"""
|
| 370 |
+
return Locale.parse(locale or LC_TIME).date_formats[format]
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
def get_datetime_format(
|
| 374 |
+
format: _PredefinedTimeFormat = 'medium',
|
| 375 |
+
locale: Locale | str | None = None,
|
| 376 |
+
) -> DateTimePattern:
|
| 377 |
+
"""Return the datetime formatting patterns used by the locale for the
|
| 378 |
+
specified format.
|
| 379 |
+
|
| 380 |
+
>>> get_datetime_format(locale='en_US')
|
| 381 |
+
u'{1}, {0}'
|
| 382 |
+
|
| 383 |
+
:param format: the format to use, one of "full", "long", "medium", or
|
| 384 |
+
"short"
|
| 385 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 386 |
+
"""
|
| 387 |
+
patterns = Locale.parse(locale or LC_TIME).datetime_formats
|
| 388 |
+
if format not in patterns:
|
| 389 |
+
format = None
|
| 390 |
+
return patterns[format]
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def get_time_format(
|
| 394 |
+
format: _PredefinedTimeFormat = 'medium',
|
| 395 |
+
locale: Locale | str | None = None,
|
| 396 |
+
) -> DateTimePattern:
|
| 397 |
+
"""Return the time formatting patterns used by the locale for the specified
|
| 398 |
+
format.
|
| 399 |
+
|
| 400 |
+
>>> get_time_format(locale='en_US')
|
| 401 |
+
<DateTimePattern u'h:mm:ss\u202fa'>
|
| 402 |
+
>>> get_time_format('full', locale='de_DE')
|
| 403 |
+
<DateTimePattern u'HH:mm:ss zzzz'>
|
| 404 |
+
|
| 405 |
+
:param format: the format to use, one of "full", "long", "medium", or
|
| 406 |
+
"short"
|
| 407 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 408 |
+
"""
|
| 409 |
+
return Locale.parse(locale or LC_TIME).time_formats[format]
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
def get_timezone_gmt(
|
| 413 |
+
datetime: _Instant = None,
|
| 414 |
+
width: Literal['long', 'short', 'iso8601', 'iso8601_short'] = 'long',
|
| 415 |
+
locale: Locale | str | None = None,
|
| 416 |
+
return_z: bool = False,
|
| 417 |
+
) -> str:
|
| 418 |
+
"""Return the timezone associated with the given `datetime` object formatted
|
| 419 |
+
as string indicating the offset from GMT.
|
| 420 |
+
|
| 421 |
+
>>> from datetime import datetime
|
| 422 |
+
>>> dt = datetime(2007, 4, 1, 15, 30)
|
| 423 |
+
>>> get_timezone_gmt(dt, locale='en')
|
| 424 |
+
u'GMT+00:00'
|
| 425 |
+
>>> get_timezone_gmt(dt, locale='en', return_z=True)
|
| 426 |
+
'Z'
|
| 427 |
+
>>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
|
| 428 |
+
u'+00'
|
| 429 |
+
>>> tz = get_timezone('America/Los_Angeles')
|
| 430 |
+
>>> dt = _localize(tz, datetime(2007, 4, 1, 15, 30))
|
| 431 |
+
>>> get_timezone_gmt(dt, locale='en')
|
| 432 |
+
u'GMT-07:00'
|
| 433 |
+
>>> get_timezone_gmt(dt, 'short', locale='en')
|
| 434 |
+
u'-0700'
|
| 435 |
+
>>> get_timezone_gmt(dt, locale='en', width='iso8601_short')
|
| 436 |
+
u'-07'
|
| 437 |
+
|
| 438 |
+
The long format depends on the locale, for example in France the acronym
|
| 439 |
+
UTC string is used instead of GMT:
|
| 440 |
+
|
| 441 |
+
>>> get_timezone_gmt(dt, 'long', locale='fr_FR')
|
| 442 |
+
u'UTC-07:00'
|
| 443 |
+
|
| 444 |
+
.. versionadded:: 0.9
|
| 445 |
+
|
| 446 |
+
:param datetime: the ``datetime`` object; if `None`, the current date and
|
| 447 |
+
time in UTC is used
|
| 448 |
+
:param width: either "long" or "short" or "iso8601" or "iso8601_short"
|
| 449 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 450 |
+
:param return_z: True or False; Function returns indicator "Z"
|
| 451 |
+
when local time offset is 0
|
| 452 |
+
"""
|
| 453 |
+
datetime = _ensure_datetime_tzinfo(_get_datetime(datetime))
|
| 454 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 455 |
+
|
| 456 |
+
offset = datetime.tzinfo.utcoffset(datetime)
|
| 457 |
+
seconds = offset.days * 24 * 60 * 60 + offset.seconds
|
| 458 |
+
hours, seconds = divmod(seconds, 3600)
|
| 459 |
+
if return_z and hours == 0 and seconds == 0:
|
| 460 |
+
return 'Z'
|
| 461 |
+
elif seconds == 0 and width == 'iso8601_short':
|
| 462 |
+
return '%+03d' % hours
|
| 463 |
+
elif width == 'short' or width == 'iso8601_short':
|
| 464 |
+
pattern = '%+03d%02d'
|
| 465 |
+
elif width == 'iso8601':
|
| 466 |
+
pattern = '%+03d:%02d'
|
| 467 |
+
else:
|
| 468 |
+
pattern = locale.zone_formats['gmt'] % '%+03d:%02d'
|
| 469 |
+
return pattern % (hours, seconds // 60)
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
def get_timezone_location(
|
| 473 |
+
dt_or_tzinfo: _DtOrTzinfo = None,
|
| 474 |
+
locale: Locale | str | None = None,
|
| 475 |
+
return_city: bool = False,
|
| 476 |
+
) -> str:
|
| 477 |
+
"""Return a representation of the given timezone using "location format".
|
| 478 |
+
|
| 479 |
+
The result depends on both the local display name of the country and the
|
| 480 |
+
city associated with the time zone:
|
| 481 |
+
|
| 482 |
+
>>> tz = get_timezone('America/St_Johns')
|
| 483 |
+
>>> print(get_timezone_location(tz, locale='de_DE'))
|
| 484 |
+
Kanada (St. John’s) (Ortszeit)
|
| 485 |
+
>>> print(get_timezone_location(tz, locale='en'))
|
| 486 |
+
Canada (St. John’s) Time
|
| 487 |
+
>>> print(get_timezone_location(tz, locale='en', return_city=True))
|
| 488 |
+
St. John’s
|
| 489 |
+
>>> tz = get_timezone('America/Mexico_City')
|
| 490 |
+
>>> get_timezone_location(tz, locale='de_DE')
|
| 491 |
+
u'Mexiko (Mexiko-Stadt) (Ortszeit)'
|
| 492 |
+
|
| 493 |
+
If the timezone is associated with a country that uses only a single
|
| 494 |
+
timezone, just the localized country name is returned:
|
| 495 |
+
|
| 496 |
+
>>> tz = get_timezone('Europe/Berlin')
|
| 497 |
+
>>> get_timezone_name(tz, locale='de_DE')
|
| 498 |
+
u'Mitteleurop\\xe4ische Zeit'
|
| 499 |
+
|
| 500 |
+
.. versionadded:: 0.9
|
| 501 |
+
|
| 502 |
+
:param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
|
| 503 |
+
the timezone; if `None`, the current date and time in
|
| 504 |
+
UTC is assumed
|
| 505 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 506 |
+
:param return_city: True or False, if True then return exemplar city (location)
|
| 507 |
+
for the time zone
|
| 508 |
+
:return: the localized timezone name using location format
|
| 509 |
+
|
| 510 |
+
"""
|
| 511 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 512 |
+
|
| 513 |
+
zone = _get_tz_name(dt_or_tzinfo)
|
| 514 |
+
|
| 515 |
+
# Get the canonical time-zone code
|
| 516 |
+
zone = get_global('zone_aliases').get(zone, zone)
|
| 517 |
+
|
| 518 |
+
info = locale.time_zones.get(zone, {})
|
| 519 |
+
|
| 520 |
+
# Otherwise, if there is only one timezone for the country, return the
|
| 521 |
+
# localized country name
|
| 522 |
+
region_format = locale.zone_formats['region']
|
| 523 |
+
territory = get_global('zone_territories').get(zone)
|
| 524 |
+
if territory not in locale.territories:
|
| 525 |
+
territory = 'ZZ' # invalid/unknown
|
| 526 |
+
territory_name = locale.territories[territory]
|
| 527 |
+
if not return_city and territory and len(get_global('territory_zones').get(territory, [])) == 1:
|
| 528 |
+
return region_format % territory_name
|
| 529 |
+
|
| 530 |
+
# Otherwise, include the city in the output
|
| 531 |
+
fallback_format = locale.zone_formats['fallback']
|
| 532 |
+
if 'city' in info:
|
| 533 |
+
city_name = info['city']
|
| 534 |
+
else:
|
| 535 |
+
metazone = get_global('meta_zones').get(zone)
|
| 536 |
+
metazone_info = locale.meta_zones.get(metazone, {})
|
| 537 |
+
if 'city' in metazone_info:
|
| 538 |
+
city_name = metazone_info['city']
|
| 539 |
+
elif '/' in zone:
|
| 540 |
+
city_name = zone.split('/', 1)[1].replace('_', ' ')
|
| 541 |
+
else:
|
| 542 |
+
city_name = zone.replace('_', ' ')
|
| 543 |
+
|
| 544 |
+
if return_city:
|
| 545 |
+
return city_name
|
| 546 |
+
return region_format % (fallback_format % {
|
| 547 |
+
'0': city_name,
|
| 548 |
+
'1': territory_name,
|
| 549 |
+
})
|
| 550 |
+
|
| 551 |
+
|
| 552 |
+
def get_timezone_name(
|
| 553 |
+
dt_or_tzinfo: _DtOrTzinfo = None,
|
| 554 |
+
width: Literal['long', 'short'] = 'long',
|
| 555 |
+
uncommon: bool = False,
|
| 556 |
+
locale: Locale | str | None = None,
|
| 557 |
+
zone_variant: Literal['generic', 'daylight', 'standard'] | None = None,
|
| 558 |
+
return_zone: bool = False,
|
| 559 |
+
) -> str:
|
| 560 |
+
r"""Return the localized display name for the given timezone. The timezone
|
| 561 |
+
may be specified using a ``datetime`` or `tzinfo` object.
|
| 562 |
+
|
| 563 |
+
>>> from datetime import time
|
| 564 |
+
>>> dt = time(15, 30, tzinfo=get_timezone('America/Los_Angeles'))
|
| 565 |
+
>>> get_timezone_name(dt, locale='en_US') # doctest: +SKIP
|
| 566 |
+
u'Pacific Standard Time'
|
| 567 |
+
>>> get_timezone_name(dt, locale='en_US', return_zone=True)
|
| 568 |
+
'America/Los_Angeles'
|
| 569 |
+
>>> get_timezone_name(dt, width='short', locale='en_US') # doctest: +SKIP
|
| 570 |
+
u'PST'
|
| 571 |
+
|
| 572 |
+
If this function gets passed only a `tzinfo` object and no concrete
|
| 573 |
+
`datetime`, the returned display name is independent of daylight savings
|
| 574 |
+
time. This can be used for example for selecting timezones, or to set the
|
| 575 |
+
time of events that recur across DST changes:
|
| 576 |
+
|
| 577 |
+
>>> tz = get_timezone('America/Los_Angeles')
|
| 578 |
+
>>> get_timezone_name(tz, locale='en_US')
|
| 579 |
+
u'Pacific Time'
|
| 580 |
+
>>> get_timezone_name(tz, 'short', locale='en_US')
|
| 581 |
+
u'PT'
|
| 582 |
+
|
| 583 |
+
If no localized display name for the timezone is available, and the timezone
|
| 584 |
+
is associated with a country that uses only a single timezone, the name of
|
| 585 |
+
that country is returned, formatted according to the locale:
|
| 586 |
+
|
| 587 |
+
>>> tz = get_timezone('Europe/Berlin')
|
| 588 |
+
>>> get_timezone_name(tz, locale='de_DE')
|
| 589 |
+
u'Mitteleurop\xe4ische Zeit'
|
| 590 |
+
>>> get_timezone_name(tz, locale='pt_BR')
|
| 591 |
+
u'Hor\xe1rio da Europa Central'
|
| 592 |
+
|
| 593 |
+
On the other hand, if the country uses multiple timezones, the city is also
|
| 594 |
+
included in the representation:
|
| 595 |
+
|
| 596 |
+
>>> tz = get_timezone('America/St_Johns')
|
| 597 |
+
>>> get_timezone_name(tz, locale='de_DE')
|
| 598 |
+
u'Neufundland-Zeit'
|
| 599 |
+
|
| 600 |
+
Note that short format is currently not supported for all timezones and
|
| 601 |
+
all locales. This is partially because not every timezone has a short
|
| 602 |
+
code in every locale. In that case it currently falls back to the long
|
| 603 |
+
format.
|
| 604 |
+
|
| 605 |
+
For more information see `LDML Appendix J: Time Zone Display Names
|
| 606 |
+
<https://www.unicode.org/reports/tr35/#Time_Zone_Fallback>`_
|
| 607 |
+
|
| 608 |
+
.. versionadded:: 0.9
|
| 609 |
+
|
| 610 |
+
.. versionchanged:: 1.0
|
| 611 |
+
Added `zone_variant` support.
|
| 612 |
+
|
| 613 |
+
:param dt_or_tzinfo: the ``datetime`` or ``tzinfo`` object that determines
|
| 614 |
+
the timezone; if a ``tzinfo`` object is used, the
|
| 615 |
+
resulting display name will be generic, i.e.
|
| 616 |
+
independent of daylight savings time; if `None`, the
|
| 617 |
+
current date in UTC is assumed
|
| 618 |
+
:param width: either "long" or "short"
|
| 619 |
+
:param uncommon: deprecated and ignored
|
| 620 |
+
:param zone_variant: defines the zone variation to return. By default the
|
| 621 |
+
variation is defined from the datetime object
|
| 622 |
+
passed in. If no datetime object is passed in, the
|
| 623 |
+
``'generic'`` variation is assumed. The following
|
| 624 |
+
values are valid: ``'generic'``, ``'daylight'`` and
|
| 625 |
+
``'standard'``.
|
| 626 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 627 |
+
:param return_zone: True or False. If true then function
|
| 628 |
+
returns long time zone ID
|
| 629 |
+
"""
|
| 630 |
+
dt, tzinfo = _get_dt_and_tzinfo(dt_or_tzinfo)
|
| 631 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 632 |
+
|
| 633 |
+
zone = _get_tz_name(dt_or_tzinfo)
|
| 634 |
+
|
| 635 |
+
if zone_variant is None:
|
| 636 |
+
if dt is None:
|
| 637 |
+
zone_variant = 'generic'
|
| 638 |
+
else:
|
| 639 |
+
dst = tzinfo.dst(dt)
|
| 640 |
+
zone_variant = "daylight" if dst else "standard"
|
| 641 |
+
else:
|
| 642 |
+
if zone_variant not in ('generic', 'standard', 'daylight'):
|
| 643 |
+
raise ValueError('Invalid zone variation')
|
| 644 |
+
|
| 645 |
+
# Get the canonical time-zone code
|
| 646 |
+
zone = get_global('zone_aliases').get(zone, zone)
|
| 647 |
+
if return_zone:
|
| 648 |
+
return zone
|
| 649 |
+
info = locale.time_zones.get(zone, {})
|
| 650 |
+
# Try explicitly translated zone names first
|
| 651 |
+
if width in info and zone_variant in info[width]:
|
| 652 |
+
return info[width][zone_variant]
|
| 653 |
+
|
| 654 |
+
metazone = get_global('meta_zones').get(zone)
|
| 655 |
+
if metazone:
|
| 656 |
+
metazone_info = locale.meta_zones.get(metazone, {})
|
| 657 |
+
if width in metazone_info:
|
| 658 |
+
name = metazone_info[width].get(zone_variant)
|
| 659 |
+
if width == 'short' and name == NO_INHERITANCE_MARKER:
|
| 660 |
+
# If the short form is marked no-inheritance,
|
| 661 |
+
# try to fall back to the long name instead.
|
| 662 |
+
name = metazone_info.get('long', {}).get(zone_variant)
|
| 663 |
+
if name:
|
| 664 |
+
return name
|
| 665 |
+
|
| 666 |
+
# If we have a concrete datetime, we assume that the result can't be
|
| 667 |
+
# independent of daylight savings time, so we return the GMT offset
|
| 668 |
+
if dt is not None:
|
| 669 |
+
return get_timezone_gmt(dt, width=width, locale=locale)
|
| 670 |
+
|
| 671 |
+
return get_timezone_location(dt_or_tzinfo, locale=locale)
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
def format_date(
|
| 675 |
+
date: datetime.date | None = None,
|
| 676 |
+
format: _PredefinedTimeFormat | str = 'medium',
|
| 677 |
+
locale: Locale | str | None = None,
|
| 678 |
+
) -> str:
|
| 679 |
+
"""Return a date formatted according to the given pattern.
|
| 680 |
+
|
| 681 |
+
>>> from datetime import date
|
| 682 |
+
>>> d = date(2007, 4, 1)
|
| 683 |
+
>>> format_date(d, locale='en_US')
|
| 684 |
+
u'Apr 1, 2007'
|
| 685 |
+
>>> format_date(d, format='full', locale='de_DE')
|
| 686 |
+
u'Sonntag, 1. April 2007'
|
| 687 |
+
|
| 688 |
+
If you don't want to use the locale default formats, you can specify a
|
| 689 |
+
custom date pattern:
|
| 690 |
+
|
| 691 |
+
>>> format_date(d, "EEE, MMM d, ''yy", locale='en')
|
| 692 |
+
u"Sun, Apr 1, '07"
|
| 693 |
+
|
| 694 |
+
:param date: the ``date`` or ``datetime`` object; if `None`, the current
|
| 695 |
+
date is used
|
| 696 |
+
:param format: one of "full", "long", "medium", or "short", or a custom
|
| 697 |
+
date/time pattern
|
| 698 |
+
:param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
|
| 699 |
+
"""
|
| 700 |
+
if date is None:
|
| 701 |
+
date = datetime.date.today()
|
| 702 |
+
elif isinstance(date, datetime.datetime):
|
| 703 |
+
date = date.date()
|
| 704 |
+
|
| 705 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 706 |
+
if format in ('full', 'long', 'medium', 'short'):
|
| 707 |
+
format = get_date_format(format, locale=locale)
|
| 708 |
+
pattern = parse_pattern(format)
|
| 709 |
+
return pattern.apply(date, locale)
|
| 710 |
+
|
| 711 |
+
|
| 712 |
+
def format_datetime(
|
| 713 |
+
datetime: _Instant = None,
|
| 714 |
+
format: _PredefinedTimeFormat | str = 'medium',
|
| 715 |
+
tzinfo: datetime.tzinfo | None = None,
|
| 716 |
+
locale: Locale | str | None = None,
|
| 717 |
+
) -> str:
|
| 718 |
+
r"""Return a date formatted according to the given pattern.
|
| 719 |
+
|
| 720 |
+
>>> from datetime import datetime
|
| 721 |
+
>>> dt = datetime(2007, 4, 1, 15, 30)
|
| 722 |
+
>>> format_datetime(dt, locale='en_US')
|
| 723 |
+
u'Apr 1, 2007, 3:30:00\u202fPM'
|
| 724 |
+
|
| 725 |
+
For any pattern requiring the display of the timezone:
|
| 726 |
+
|
| 727 |
+
>>> format_datetime(dt, 'full', tzinfo=get_timezone('Europe/Paris'),
|
| 728 |
+
... locale='fr_FR')
|
| 729 |
+
'dimanche 1 avril 2007, 17:30:00 heure d’été d’Europe centrale'
|
| 730 |
+
>>> format_datetime(dt, "yyyy.MM.dd G 'at' HH:mm:ss zzz",
|
| 731 |
+
... tzinfo=get_timezone('US/Eastern'), locale='en')
|
| 732 |
+
u'2007.04.01 AD at 11:30:00 EDT'
|
| 733 |
+
|
| 734 |
+
:param datetime: the `datetime` object; if `None`, the current date and
|
| 735 |
+
time is used
|
| 736 |
+
:param format: one of "full", "long", "medium", or "short", or a custom
|
| 737 |
+
date/time pattern
|
| 738 |
+
:param tzinfo: the timezone to apply to the time for display
|
| 739 |
+
:param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
|
| 740 |
+
"""
|
| 741 |
+
datetime = _ensure_datetime_tzinfo(_get_datetime(datetime), tzinfo)
|
| 742 |
+
|
| 743 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 744 |
+
if format in ('full', 'long', 'medium', 'short'):
|
| 745 |
+
return get_datetime_format(format, locale=locale) \
|
| 746 |
+
.replace("'", "") \
|
| 747 |
+
.replace('{0}', format_time(datetime, format, tzinfo=None,
|
| 748 |
+
locale=locale)) \
|
| 749 |
+
.replace('{1}', format_date(datetime, format, locale=locale))
|
| 750 |
+
else:
|
| 751 |
+
return parse_pattern(format).apply(datetime, locale)
|
| 752 |
+
|
| 753 |
+
|
| 754 |
+
def format_time(
|
| 755 |
+
time: datetime.time | datetime.datetime | float | None = None,
|
| 756 |
+
format: _PredefinedTimeFormat | str = 'medium',
|
| 757 |
+
tzinfo: datetime.tzinfo | None = None,
|
| 758 |
+
locale: Locale | str | None = None,
|
| 759 |
+
) -> str:
|
| 760 |
+
r"""Return a time formatted according to the given pattern.
|
| 761 |
+
|
| 762 |
+
>>> from datetime import datetime, time
|
| 763 |
+
>>> t = time(15, 30)
|
| 764 |
+
>>> format_time(t, locale='en_US')
|
| 765 |
+
u'3:30:00\u202fPM'
|
| 766 |
+
>>> format_time(t, format='short', locale='de_DE')
|
| 767 |
+
u'15:30'
|
| 768 |
+
|
| 769 |
+
If you don't want to use the locale default formats, you can specify a
|
| 770 |
+
custom time pattern:
|
| 771 |
+
|
| 772 |
+
>>> format_time(t, "hh 'o''clock' a", locale='en')
|
| 773 |
+
u"03 o'clock PM"
|
| 774 |
+
|
| 775 |
+
For any pattern requiring the display of the time-zone a
|
| 776 |
+
timezone has to be specified explicitly:
|
| 777 |
+
|
| 778 |
+
>>> t = datetime(2007, 4, 1, 15, 30)
|
| 779 |
+
>>> tzinfo = get_timezone('Europe/Paris')
|
| 780 |
+
>>> t = _localize(tzinfo, t)
|
| 781 |
+
>>> format_time(t, format='full', tzinfo=tzinfo, locale='fr_FR')
|
| 782 |
+
'15:30:00 heure d’été d’Europe centrale'
|
| 783 |
+
>>> format_time(t, "hh 'o''clock' a, zzzz", tzinfo=get_timezone('US/Eastern'),
|
| 784 |
+
... locale='en')
|
| 785 |
+
u"09 o'clock AM, Eastern Daylight Time"
|
| 786 |
+
|
| 787 |
+
As that example shows, when this function gets passed a
|
| 788 |
+
``datetime.datetime`` value, the actual time in the formatted string is
|
| 789 |
+
adjusted to the timezone specified by the `tzinfo` parameter. If the
|
| 790 |
+
``datetime`` is "naive" (i.e. it has no associated timezone information),
|
| 791 |
+
it is assumed to be in UTC.
|
| 792 |
+
|
| 793 |
+
These timezone calculations are **not** performed if the value is of type
|
| 794 |
+
``datetime.time``, as without date information there's no way to determine
|
| 795 |
+
what a given time would translate to in a different timezone without
|
| 796 |
+
information about whether daylight savings time is in effect or not. This
|
| 797 |
+
means that time values are left as-is, and the value of the `tzinfo`
|
| 798 |
+
parameter is only used to display the timezone name if needed:
|
| 799 |
+
|
| 800 |
+
>>> t = time(15, 30)
|
| 801 |
+
>>> format_time(t, format='full', tzinfo=get_timezone('Europe/Paris'),
|
| 802 |
+
... locale='fr_FR') # doctest: +SKIP
|
| 803 |
+
u'15:30:00 heure normale d\u2019Europe centrale'
|
| 804 |
+
>>> format_time(t, format='full', tzinfo=get_timezone('US/Eastern'),
|
| 805 |
+
... locale='en_US') # doctest: +SKIP
|
| 806 |
+
u'3:30:00\u202fPM Eastern Standard Time'
|
| 807 |
+
|
| 808 |
+
:param time: the ``time`` or ``datetime`` object; if `None`, the current
|
| 809 |
+
time in UTC is used
|
| 810 |
+
:param format: one of "full", "long", "medium", or "short", or a custom
|
| 811 |
+
date/time pattern
|
| 812 |
+
:param tzinfo: the time-zone to apply to the time for display
|
| 813 |
+
:param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
|
| 814 |
+
"""
|
| 815 |
+
|
| 816 |
+
# get reference date for if we need to find the right timezone variant
|
| 817 |
+
# in the pattern
|
| 818 |
+
ref_date = time.date() if isinstance(time, datetime.datetime) else None
|
| 819 |
+
|
| 820 |
+
time = _get_time(time, tzinfo)
|
| 821 |
+
|
| 822 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 823 |
+
if format in ('full', 'long', 'medium', 'short'):
|
| 824 |
+
format = get_time_format(format, locale=locale)
|
| 825 |
+
return parse_pattern(format).apply(time, locale, reference_date=ref_date)
|
| 826 |
+
|
| 827 |
+
|
| 828 |
+
def format_skeleton(
|
| 829 |
+
skeleton: str,
|
| 830 |
+
datetime: _Instant = None,
|
| 831 |
+
tzinfo: datetime.tzinfo | None = None,
|
| 832 |
+
fuzzy: bool = True,
|
| 833 |
+
locale: Locale | str | None = None,
|
| 834 |
+
) -> str:
|
| 835 |
+
r"""Return a time and/or date formatted according to the given pattern.
|
| 836 |
+
|
| 837 |
+
The skeletons are defined in the CLDR data and provide more flexibility
|
| 838 |
+
than the simple short/long/medium formats, but are a bit harder to use.
|
| 839 |
+
The are defined using the date/time symbols without order or punctuation
|
| 840 |
+
and map to a suitable format for the given locale.
|
| 841 |
+
|
| 842 |
+
>>> from datetime import datetime
|
| 843 |
+
>>> t = datetime(2007, 4, 1, 15, 30)
|
| 844 |
+
>>> format_skeleton('MMMEd', t, locale='fr')
|
| 845 |
+
u'dim. 1 avr.'
|
| 846 |
+
>>> format_skeleton('MMMEd', t, locale='en')
|
| 847 |
+
u'Sun, Apr 1'
|
| 848 |
+
>>> format_skeleton('yMMd', t, locale='fi') # yMMd is not in the Finnish locale; yMd gets used
|
| 849 |
+
u'1.4.2007'
|
| 850 |
+
>>> format_skeleton('yMMd', t, fuzzy=False, locale='fi') # yMMd is not in the Finnish locale, an error is thrown
|
| 851 |
+
Traceback (most recent call last):
|
| 852 |
+
...
|
| 853 |
+
KeyError: yMMd
|
| 854 |
+
>>> format_skeleton('GH', t, fuzzy=True, locale='fi_FI') # GH is not in the Finnish locale and there is no close match, an error is thrown
|
| 855 |
+
Traceback (most recent call last):
|
| 856 |
+
...
|
| 857 |
+
KeyError: None
|
| 858 |
+
|
| 859 |
+
After the skeleton is resolved to a pattern `format_datetime` is called so
|
| 860 |
+
all timezone processing etc is the same as for that.
|
| 861 |
+
|
| 862 |
+
:param skeleton: A date time skeleton as defined in the cldr data.
|
| 863 |
+
:param datetime: the ``time`` or ``datetime`` object; if `None`, the current
|
| 864 |
+
time in UTC is used
|
| 865 |
+
:param tzinfo: the time-zone to apply to the time for display
|
| 866 |
+
:param fuzzy: If the skeleton is not found, allow choosing a skeleton that's
|
| 867 |
+
close enough to it. If there is no close match, a `KeyError`
|
| 868 |
+
is thrown.
|
| 869 |
+
:param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
|
| 870 |
+
"""
|
| 871 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 872 |
+
if fuzzy and skeleton not in locale.datetime_skeletons:
|
| 873 |
+
skeleton = match_skeleton(skeleton, locale.datetime_skeletons)
|
| 874 |
+
format = locale.datetime_skeletons[skeleton]
|
| 875 |
+
return format_datetime(datetime, format, tzinfo, locale)
|
| 876 |
+
|
| 877 |
+
|
| 878 |
+
TIMEDELTA_UNITS: tuple[tuple[str, int], ...] = (
|
| 879 |
+
('year', 3600 * 24 * 365),
|
| 880 |
+
('month', 3600 * 24 * 30),
|
| 881 |
+
('week', 3600 * 24 * 7),
|
| 882 |
+
('day', 3600 * 24),
|
| 883 |
+
('hour', 3600),
|
| 884 |
+
('minute', 60),
|
| 885 |
+
('second', 1),
|
| 886 |
+
)
|
| 887 |
+
|
| 888 |
+
|
| 889 |
+
def format_timedelta(
|
| 890 |
+
delta: datetime.timedelta | int,
|
| 891 |
+
granularity: Literal['year', 'month', 'week', 'day', 'hour', 'minute', 'second'] = 'second',
|
| 892 |
+
threshold: float = .85,
|
| 893 |
+
add_direction: bool = False,
|
| 894 |
+
format: Literal['narrow', 'short', 'medium', 'long'] = 'long',
|
| 895 |
+
locale: Locale | str | None = None,
|
| 896 |
+
) -> str:
|
| 897 |
+
"""Return a time delta according to the rules of the given locale.
|
| 898 |
+
|
| 899 |
+
>>> from datetime import timedelta
|
| 900 |
+
>>> format_timedelta(timedelta(weeks=12), locale='en_US')
|
| 901 |
+
u'3 months'
|
| 902 |
+
>>> format_timedelta(timedelta(seconds=1), locale='es')
|
| 903 |
+
u'1 segundo'
|
| 904 |
+
|
| 905 |
+
The granularity parameter can be provided to alter the lowest unit
|
| 906 |
+
presented, which defaults to a second.
|
| 907 |
+
|
| 908 |
+
>>> format_timedelta(timedelta(hours=3), granularity='day', locale='en_US')
|
| 909 |
+
u'1 day'
|
| 910 |
+
|
| 911 |
+
The threshold parameter can be used to determine at which value the
|
| 912 |
+
presentation switches to the next higher unit. A higher threshold factor
|
| 913 |
+
means the presentation will switch later. For example:
|
| 914 |
+
|
| 915 |
+
>>> format_timedelta(timedelta(hours=23), threshold=0.9, locale='en_US')
|
| 916 |
+
u'1 day'
|
| 917 |
+
>>> format_timedelta(timedelta(hours=23), threshold=1.1, locale='en_US')
|
| 918 |
+
u'23 hours'
|
| 919 |
+
|
| 920 |
+
In addition directional information can be provided that informs
|
| 921 |
+
the user if the date is in the past or in the future:
|
| 922 |
+
|
| 923 |
+
>>> format_timedelta(timedelta(hours=1), add_direction=True, locale='en')
|
| 924 |
+
u'in 1 hour'
|
| 925 |
+
>>> format_timedelta(timedelta(hours=-1), add_direction=True, locale='en')
|
| 926 |
+
u'1 hour ago'
|
| 927 |
+
|
| 928 |
+
The format parameter controls how compact or wide the presentation is:
|
| 929 |
+
|
| 930 |
+
>>> format_timedelta(timedelta(hours=3), format='short', locale='en')
|
| 931 |
+
u'3 hr'
|
| 932 |
+
>>> format_timedelta(timedelta(hours=3), format='narrow', locale='en')
|
| 933 |
+
u'3h'
|
| 934 |
+
|
| 935 |
+
:param delta: a ``timedelta`` object representing the time difference to
|
| 936 |
+
format, or the delta in seconds as an `int` value
|
| 937 |
+
:param granularity: determines the smallest unit that should be displayed,
|
| 938 |
+
the value can be one of "year", "month", "week", "day",
|
| 939 |
+
"hour", "minute" or "second"
|
| 940 |
+
:param threshold: factor that determines at which point the presentation
|
| 941 |
+
switches to the next higher unit
|
| 942 |
+
:param add_direction: if this flag is set to `True` the return value will
|
| 943 |
+
include directional information. For instance a
|
| 944 |
+
positive timedelta will include the information about
|
| 945 |
+
it being in the future, a negative will be information
|
| 946 |
+
about the value being in the past.
|
| 947 |
+
:param format: the format, can be "narrow", "short" or "long". (
|
| 948 |
+
"medium" is deprecated, currently converted to "long" to
|
| 949 |
+
maintain compatibility)
|
| 950 |
+
:param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
|
| 951 |
+
"""
|
| 952 |
+
if format not in ('narrow', 'short', 'medium', 'long'):
|
| 953 |
+
raise TypeError('Format must be one of "narrow", "short" or "long"')
|
| 954 |
+
if format == 'medium':
|
| 955 |
+
warnings.warn(
|
| 956 |
+
'"medium" value for format param of format_timedelta'
|
| 957 |
+
' is deprecated. Use "long" instead',
|
| 958 |
+
category=DeprecationWarning,
|
| 959 |
+
stacklevel=2,
|
| 960 |
+
)
|
| 961 |
+
format = 'long'
|
| 962 |
+
if isinstance(delta, datetime.timedelta):
|
| 963 |
+
seconds = int((delta.days * 86400) + delta.seconds)
|
| 964 |
+
else:
|
| 965 |
+
seconds = delta
|
| 966 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 967 |
+
date_fields = locale._data["date_fields"]
|
| 968 |
+
unit_patterns = locale._data["unit_patterns"]
|
| 969 |
+
|
| 970 |
+
def _iter_patterns(a_unit):
|
| 971 |
+
if add_direction:
|
| 972 |
+
# Try to find the length variant version first ("year-narrow")
|
| 973 |
+
# before falling back to the default.
|
| 974 |
+
unit_rel_patterns = (date_fields.get(f"{a_unit}-{format}") or date_fields[a_unit])
|
| 975 |
+
if seconds >= 0:
|
| 976 |
+
yield unit_rel_patterns['future']
|
| 977 |
+
else:
|
| 978 |
+
yield unit_rel_patterns['past']
|
| 979 |
+
a_unit = f"duration-{a_unit}"
|
| 980 |
+
unit_pats = unit_patterns.get(a_unit, {})
|
| 981 |
+
yield unit_pats.get(format)
|
| 982 |
+
# We do not support `<alias>` tags at all while ingesting CLDR data,
|
| 983 |
+
# so these aliases specified in `root.xml` are hard-coded here:
|
| 984 |
+
# <unitLength type="long"><alias source="locale" path="../unitLength[@type='short']"/></unitLength>
|
| 985 |
+
# <unitLength type="narrow"><alias source="locale" path="../unitLength[@type='short']"/></unitLength>
|
| 986 |
+
if format in ("long", "narrow"):
|
| 987 |
+
yield unit_pats.get("short")
|
| 988 |
+
|
| 989 |
+
for unit, secs_per_unit in TIMEDELTA_UNITS:
|
| 990 |
+
value = abs(seconds) / secs_per_unit
|
| 991 |
+
if value >= threshold or unit == granularity:
|
| 992 |
+
if unit == granularity and value > 0:
|
| 993 |
+
value = max(1, value)
|
| 994 |
+
value = int(round(value))
|
| 995 |
+
plural_form = locale.plural_form(value)
|
| 996 |
+
pattern = None
|
| 997 |
+
for patterns in _iter_patterns(unit):
|
| 998 |
+
if patterns is not None:
|
| 999 |
+
pattern = patterns.get(plural_form) or patterns.get('other')
|
| 1000 |
+
if pattern:
|
| 1001 |
+
break
|
| 1002 |
+
# This really should not happen
|
| 1003 |
+
if pattern is None:
|
| 1004 |
+
return ''
|
| 1005 |
+
return pattern.replace('{0}', str(value))
|
| 1006 |
+
|
| 1007 |
+
return ''
|
| 1008 |
+
|
| 1009 |
+
|
| 1010 |
+
def _format_fallback_interval(
|
| 1011 |
+
start: _Instant,
|
| 1012 |
+
end: _Instant,
|
| 1013 |
+
skeleton: str | None,
|
| 1014 |
+
tzinfo: datetime.tzinfo | None,
|
| 1015 |
+
locale: Locale,
|
| 1016 |
+
) -> str:
|
| 1017 |
+
if skeleton in locale.datetime_skeletons: # Use the given skeleton
|
| 1018 |
+
format = lambda dt: format_skeleton(skeleton, dt, tzinfo, locale=locale)
|
| 1019 |
+
elif all((isinstance(d, datetime.date) and not isinstance(d, datetime.datetime)) for d in (start, end)): # Both are just dates
|
| 1020 |
+
format = lambda dt: format_date(dt, locale=locale)
|
| 1021 |
+
elif all((isinstance(d, datetime.time) and not isinstance(d, datetime.date)) for d in (start, end)): # Both are times
|
| 1022 |
+
format = lambda dt: format_time(dt, tzinfo=tzinfo, locale=locale)
|
| 1023 |
+
else:
|
| 1024 |
+
format = lambda dt: format_datetime(dt, tzinfo=tzinfo, locale=locale)
|
| 1025 |
+
|
| 1026 |
+
formatted_start = format(start)
|
| 1027 |
+
formatted_end = format(end)
|
| 1028 |
+
|
| 1029 |
+
if formatted_start == formatted_end:
|
| 1030 |
+
return format(start)
|
| 1031 |
+
|
| 1032 |
+
return (
|
| 1033 |
+
locale.interval_formats.get(None, "{0}-{1}").
|
| 1034 |
+
replace("{0}", formatted_start).
|
| 1035 |
+
replace("{1}", formatted_end)
|
| 1036 |
+
)
|
| 1037 |
+
|
| 1038 |
+
|
| 1039 |
+
def format_interval(
|
| 1040 |
+
start: _Instant,
|
| 1041 |
+
end: _Instant,
|
| 1042 |
+
skeleton: str | None = None,
|
| 1043 |
+
tzinfo: datetime.tzinfo | None = None,
|
| 1044 |
+
fuzzy: bool = True,
|
| 1045 |
+
locale: Locale | str | None = None,
|
| 1046 |
+
) -> str:
|
| 1047 |
+
"""
|
| 1048 |
+
Format an interval between two instants according to the locale's rules.
|
| 1049 |
+
|
| 1050 |
+
>>> from datetime import date, time
|
| 1051 |
+
>>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "yMd", locale="fi")
|
| 1052 |
+
u'15.\u201317.1.2016'
|
| 1053 |
+
|
| 1054 |
+
>>> format_interval(time(12, 12), time(16, 16), "Hm", locale="en_GB")
|
| 1055 |
+
'12:12\u201316:16'
|
| 1056 |
+
|
| 1057 |
+
>>> format_interval(time(5, 12), time(16, 16), "hm", locale="en_US")
|
| 1058 |
+
'5:12\u202fAM\u2009–\u20094:16\u202fPM'
|
| 1059 |
+
|
| 1060 |
+
>>> format_interval(time(16, 18), time(16, 24), "Hm", locale="it")
|
| 1061 |
+
'16:18\u201316:24'
|
| 1062 |
+
|
| 1063 |
+
If the start instant equals the end instant, the interval is formatted like the instant.
|
| 1064 |
+
|
| 1065 |
+
>>> format_interval(time(16, 18), time(16, 18), "Hm", locale="it")
|
| 1066 |
+
'16:18'
|
| 1067 |
+
|
| 1068 |
+
Unknown skeletons fall back to "default" formatting.
|
| 1069 |
+
|
| 1070 |
+
>>> format_interval(date(2015, 1, 1), date(2017, 1, 1), "wzq", locale="ja")
|
| 1071 |
+
'2015/01/01\uff5e2017/01/01'
|
| 1072 |
+
|
| 1073 |
+
>>> format_interval(time(16, 18), time(16, 24), "xxx", locale="ja")
|
| 1074 |
+
'16:18:00\uff5e16:24:00'
|
| 1075 |
+
|
| 1076 |
+
>>> format_interval(date(2016, 1, 15), date(2016, 1, 17), "xxx", locale="de")
|
| 1077 |
+
'15.01.2016\u2009–\u200917.01.2016'
|
| 1078 |
+
|
| 1079 |
+
:param start: First instant (datetime/date/time)
|
| 1080 |
+
:param end: Second instant (datetime/date/time)
|
| 1081 |
+
:param skeleton: The "skeleton format" to use for formatting.
|
| 1082 |
+
:param tzinfo: tzinfo to use (if none is already attached)
|
| 1083 |
+
:param fuzzy: If the skeleton is not found, allow choosing a skeleton that's
|
| 1084 |
+
close enough to it.
|
| 1085 |
+
:param locale: A locale object or identifier. Defaults to the system time locale.
|
| 1086 |
+
:return: Formatted interval
|
| 1087 |
+
"""
|
| 1088 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 1089 |
+
|
| 1090 |
+
# NB: The quote comments below are from the algorithm description in
|
| 1091 |
+
# https://www.unicode.org/reports/tr35/tr35-dates.html#intervalFormats
|
| 1092 |
+
|
| 1093 |
+
# > Look for the intervalFormatItem element that matches the "skeleton",
|
| 1094 |
+
# > starting in the current locale and then following the locale fallback
|
| 1095 |
+
# > chain up to, but not including root.
|
| 1096 |
+
|
| 1097 |
+
interval_formats = locale.interval_formats
|
| 1098 |
+
|
| 1099 |
+
if skeleton not in interval_formats or not skeleton:
|
| 1100 |
+
# > If no match was found from the previous step, check what the closest
|
| 1101 |
+
# > match is in the fallback locale chain, as in availableFormats. That
|
| 1102 |
+
# > is, this allows for adjusting the string value field's width,
|
| 1103 |
+
# > including adjusting between "MMM" and "MMMM", and using different
|
| 1104 |
+
# > variants of the same field, such as 'v' and 'z'.
|
| 1105 |
+
if skeleton and fuzzy:
|
| 1106 |
+
skeleton = match_skeleton(skeleton, interval_formats)
|
| 1107 |
+
else:
|
| 1108 |
+
skeleton = None
|
| 1109 |
+
if not skeleton: # Still no match whatsoever?
|
| 1110 |
+
# > Otherwise, format the start and end datetime using the fallback pattern.
|
| 1111 |
+
return _format_fallback_interval(start, end, skeleton, tzinfo, locale)
|
| 1112 |
+
|
| 1113 |
+
skel_formats = interval_formats[skeleton]
|
| 1114 |
+
|
| 1115 |
+
if start == end:
|
| 1116 |
+
return format_skeleton(skeleton, start, tzinfo, fuzzy=fuzzy, locale=locale)
|
| 1117 |
+
|
| 1118 |
+
start = _ensure_datetime_tzinfo(_get_datetime(start), tzinfo=tzinfo)
|
| 1119 |
+
end = _ensure_datetime_tzinfo(_get_datetime(end), tzinfo=tzinfo)
|
| 1120 |
+
|
| 1121 |
+
start_fmt = DateTimeFormat(start, locale=locale)
|
| 1122 |
+
end_fmt = DateTimeFormat(end, locale=locale)
|
| 1123 |
+
|
| 1124 |
+
# > If a match is found from previous steps, compute the calendar field
|
| 1125 |
+
# > with the greatest difference between start and end datetime. If there
|
| 1126 |
+
# > is no difference among any of the fields in the pattern, format as a
|
| 1127 |
+
# > single date using availableFormats, and return.
|
| 1128 |
+
|
| 1129 |
+
for field in PATTERN_CHAR_ORDER: # These are in largest-to-smallest order
|
| 1130 |
+
if field in skel_formats and start_fmt.extract(field) != end_fmt.extract(field):
|
| 1131 |
+
# > If there is a match, use the pieces of the corresponding pattern to
|
| 1132 |
+
# > format the start and end datetime, as above.
|
| 1133 |
+
return "".join(
|
| 1134 |
+
parse_pattern(pattern).apply(instant, locale)
|
| 1135 |
+
for pattern, instant
|
| 1136 |
+
in zip(skel_formats[field], (start, end))
|
| 1137 |
+
)
|
| 1138 |
+
|
| 1139 |
+
# > Otherwise, format the start and end datetime using the fallback pattern.
|
| 1140 |
+
|
| 1141 |
+
return _format_fallback_interval(start, end, skeleton, tzinfo, locale)
|
| 1142 |
+
|
| 1143 |
+
|
| 1144 |
+
def get_period_id(
|
| 1145 |
+
time: _Instant,
|
| 1146 |
+
tzinfo: datetime.tzinfo | None = None,
|
| 1147 |
+
type: Literal['selection'] | None = None,
|
| 1148 |
+
locale: Locale | str | None = None,
|
| 1149 |
+
) -> str:
|
| 1150 |
+
"""
|
| 1151 |
+
Get the day period ID for a given time.
|
| 1152 |
+
|
| 1153 |
+
This ID can be used as a key for the period name dictionary.
|
| 1154 |
+
|
| 1155 |
+
>>> from datetime import time
|
| 1156 |
+
>>> get_period_names(locale="de")[get_period_id(time(7, 42), locale="de")]
|
| 1157 |
+
u'Morgen'
|
| 1158 |
+
|
| 1159 |
+
>>> get_period_id(time(0), locale="en_US")
|
| 1160 |
+
u'midnight'
|
| 1161 |
+
|
| 1162 |
+
>>> get_period_id(time(0), type="selection", locale="en_US")
|
| 1163 |
+
u'night1'
|
| 1164 |
+
|
| 1165 |
+
:param time: The time to inspect.
|
| 1166 |
+
:param tzinfo: The timezone for the time. See ``format_time``.
|
| 1167 |
+
:param type: The period type to use. Either "selection" or None.
|
| 1168 |
+
The selection type is used for selecting among phrases such as
|
| 1169 |
+
“Your email arrived yesterday evening” or “Your email arrived last night”.
|
| 1170 |
+
:param locale: the `Locale` object, or a locale string. Defaults to the system time locale.
|
| 1171 |
+
:return: period ID. Something is always returned -- even if it's just "am" or "pm".
|
| 1172 |
+
"""
|
| 1173 |
+
time = _get_time(time, tzinfo)
|
| 1174 |
+
seconds_past_midnight = int(time.hour * 60 * 60 + time.minute * 60 + time.second)
|
| 1175 |
+
locale = Locale.parse(locale or LC_TIME)
|
| 1176 |
+
|
| 1177 |
+
# The LDML rules state that the rules may not overlap, so iterating in arbitrary
|
| 1178 |
+
# order should be alright, though `at` periods should be preferred.
|
| 1179 |
+
rulesets = locale.day_period_rules.get(type, {}).items()
|
| 1180 |
+
|
| 1181 |
+
for rule_id, rules in rulesets:
|
| 1182 |
+
for rule in rules:
|
| 1183 |
+
if "at" in rule and rule["at"] == seconds_past_midnight:
|
| 1184 |
+
return rule_id
|
| 1185 |
+
|
| 1186 |
+
for rule_id, rules in rulesets:
|
| 1187 |
+
for rule in rules:
|
| 1188 |
+
if "from" in rule and "before" in rule:
|
| 1189 |
+
if rule["from"] < rule["before"]:
|
| 1190 |
+
if rule["from"] <= seconds_past_midnight < rule["before"]:
|
| 1191 |
+
return rule_id
|
| 1192 |
+
else:
|
| 1193 |
+
# e.g. from="21:00" before="06:00"
|
| 1194 |
+
if rule["from"] <= seconds_past_midnight < 86400 or \
|
| 1195 |
+
0 <= seconds_past_midnight < rule["before"]:
|
| 1196 |
+
return rule_id
|
| 1197 |
+
|
| 1198 |
+
start_ok = end_ok = False
|
| 1199 |
+
|
| 1200 |
+
if "from" in rule and seconds_past_midnight >= rule["from"]:
|
| 1201 |
+
start_ok = True
|
| 1202 |
+
if "to" in rule and seconds_past_midnight <= rule["to"]:
|
| 1203 |
+
# This rule type does not exist in the present CLDR data;
|
| 1204 |
+
# excuse the lack of test coverage.
|
| 1205 |
+
end_ok = True
|
| 1206 |
+
if "before" in rule and seconds_past_midnight < rule["before"]:
|
| 1207 |
+
end_ok = True
|
| 1208 |
+
if "after" in rule:
|
| 1209 |
+
raise NotImplementedError("'after' is deprecated as of CLDR 29.")
|
| 1210 |
+
|
| 1211 |
+
if start_ok and end_ok:
|
| 1212 |
+
return rule_id
|
| 1213 |
+
|
| 1214 |
+
if seconds_past_midnight < 43200:
|
| 1215 |
+
return "am"
|
| 1216 |
+
else:
|
| 1217 |
+
return "pm"
|
| 1218 |
+
|
| 1219 |
+
|
| 1220 |
+
class ParseError(ValueError):
|
| 1221 |
+
pass
|
| 1222 |
+
|
| 1223 |
+
|
| 1224 |
+
def parse_date(
|
| 1225 |
+
string: str,
|
| 1226 |
+
locale: Locale | str | None = None,
|
| 1227 |
+
format: _PredefinedTimeFormat | str = 'medium',
|
| 1228 |
+
) -> datetime.date:
|
| 1229 |
+
"""Parse a date from a string.
|
| 1230 |
+
|
| 1231 |
+
If an explicit format is provided, it is used to parse the date.
|
| 1232 |
+
|
| 1233 |
+
>>> parse_date('01.04.2004', format='dd.MM.yyyy')
|
| 1234 |
+
datetime.date(2004, 4, 1)
|
| 1235 |
+
|
| 1236 |
+
If no format is given, or if it is one of "full", "long", "medium",
|
| 1237 |
+
or "short", the function first tries to interpret the string as
|
| 1238 |
+
ISO-8601 date format and then uses the date format for the locale
|
| 1239 |
+
as a hint to determine the order in which the date fields appear in
|
| 1240 |
+
the string.
|
| 1241 |
+
|
| 1242 |
+
>>> parse_date('4/1/04', locale='en_US')
|
| 1243 |
+
datetime.date(2004, 4, 1)
|
| 1244 |
+
>>> parse_date('01.04.2004', locale='de_DE')
|
| 1245 |
+
datetime.date(2004, 4, 1)
|
| 1246 |
+
>>> parse_date('2004-04-01', locale='en_US')
|
| 1247 |
+
datetime.date(2004, 4, 1)
|
| 1248 |
+
>>> parse_date('2004-04-01', locale='de_DE')
|
| 1249 |
+
datetime.date(2004, 4, 1)
|
| 1250 |
+
>>> parse_date('01.04.04', locale='de_DE', format='short')
|
| 1251 |
+
datetime.date(2004, 4, 1)
|
| 1252 |
+
|
| 1253 |
+
:param string: the string containing the date
|
| 1254 |
+
:param locale: a `Locale` object or a locale identifier
|
| 1255 |
+
:param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
|
| 1256 |
+
:param format: the format to use, either an explicit date format,
|
| 1257 |
+
or one of "full", "long", "medium", or "short"
|
| 1258 |
+
(see ``get_time_format``)
|
| 1259 |
+
"""
|
| 1260 |
+
numbers = re.findall(r'(\d+)', string)
|
| 1261 |
+
if not numbers:
|
| 1262 |
+
raise ParseError("No numbers were found in input")
|
| 1263 |
+
|
| 1264 |
+
use_predefined_format = format in ('full', 'long', 'medium', 'short')
|
| 1265 |
+
# we try ISO-8601 format first, meaning similar to formats
|
| 1266 |
+
# extended YYYY-MM-DD or basic YYYYMMDD
|
| 1267 |
+
iso_alike = re.match(r'^(\d{4})-?([01]\d)-?([0-3]\d)$',
|
| 1268 |
+
string, flags=re.ASCII) # allow only ASCII digits
|
| 1269 |
+
if iso_alike and use_predefined_format:
|
| 1270 |
+
try:
|
| 1271 |
+
return datetime.date(*map(int, iso_alike.groups()))
|
| 1272 |
+
except ValueError:
|
| 1273 |
+
pass # a locale format might fit better, so let's continue
|
| 1274 |
+
|
| 1275 |
+
if use_predefined_format:
|
| 1276 |
+
fmt = get_date_format(format=format, locale=locale)
|
| 1277 |
+
else:
|
| 1278 |
+
fmt = parse_pattern(format)
|
| 1279 |
+
format_str = fmt.pattern.lower()
|
| 1280 |
+
year_idx = format_str.index('y')
|
| 1281 |
+
month_idx = format_str.find('m')
|
| 1282 |
+
if month_idx < 0:
|
| 1283 |
+
month_idx = format_str.index('l')
|
| 1284 |
+
day_idx = format_str.index('d')
|
| 1285 |
+
|
| 1286 |
+
indexes = sorted([(year_idx, 'Y'), (month_idx, 'M'), (day_idx, 'D')])
|
| 1287 |
+
indexes = {item[1]: idx for idx, item in enumerate(indexes)}
|
| 1288 |
+
|
| 1289 |
+
# FIXME: this currently only supports numbers, but should also support month
|
| 1290 |
+
# names, both in the requested locale, and english
|
| 1291 |
+
|
| 1292 |
+
year = numbers[indexes['Y']]
|
| 1293 |
+
year = 2000 + int(year) if len(year) == 2 else int(year)
|
| 1294 |
+
month = int(numbers[indexes['M']])
|
| 1295 |
+
day = int(numbers[indexes['D']])
|
| 1296 |
+
if month > 12:
|
| 1297 |
+
month, day = day, month
|
| 1298 |
+
return datetime.date(year, month, day)
|
| 1299 |
+
|
| 1300 |
+
|
| 1301 |
+
def parse_time(
|
| 1302 |
+
string: str,
|
| 1303 |
+
locale: Locale | str | None = None,
|
| 1304 |
+
format: _PredefinedTimeFormat | str = 'medium',
|
| 1305 |
+
) -> datetime.time:
|
| 1306 |
+
"""Parse a time from a string.
|
| 1307 |
+
|
| 1308 |
+
This function uses the time format for the locale as a hint to determine
|
| 1309 |
+
the order in which the time fields appear in the string.
|
| 1310 |
+
|
| 1311 |
+
If an explicit format is provided, the function will use it to parse
|
| 1312 |
+
the time instead.
|
| 1313 |
+
|
| 1314 |
+
>>> parse_time('15:30:00', locale='en_US')
|
| 1315 |
+
datetime.time(15, 30)
|
| 1316 |
+
>>> parse_time('15:30:00', format='H:mm:ss')
|
| 1317 |
+
datetime.time(15, 30)
|
| 1318 |
+
|
| 1319 |
+
:param string: the string containing the time
|
| 1320 |
+
:param locale: a `Locale` object or a locale identifier. Defaults to the system time locale.
|
| 1321 |
+
:param format: the format to use, either an explicit time format,
|
| 1322 |
+
or one of "full", "long", "medium", or "short"
|
| 1323 |
+
(see ``get_time_format``)
|
| 1324 |
+
:return: the parsed time
|
| 1325 |
+
:rtype: `time`
|
| 1326 |
+
"""
|
| 1327 |
+
numbers = re.findall(r'(\d+)', string)
|
| 1328 |
+
if not numbers:
|
| 1329 |
+
raise ParseError("No numbers were found in input")
|
| 1330 |
+
|
| 1331 |
+
# TODO: try ISO format first?
|
| 1332 |
+
if format in ('full', 'long', 'medium', 'short'):
|
| 1333 |
+
fmt = get_time_format(format=format, locale=locale)
|
| 1334 |
+
else:
|
| 1335 |
+
fmt = parse_pattern(format)
|
| 1336 |
+
format_str = fmt.pattern.lower()
|
| 1337 |
+
hour_idx = format_str.find('h')
|
| 1338 |
+
if hour_idx < 0:
|
| 1339 |
+
hour_idx = format_str.index('k')
|
| 1340 |
+
min_idx = format_str.index('m')
|
| 1341 |
+
# format might not contain seconds
|
| 1342 |
+
if (sec_idx := format_str.find('s')) < 0:
|
| 1343 |
+
sec_idx = math.inf
|
| 1344 |
+
|
| 1345 |
+
indexes = sorted([(hour_idx, 'H'), (min_idx, 'M'), (sec_idx, 'S')])
|
| 1346 |
+
indexes = {item[1]: idx for idx, item in enumerate(indexes)}
|
| 1347 |
+
|
| 1348 |
+
# TODO: support time zones
|
| 1349 |
+
|
| 1350 |
+
# Check if the format specifies a period to be used;
|
| 1351 |
+
# if it does, look for 'pm' to figure out an offset.
|
| 1352 |
+
hour_offset = 0
|
| 1353 |
+
if 'a' in format_str and 'pm' in string.lower():
|
| 1354 |
+
hour_offset = 12
|
| 1355 |
+
|
| 1356 |
+
# Parse up to three numbers from the string.
|
| 1357 |
+
minute = second = 0
|
| 1358 |
+
hour = int(numbers[indexes['H']]) + hour_offset
|
| 1359 |
+
if len(numbers) > 1:
|
| 1360 |
+
minute = int(numbers[indexes['M']])
|
| 1361 |
+
if len(numbers) > 2:
|
| 1362 |
+
second = int(numbers[indexes['S']])
|
| 1363 |
+
return datetime.time(hour, minute, second)
|
| 1364 |
+
|
| 1365 |
+
|
| 1366 |
+
class DateTimePattern:
|
| 1367 |
+
|
| 1368 |
+
def __init__(self, pattern: str, format: DateTimeFormat):
|
| 1369 |
+
self.pattern = pattern
|
| 1370 |
+
self.format = format
|
| 1371 |
+
|
| 1372 |
+
def __repr__(self) -> str:
|
| 1373 |
+
return f"<{type(self).__name__} {self.pattern!r}>"
|
| 1374 |
+
|
| 1375 |
+
def __str__(self) -> str:
|
| 1376 |
+
pat = self.pattern
|
| 1377 |
+
return pat
|
| 1378 |
+
|
| 1379 |
+
def __mod__(self, other: DateTimeFormat) -> str:
|
| 1380 |
+
if not isinstance(other, DateTimeFormat):
|
| 1381 |
+
return NotImplemented
|
| 1382 |
+
return self.format % other
|
| 1383 |
+
|
| 1384 |
+
def apply(
|
| 1385 |
+
self,
|
| 1386 |
+
datetime: datetime.date | datetime.time,
|
| 1387 |
+
locale: Locale | str | None,
|
| 1388 |
+
reference_date: datetime.date | None = None,
|
| 1389 |
+
) -> str:
|
| 1390 |
+
return self % DateTimeFormat(datetime, locale, reference_date)
|
| 1391 |
+
|
| 1392 |
+
|
| 1393 |
+
class DateTimeFormat:
|
| 1394 |
+
|
| 1395 |
+
def __init__(
|
| 1396 |
+
self,
|
| 1397 |
+
value: datetime.date | datetime.time,
|
| 1398 |
+
locale: Locale | str,
|
| 1399 |
+
reference_date: datetime.date | None = None,
|
| 1400 |
+
) -> None:
|
| 1401 |
+
assert isinstance(value, (datetime.date, datetime.datetime, datetime.time))
|
| 1402 |
+
if isinstance(value, (datetime.datetime, datetime.time)) and value.tzinfo is None:
|
| 1403 |
+
value = value.replace(tzinfo=UTC)
|
| 1404 |
+
self.value = value
|
| 1405 |
+
self.locale = Locale.parse(locale)
|
| 1406 |
+
self.reference_date = reference_date
|
| 1407 |
+
|
| 1408 |
+
def __getitem__(self, name: str) -> str:
|
| 1409 |
+
char = name[0]
|
| 1410 |
+
num = len(name)
|
| 1411 |
+
if char == 'G':
|
| 1412 |
+
return self.format_era(char, num)
|
| 1413 |
+
elif char in ('y', 'Y', 'u'):
|
| 1414 |
+
return self.format_year(char, num)
|
| 1415 |
+
elif char in ('Q', 'q'):
|
| 1416 |
+
return self.format_quarter(char, num)
|
| 1417 |
+
elif char in ('M', 'L'):
|
| 1418 |
+
return self.format_month(char, num)
|
| 1419 |
+
elif char in ('w', 'W'):
|
| 1420 |
+
return self.format_week(char, num)
|
| 1421 |
+
elif char == 'd':
|
| 1422 |
+
return self.format(self.value.day, num)
|
| 1423 |
+
elif char == 'D':
|
| 1424 |
+
return self.format_day_of_year(num)
|
| 1425 |
+
elif char == 'F':
|
| 1426 |
+
return self.format_day_of_week_in_month()
|
| 1427 |
+
elif char in ('E', 'e', 'c'):
|
| 1428 |
+
return self.format_weekday(char, num)
|
| 1429 |
+
elif char in ('a', 'b', 'B'):
|
| 1430 |
+
return self.format_period(char, num)
|
| 1431 |
+
elif char == 'h':
|
| 1432 |
+
if self.value.hour % 12 == 0:
|
| 1433 |
+
return self.format(12, num)
|
| 1434 |
+
else:
|
| 1435 |
+
return self.format(self.value.hour % 12, num)
|
| 1436 |
+
elif char == 'H':
|
| 1437 |
+
return self.format(self.value.hour, num)
|
| 1438 |
+
elif char == 'K':
|
| 1439 |
+
return self.format(self.value.hour % 12, num)
|
| 1440 |
+
elif char == 'k':
|
| 1441 |
+
if self.value.hour == 0:
|
| 1442 |
+
return self.format(24, num)
|
| 1443 |
+
else:
|
| 1444 |
+
return self.format(self.value.hour, num)
|
| 1445 |
+
elif char == 'm':
|
| 1446 |
+
return self.format(self.value.minute, num)
|
| 1447 |
+
elif char == 's':
|
| 1448 |
+
return self.format(self.value.second, num)
|
| 1449 |
+
elif char == 'S':
|
| 1450 |
+
return self.format_frac_seconds(num)
|
| 1451 |
+
elif char == 'A':
|
| 1452 |
+
return self.format_milliseconds_in_day(num)
|
| 1453 |
+
elif char in ('z', 'Z', 'v', 'V', 'x', 'X', 'O'):
|
| 1454 |
+
return self.format_timezone(char, num)
|
| 1455 |
+
else:
|
| 1456 |
+
raise KeyError(f"Unsupported date/time field {char!r}")
|
| 1457 |
+
|
| 1458 |
+
def extract(self, char: str) -> int:
|
| 1459 |
+
char = str(char)[0]
|
| 1460 |
+
if char == 'y':
|
| 1461 |
+
return self.value.year
|
| 1462 |
+
elif char == 'M':
|
| 1463 |
+
return self.value.month
|
| 1464 |
+
elif char == 'd':
|
| 1465 |
+
return self.value.day
|
| 1466 |
+
elif char == 'H':
|
| 1467 |
+
return self.value.hour
|
| 1468 |
+
elif char == 'h':
|
| 1469 |
+
return self.value.hour % 12 or 12
|
| 1470 |
+
elif char == 'm':
|
| 1471 |
+
return self.value.minute
|
| 1472 |
+
elif char == 'a':
|
| 1473 |
+
return int(self.value.hour >= 12) # 0 for am, 1 for pm
|
| 1474 |
+
else:
|
| 1475 |
+
raise NotImplementedError(f"Not implemented: extracting {char!r} from {self.value!r}")
|
| 1476 |
+
|
| 1477 |
+
def format_era(self, char: str, num: int) -> str:
|
| 1478 |
+
width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)]
|
| 1479 |
+
era = int(self.value.year >= 0)
|
| 1480 |
+
return get_era_names(width, self.locale)[era]
|
| 1481 |
+
|
| 1482 |
+
def format_year(self, char: str, num: int) -> str:
|
| 1483 |
+
value = self.value.year
|
| 1484 |
+
if char.isupper():
|
| 1485 |
+
month = self.value.month
|
| 1486 |
+
if month == 1 and self.value.day < 7 and self.get_week_of_year() >= 52:
|
| 1487 |
+
value -= 1
|
| 1488 |
+
elif month == 12 and self.value.day > 25 and self.get_week_of_year() <= 2:
|
| 1489 |
+
value += 1
|
| 1490 |
+
year = self.format(value, num)
|
| 1491 |
+
if num == 2:
|
| 1492 |
+
year = year[-2:]
|
| 1493 |
+
return year
|
| 1494 |
+
|
| 1495 |
+
def format_quarter(self, char: str, num: int) -> str:
|
| 1496 |
+
quarter = (self.value.month - 1) // 3 + 1
|
| 1497 |
+
if num <= 2:
|
| 1498 |
+
return '%0*d' % (num, quarter)
|
| 1499 |
+
width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num]
|
| 1500 |
+
context = {'Q': 'format', 'q': 'stand-alone'}[char]
|
| 1501 |
+
return get_quarter_names(width, context, self.locale)[quarter]
|
| 1502 |
+
|
| 1503 |
+
def format_month(self, char: str, num: int) -> str:
|
| 1504 |
+
if num <= 2:
|
| 1505 |
+
return '%0*d' % (num, self.value.month)
|
| 1506 |
+
width = {3: 'abbreviated', 4: 'wide', 5: 'narrow'}[num]
|
| 1507 |
+
context = {'M': 'format', 'L': 'stand-alone'}[char]
|
| 1508 |
+
return get_month_names(width, context, self.locale)[self.value.month]
|
| 1509 |
+
|
| 1510 |
+
def format_week(self, char: str, num: int) -> str:
|
| 1511 |
+
if char.islower(): # week of year
|
| 1512 |
+
week = self.get_week_of_year()
|
| 1513 |
+
return self.format(week, num)
|
| 1514 |
+
else: # week of month
|
| 1515 |
+
week = self.get_week_of_month()
|
| 1516 |
+
return str(week)
|
| 1517 |
+
|
| 1518 |
+
def format_weekday(self, char: str = 'E', num: int = 4) -> str:
|
| 1519 |
+
"""
|
| 1520 |
+
Return weekday from parsed datetime according to format pattern.
|
| 1521 |
+
|
| 1522 |
+
>>> from datetime import date
|
| 1523 |
+
>>> format = DateTimeFormat(date(2016, 2, 28), Locale.parse('en_US'))
|
| 1524 |
+
>>> format.format_weekday()
|
| 1525 |
+
u'Sunday'
|
| 1526 |
+
|
| 1527 |
+
'E': Day of week - Use one through three letters for the abbreviated day name, four for the full (wide) name,
|
| 1528 |
+
five for the narrow name, or six for the short name.
|
| 1529 |
+
>>> format.format_weekday('E',2)
|
| 1530 |
+
u'Sun'
|
| 1531 |
+
|
| 1532 |
+
'e': Local day of week. Same as E except adds a numeric value that will depend on the local starting day of the
|
| 1533 |
+
week, using one or two letters. For this example, Monday is the first day of the week.
|
| 1534 |
+
>>> format.format_weekday('e',2)
|
| 1535 |
+
'01'
|
| 1536 |
+
|
| 1537 |
+
'c': Stand-Alone local day of week - Use one letter for the local numeric value (same as 'e'), three for the
|
| 1538 |
+
abbreviated day name, four for the full (wide) name, five for the narrow name, or six for the short name.
|
| 1539 |
+
>>> format.format_weekday('c',1)
|
| 1540 |
+
'1'
|
| 1541 |
+
|
| 1542 |
+
:param char: pattern format character ('e','E','c')
|
| 1543 |
+
:param num: count of format character
|
| 1544 |
+
|
| 1545 |
+
"""
|
| 1546 |
+
if num < 3:
|
| 1547 |
+
if char.islower():
|
| 1548 |
+
value = 7 - self.locale.first_week_day + self.value.weekday()
|
| 1549 |
+
return self.format(value % 7 + 1, num)
|
| 1550 |
+
num = 3
|
| 1551 |
+
weekday = self.value.weekday()
|
| 1552 |
+
width = {3: 'abbreviated', 4: 'wide', 5: 'narrow', 6: 'short'}[num]
|
| 1553 |
+
context = "stand-alone" if char == "c" else "format"
|
| 1554 |
+
return get_day_names(width, context, self.locale)[weekday]
|
| 1555 |
+
|
| 1556 |
+
def format_day_of_year(self, num: int) -> str:
|
| 1557 |
+
return self.format(self.get_day_of_year(), num)
|
| 1558 |
+
|
| 1559 |
+
def format_day_of_week_in_month(self) -> str:
|
| 1560 |
+
return str((self.value.day - 1) // 7 + 1)
|
| 1561 |
+
|
| 1562 |
+
def format_period(self, char: str, num: int) -> str:
|
| 1563 |
+
"""
|
| 1564 |
+
Return period from parsed datetime according to format pattern.
|
| 1565 |
+
|
| 1566 |
+
>>> from datetime import datetime, time
|
| 1567 |
+
>>> format = DateTimeFormat(time(13, 42), 'fi_FI')
|
| 1568 |
+
>>> format.format_period('a', 1)
|
| 1569 |
+
u'ip.'
|
| 1570 |
+
>>> format.format_period('b', 1)
|
| 1571 |
+
u'iltap.'
|
| 1572 |
+
>>> format.format_period('b', 4)
|
| 1573 |
+
u'iltapäivä'
|
| 1574 |
+
>>> format.format_period('B', 4)
|
| 1575 |
+
u'iltapäivällä'
|
| 1576 |
+
>>> format.format_period('B', 5)
|
| 1577 |
+
u'ip.'
|
| 1578 |
+
|
| 1579 |
+
>>> format = DateTimeFormat(datetime(2022, 4, 28, 6, 27), 'zh_Hant')
|
| 1580 |
+
>>> format.format_period('a', 1)
|
| 1581 |
+
u'上午'
|
| 1582 |
+
>>> format.format_period('B', 1)
|
| 1583 |
+
u'清晨'
|
| 1584 |
+
|
| 1585 |
+
:param char: pattern format character ('a', 'b', 'B')
|
| 1586 |
+
:param num: count of format character
|
| 1587 |
+
|
| 1588 |
+
"""
|
| 1589 |
+
widths = [{3: 'abbreviated', 4: 'wide', 5: 'narrow'}[max(3, num)],
|
| 1590 |
+
'wide', 'narrow', 'abbreviated']
|
| 1591 |
+
if char == 'a':
|
| 1592 |
+
period = 'pm' if self.value.hour >= 12 else 'am'
|
| 1593 |
+
context = 'format'
|
| 1594 |
+
else:
|
| 1595 |
+
period = get_period_id(self.value, locale=self.locale)
|
| 1596 |
+
context = 'format' if char == 'B' else 'stand-alone'
|
| 1597 |
+
for width in widths:
|
| 1598 |
+
period_names = get_period_names(context=context, width=width, locale=self.locale)
|
| 1599 |
+
if period in period_names:
|
| 1600 |
+
return period_names[period]
|
| 1601 |
+
raise ValueError(f"Could not format period {period} in {self.locale}")
|
| 1602 |
+
|
| 1603 |
+
def format_frac_seconds(self, num: int) -> str:
|
| 1604 |
+
""" Return fractional seconds.
|
| 1605 |
+
|
| 1606 |
+
Rounds the time's microseconds to the precision given by the number \
|
| 1607 |
+
of digits passed in.
|
| 1608 |
+
"""
|
| 1609 |
+
value = self.value.microsecond / 1000000
|
| 1610 |
+
return self.format(round(value, num) * 10**num, num)
|
| 1611 |
+
|
| 1612 |
+
def format_milliseconds_in_day(self, num):
|
| 1613 |
+
msecs = self.value.microsecond // 1000 + self.value.second * 1000 + \
|
| 1614 |
+
self.value.minute * 60000 + self.value.hour * 3600000
|
| 1615 |
+
return self.format(msecs, num)
|
| 1616 |
+
|
| 1617 |
+
def format_timezone(self, char: str, num: int) -> str:
|
| 1618 |
+
width = {3: 'short', 4: 'long', 5: 'iso8601'}[max(3, num)]
|
| 1619 |
+
|
| 1620 |
+
# It could be that we only receive a time to format, but also have a
|
| 1621 |
+
# reference date which is important to distinguish between timezone
|
| 1622 |
+
# variants (summer/standard time)
|
| 1623 |
+
value = self.value
|
| 1624 |
+
if self.reference_date:
|
| 1625 |
+
value = datetime.datetime.combine(self.reference_date, self.value)
|
| 1626 |
+
|
| 1627 |
+
if char == 'z':
|
| 1628 |
+
return get_timezone_name(value, width, locale=self.locale)
|
| 1629 |
+
elif char == 'Z':
|
| 1630 |
+
if num == 5:
|
| 1631 |
+
return get_timezone_gmt(value, width, locale=self.locale, return_z=True)
|
| 1632 |
+
return get_timezone_gmt(value, width, locale=self.locale)
|
| 1633 |
+
elif char == 'O':
|
| 1634 |
+
if num == 4:
|
| 1635 |
+
return get_timezone_gmt(value, width, locale=self.locale)
|
| 1636 |
+
# TODO: To add support for O:1
|
| 1637 |
+
elif char == 'v':
|
| 1638 |
+
return get_timezone_name(value.tzinfo, width,
|
| 1639 |
+
locale=self.locale)
|
| 1640 |
+
elif char == 'V':
|
| 1641 |
+
if num == 1:
|
| 1642 |
+
return get_timezone_name(value.tzinfo, width,
|
| 1643 |
+
uncommon=True, locale=self.locale)
|
| 1644 |
+
elif num == 2:
|
| 1645 |
+
return get_timezone_name(value.tzinfo, locale=self.locale, return_zone=True)
|
| 1646 |
+
elif num == 3:
|
| 1647 |
+
return get_timezone_location(value.tzinfo, locale=self.locale, return_city=True)
|
| 1648 |
+
return get_timezone_location(value.tzinfo, locale=self.locale)
|
| 1649 |
+
# Included additional elif condition to add support for 'Xx' in timezone format
|
| 1650 |
+
elif char == 'X':
|
| 1651 |
+
if num == 1:
|
| 1652 |
+
return get_timezone_gmt(value, width='iso8601_short', locale=self.locale,
|
| 1653 |
+
return_z=True)
|
| 1654 |
+
elif num in (2, 4):
|
| 1655 |
+
return get_timezone_gmt(value, width='short', locale=self.locale,
|
| 1656 |
+
return_z=True)
|
| 1657 |
+
elif num in (3, 5):
|
| 1658 |
+
return get_timezone_gmt(value, width='iso8601', locale=self.locale,
|
| 1659 |
+
return_z=True)
|
| 1660 |
+
elif char == 'x':
|
| 1661 |
+
if num == 1:
|
| 1662 |
+
return get_timezone_gmt(value, width='iso8601_short', locale=self.locale)
|
| 1663 |
+
elif num in (2, 4):
|
| 1664 |
+
return get_timezone_gmt(value, width='short', locale=self.locale)
|
| 1665 |
+
elif num in (3, 5):
|
| 1666 |
+
return get_timezone_gmt(value, width='iso8601', locale=self.locale)
|
| 1667 |
+
|
| 1668 |
+
def format(self, value: SupportsInt, length: int) -> str:
|
| 1669 |
+
return '%0*d' % (length, value)
|
| 1670 |
+
|
| 1671 |
+
def get_day_of_year(self, date: datetime.date | None = None) -> int:
|
| 1672 |
+
if date is None:
|
| 1673 |
+
date = self.value
|
| 1674 |
+
return (date - date.replace(month=1, day=1)).days + 1
|
| 1675 |
+
|
| 1676 |
+
def get_week_of_year(self) -> int:
|
| 1677 |
+
"""Return the week of the year."""
|
| 1678 |
+
day_of_year = self.get_day_of_year(self.value)
|
| 1679 |
+
week = self.get_week_number(day_of_year)
|
| 1680 |
+
if week == 0:
|
| 1681 |
+
date = datetime.date(self.value.year - 1, 12, 31)
|
| 1682 |
+
week = self.get_week_number(self.get_day_of_year(date),
|
| 1683 |
+
date.weekday())
|
| 1684 |
+
elif week > 52:
|
| 1685 |
+
weekday = datetime.date(self.value.year + 1, 1, 1).weekday()
|
| 1686 |
+
if self.get_week_number(1, weekday) == 1 and \
|
| 1687 |
+
32 - (weekday - self.locale.first_week_day) % 7 <= self.value.day:
|
| 1688 |
+
week = 1
|
| 1689 |
+
return week
|
| 1690 |
+
|
| 1691 |
+
def get_week_of_month(self) -> int:
|
| 1692 |
+
"""Return the week of the month."""
|
| 1693 |
+
return self.get_week_number(self.value.day)
|
| 1694 |
+
|
| 1695 |
+
def get_week_number(self, day_of_period: int, day_of_week: int | None = None) -> int:
|
| 1696 |
+
"""Return the number of the week of a day within a period. This may be
|
| 1697 |
+
the week number in a year or the week number in a month.
|
| 1698 |
+
|
| 1699 |
+
Usually this will return a value equal to or greater than 1, but if the
|
| 1700 |
+
first week of the period is so short that it actually counts as the last
|
| 1701 |
+
week of the previous period, this function will return 0.
|
| 1702 |
+
|
| 1703 |
+
>>> date = datetime.date(2006, 1, 8)
|
| 1704 |
+
>>> DateTimeFormat(date, 'de_DE').get_week_number(6)
|
| 1705 |
+
1
|
| 1706 |
+
>>> DateTimeFormat(date, 'en_US').get_week_number(6)
|
| 1707 |
+
2
|
| 1708 |
+
|
| 1709 |
+
:param day_of_period: the number of the day in the period (usually
|
| 1710 |
+
either the day of month or the day of year)
|
| 1711 |
+
:param day_of_week: the week day; if omitted, the week day of the
|
| 1712 |
+
current date is assumed
|
| 1713 |
+
"""
|
| 1714 |
+
if day_of_week is None:
|
| 1715 |
+
day_of_week = self.value.weekday()
|
| 1716 |
+
first_day = (day_of_week - self.locale.first_week_day -
|
| 1717 |
+
day_of_period + 1) % 7
|
| 1718 |
+
if first_day < 0:
|
| 1719 |
+
first_day += 7
|
| 1720 |
+
week_number = (day_of_period + first_day - 1) // 7
|
| 1721 |
+
if 7 - first_day >= self.locale.min_week_days:
|
| 1722 |
+
week_number += 1
|
| 1723 |
+
return week_number
|
| 1724 |
+
|
| 1725 |
+
|
| 1726 |
+
PATTERN_CHARS: dict[str, list[int] | None] = {
|
| 1727 |
+
'G': [1, 2, 3, 4, 5], # era
|
| 1728 |
+
'y': None, 'Y': None, 'u': None, # year
|
| 1729 |
+
'Q': [1, 2, 3, 4, 5], 'q': [1, 2, 3, 4, 5], # quarter
|
| 1730 |
+
'M': [1, 2, 3, 4, 5], 'L': [1, 2, 3, 4, 5], # month
|
| 1731 |
+
'w': [1, 2], 'W': [1], # week
|
| 1732 |
+
'd': [1, 2], 'D': [1, 2, 3], 'F': [1], 'g': None, # day
|
| 1733 |
+
'E': [1, 2, 3, 4, 5, 6], 'e': [1, 2, 3, 4, 5, 6], 'c': [1, 3, 4, 5, 6], # week day
|
| 1734 |
+
'a': [1, 2, 3, 4, 5], 'b': [1, 2, 3, 4, 5], 'B': [1, 2, 3, 4, 5], # period
|
| 1735 |
+
'h': [1, 2], 'H': [1, 2], 'K': [1, 2], 'k': [1, 2], # hour
|
| 1736 |
+
'm': [1, 2], # minute
|
| 1737 |
+
's': [1, 2], 'S': None, 'A': None, # second
|
| 1738 |
+
'z': [1, 2, 3, 4], 'Z': [1, 2, 3, 4, 5], 'O': [1, 4], 'v': [1, 4], # zone
|
| 1739 |
+
'V': [1, 2, 3, 4], 'x': [1, 2, 3, 4, 5], 'X': [1, 2, 3, 4, 5], # zone
|
| 1740 |
+
}
|
| 1741 |
+
|
| 1742 |
+
#: The pattern characters declared in the Date Field Symbol Table
|
| 1743 |
+
#: (https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table)
|
| 1744 |
+
#: in order of decreasing magnitude.
|
| 1745 |
+
PATTERN_CHAR_ORDER = "GyYuUQqMLlwWdDFgEecabBChHKkjJmsSAzZOvVXx"
|
| 1746 |
+
|
| 1747 |
+
|
| 1748 |
+
def parse_pattern(pattern: str | DateTimePattern) -> DateTimePattern:
|
| 1749 |
+
"""Parse date, time, and datetime format patterns.
|
| 1750 |
+
|
| 1751 |
+
>>> parse_pattern("MMMMd").format
|
| 1752 |
+
u'%(MMMM)s%(d)s'
|
| 1753 |
+
>>> parse_pattern("MMM d, yyyy").format
|
| 1754 |
+
u'%(MMM)s %(d)s, %(yyyy)s'
|
| 1755 |
+
|
| 1756 |
+
Pattern can contain literal strings in single quotes:
|
| 1757 |
+
|
| 1758 |
+
>>> parse_pattern("H:mm' Uhr 'z").format
|
| 1759 |
+
u'%(H)s:%(mm)s Uhr %(z)s'
|
| 1760 |
+
|
| 1761 |
+
An actual single quote can be used by using two adjacent single quote
|
| 1762 |
+
characters:
|
| 1763 |
+
|
| 1764 |
+
>>> parse_pattern("hh' o''clock'").format
|
| 1765 |
+
u"%(hh)s o'clock"
|
| 1766 |
+
|
| 1767 |
+
:param pattern: the formatting pattern to parse
|
| 1768 |
+
"""
|
| 1769 |
+
if isinstance(pattern, DateTimePattern):
|
| 1770 |
+
return pattern
|
| 1771 |
+
return _cached_parse_pattern(pattern)
|
| 1772 |
+
|
| 1773 |
+
|
| 1774 |
+
@lru_cache(maxsize=1024)
|
| 1775 |
+
def _cached_parse_pattern(pattern: str) -> DateTimePattern:
|
| 1776 |
+
result = []
|
| 1777 |
+
|
| 1778 |
+
for tok_type, tok_value in tokenize_pattern(pattern):
|
| 1779 |
+
if tok_type == "chars":
|
| 1780 |
+
result.append(tok_value.replace('%', '%%'))
|
| 1781 |
+
elif tok_type == "field":
|
| 1782 |
+
fieldchar, fieldnum = tok_value
|
| 1783 |
+
limit = PATTERN_CHARS[fieldchar]
|
| 1784 |
+
if limit and fieldnum not in limit:
|
| 1785 |
+
raise ValueError(f"Invalid length for field: {fieldchar * fieldnum!r}")
|
| 1786 |
+
result.append('%%(%s)s' % (fieldchar * fieldnum))
|
| 1787 |
+
else:
|
| 1788 |
+
raise NotImplementedError(f"Unknown token type: {tok_type}")
|
| 1789 |
+
return DateTimePattern(pattern, ''.join(result))
|
| 1790 |
+
|
| 1791 |
+
|
| 1792 |
+
def tokenize_pattern(pattern: str) -> list[tuple[str, str | tuple[str, int]]]:
|
| 1793 |
+
"""
|
| 1794 |
+
Tokenize date format patterns.
|
| 1795 |
+
|
| 1796 |
+
Returns a list of (token_type, token_value) tuples.
|
| 1797 |
+
|
| 1798 |
+
``token_type`` may be either "chars" or "field".
|
| 1799 |
+
|
| 1800 |
+
For "chars" tokens, the value is the literal value.
|
| 1801 |
+
|
| 1802 |
+
For "field" tokens, the value is a tuple of (field character, repetition count).
|
| 1803 |
+
|
| 1804 |
+
:param pattern: Pattern string
|
| 1805 |
+
:type pattern: str
|
| 1806 |
+
:rtype: list[tuple]
|
| 1807 |
+
"""
|
| 1808 |
+
result = []
|
| 1809 |
+
quotebuf = None
|
| 1810 |
+
charbuf = []
|
| 1811 |
+
fieldchar = ['']
|
| 1812 |
+
fieldnum = [0]
|
| 1813 |
+
|
| 1814 |
+
def append_chars():
|
| 1815 |
+
result.append(('chars', ''.join(charbuf).replace('\0', "'")))
|
| 1816 |
+
del charbuf[:]
|
| 1817 |
+
|
| 1818 |
+
def append_field():
|
| 1819 |
+
result.append(('field', (fieldchar[0], fieldnum[0])))
|
| 1820 |
+
fieldchar[0] = ''
|
| 1821 |
+
fieldnum[0] = 0
|
| 1822 |
+
|
| 1823 |
+
for char in pattern.replace("''", '\0'):
|
| 1824 |
+
if quotebuf is None:
|
| 1825 |
+
if char == "'": # quote started
|
| 1826 |
+
if fieldchar[0]:
|
| 1827 |
+
append_field()
|
| 1828 |
+
elif charbuf:
|
| 1829 |
+
append_chars()
|
| 1830 |
+
quotebuf = []
|
| 1831 |
+
elif char in PATTERN_CHARS:
|
| 1832 |
+
if charbuf:
|
| 1833 |
+
append_chars()
|
| 1834 |
+
if char == fieldchar[0]:
|
| 1835 |
+
fieldnum[0] += 1
|
| 1836 |
+
else:
|
| 1837 |
+
if fieldchar[0]:
|
| 1838 |
+
append_field()
|
| 1839 |
+
fieldchar[0] = char
|
| 1840 |
+
fieldnum[0] = 1
|
| 1841 |
+
else:
|
| 1842 |
+
if fieldchar[0]:
|
| 1843 |
+
append_field()
|
| 1844 |
+
charbuf.append(char)
|
| 1845 |
+
|
| 1846 |
+
elif quotebuf is not None:
|
| 1847 |
+
if char == "'": # end of quote
|
| 1848 |
+
charbuf.extend(quotebuf)
|
| 1849 |
+
quotebuf = None
|
| 1850 |
+
else: # inside quote
|
| 1851 |
+
quotebuf.append(char)
|
| 1852 |
+
|
| 1853 |
+
if fieldchar[0]:
|
| 1854 |
+
append_field()
|
| 1855 |
+
elif charbuf:
|
| 1856 |
+
append_chars()
|
| 1857 |
+
|
| 1858 |
+
return result
|
| 1859 |
+
|
| 1860 |
+
|
| 1861 |
+
def untokenize_pattern(tokens: Iterable[tuple[str, str | tuple[str, int]]]) -> str:
|
| 1862 |
+
"""
|
| 1863 |
+
Turn a date format pattern token stream back into a string.
|
| 1864 |
+
|
| 1865 |
+
This is the reverse operation of ``tokenize_pattern``.
|
| 1866 |
+
|
| 1867 |
+
:type tokens: Iterable[tuple]
|
| 1868 |
+
:rtype: str
|
| 1869 |
+
"""
|
| 1870 |
+
output = []
|
| 1871 |
+
for tok_type, tok_value in tokens:
|
| 1872 |
+
if tok_type == "field":
|
| 1873 |
+
output.append(tok_value[0] * tok_value[1])
|
| 1874 |
+
elif tok_type == "chars":
|
| 1875 |
+
if not any(ch in PATTERN_CHARS for ch in tok_value): # No need to quote
|
| 1876 |
+
output.append(tok_value)
|
| 1877 |
+
else:
|
| 1878 |
+
output.append("'%s'" % tok_value.replace("'", "''"))
|
| 1879 |
+
return "".join(output)
|
| 1880 |
+
|
| 1881 |
+
|
| 1882 |
+
def split_interval_pattern(pattern: str) -> list[str]:
|
| 1883 |
+
"""
|
| 1884 |
+
Split an interval-describing datetime pattern into multiple pieces.
|
| 1885 |
+
|
| 1886 |
+
> The pattern is then designed to be broken up into two pieces by determining the first repeating field.
|
| 1887 |
+
- https://www.unicode.org/reports/tr35/tr35-dates.html#intervalFormats
|
| 1888 |
+
|
| 1889 |
+
>>> split_interval_pattern(u'E d.M. \u2013 E d.M.')
|
| 1890 |
+
[u'E d.M. \u2013 ', 'E d.M.']
|
| 1891 |
+
>>> split_interval_pattern("Y 'text' Y 'more text'")
|
| 1892 |
+
["Y 'text '", "Y 'more text'"]
|
| 1893 |
+
>>> split_interval_pattern(u"E, MMM d \u2013 E")
|
| 1894 |
+
[u'E, MMM d \u2013 ', u'E']
|
| 1895 |
+
>>> split_interval_pattern("MMM d")
|
| 1896 |
+
['MMM d']
|
| 1897 |
+
>>> split_interval_pattern("y G")
|
| 1898 |
+
['y G']
|
| 1899 |
+
>>> split_interval_pattern(u"MMM d \u2013 d")
|
| 1900 |
+
[u'MMM d \u2013 ', u'd']
|
| 1901 |
+
|
| 1902 |
+
:param pattern: Interval pattern string
|
| 1903 |
+
:return: list of "subpatterns"
|
| 1904 |
+
"""
|
| 1905 |
+
|
| 1906 |
+
seen_fields = set()
|
| 1907 |
+
parts = [[]]
|
| 1908 |
+
|
| 1909 |
+
for tok_type, tok_value in tokenize_pattern(pattern):
|
| 1910 |
+
if tok_type == "field":
|
| 1911 |
+
if tok_value[0] in seen_fields: # Repeated field
|
| 1912 |
+
parts.append([])
|
| 1913 |
+
seen_fields.clear()
|
| 1914 |
+
seen_fields.add(tok_value[0])
|
| 1915 |
+
parts[-1].append((tok_type, tok_value))
|
| 1916 |
+
|
| 1917 |
+
return [untokenize_pattern(tokens) for tokens in parts]
|
| 1918 |
+
|
| 1919 |
+
|
| 1920 |
+
def match_skeleton(skeleton: str, options: Iterable[str], allow_different_fields: bool = False) -> str | None:
|
| 1921 |
+
"""
|
| 1922 |
+
Find the closest match for the given datetime skeleton among the options given.
|
| 1923 |
+
|
| 1924 |
+
This uses the rules outlined in the TR35 document.
|
| 1925 |
+
|
| 1926 |
+
>>> match_skeleton('yMMd', ('yMd', 'yMMMd'))
|
| 1927 |
+
'yMd'
|
| 1928 |
+
|
| 1929 |
+
>>> match_skeleton('yMMd', ('jyMMd',), allow_different_fields=True)
|
| 1930 |
+
'jyMMd'
|
| 1931 |
+
|
| 1932 |
+
>>> match_skeleton('yMMd', ('qyMMd',), allow_different_fields=False)
|
| 1933 |
+
|
| 1934 |
+
>>> match_skeleton('hmz', ('hmv',))
|
| 1935 |
+
'hmv'
|
| 1936 |
+
|
| 1937 |
+
:param skeleton: The skeleton to match
|
| 1938 |
+
:type skeleton: str
|
| 1939 |
+
:param options: An iterable of other skeletons to match against
|
| 1940 |
+
:type options: Iterable[str]
|
| 1941 |
+
:param allow_different_fields: Whether to allow a match that uses different fields
|
| 1942 |
+
than the skeleton requested.
|
| 1943 |
+
:type allow_different_fields: bool
|
| 1944 |
+
|
| 1945 |
+
:return: The closest skeleton match, or if no match was found, None.
|
| 1946 |
+
:rtype: str|None
|
| 1947 |
+
"""
|
| 1948 |
+
|
| 1949 |
+
# TODO: maybe implement pattern expansion?
|
| 1950 |
+
|
| 1951 |
+
# Based on the implementation in
|
| 1952 |
+
# https://github.com/unicode-org/icu/blob/main/icu4j/main/core/src/main/java/com/ibm/icu/text/DateIntervalInfo.java
|
| 1953 |
+
|
| 1954 |
+
# Filter out falsy values and sort for stability; when `interval_formats` is passed in, there may be a None key.
|
| 1955 |
+
options = sorted(option for option in options if option)
|
| 1956 |
+
|
| 1957 |
+
if 'z' in skeleton and not any('z' in option for option in options):
|
| 1958 |
+
skeleton = skeleton.replace('z', 'v')
|
| 1959 |
+
if 'k' in skeleton and not any('k' in option for option in options):
|
| 1960 |
+
skeleton = skeleton.replace('k', 'H')
|
| 1961 |
+
if 'K' in skeleton and not any('K' in option for option in options):
|
| 1962 |
+
skeleton = skeleton.replace('K', 'h')
|
| 1963 |
+
if 'a' in skeleton and not any('a' in option for option in options):
|
| 1964 |
+
skeleton = skeleton.replace('a', '')
|
| 1965 |
+
if 'b' in skeleton and not any('b' in option for option in options):
|
| 1966 |
+
skeleton = skeleton.replace('b', '')
|
| 1967 |
+
|
| 1968 |
+
get_input_field_width = dict(t[1] for t in tokenize_pattern(skeleton) if t[0] == "field").get
|
| 1969 |
+
best_skeleton = None
|
| 1970 |
+
best_distance = None
|
| 1971 |
+
for option in options:
|
| 1972 |
+
get_opt_field_width = dict(t[1] for t in tokenize_pattern(option) if t[0] == "field").get
|
| 1973 |
+
distance = 0
|
| 1974 |
+
for field in PATTERN_CHARS:
|
| 1975 |
+
input_width = get_input_field_width(field, 0)
|
| 1976 |
+
opt_width = get_opt_field_width(field, 0)
|
| 1977 |
+
if input_width == opt_width:
|
| 1978 |
+
continue
|
| 1979 |
+
if opt_width == 0 or input_width == 0:
|
| 1980 |
+
if not allow_different_fields: # This one is not okay
|
| 1981 |
+
option = None
|
| 1982 |
+
break
|
| 1983 |
+
distance += 0x1000 # Magic weight constant for "entirely different fields"
|
| 1984 |
+
elif field == 'M' and ((input_width > 2 and opt_width <= 2) or (input_width <= 2 and opt_width > 2)):
|
| 1985 |
+
distance += 0x100 # Magic weight for "text turns into a number"
|
| 1986 |
+
else:
|
| 1987 |
+
distance += abs(input_width - opt_width)
|
| 1988 |
+
|
| 1989 |
+
if not option: # We lost the option along the way (probably due to "allow_different_fields")
|
| 1990 |
+
continue
|
| 1991 |
+
|
| 1992 |
+
if not best_skeleton or distance < best_distance:
|
| 1993 |
+
best_skeleton = option
|
| 1994 |
+
best_distance = distance
|
| 1995 |
+
|
| 1996 |
+
if distance == 0: # Found a perfect match!
|
| 1997 |
+
break
|
| 1998 |
+
|
| 1999 |
+
return best_skeleton
|
rtme/lib/python3.10/site-packages/babel/locale-data/LICENSE.unicode
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
UNICODE LICENSE V3
|
| 2 |
+
|
| 3 |
+
COPYRIGHT AND PERMISSION NOTICE
|
| 4 |
+
|
| 5 |
+
Copyright © 2004-2025 Unicode, Inc.
|
| 6 |
+
|
| 7 |
+
NOTICE TO USER: Carefully read the following legal agreement. BY
|
| 8 |
+
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
| 9 |
+
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
| 10 |
+
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
| 11 |
+
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
| 12 |
+
|
| 13 |
+
Permission is hereby granted, free of charge, to any person obtaining a
|
| 14 |
+
copy of data files and any associated documentation (the "Data Files") or
|
| 15 |
+
software and any associated documentation (the "Software") to deal in the
|
| 16 |
+
Data Files or Software without restriction, including without limitation
|
| 17 |
+
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
| 18 |
+
copies of the Data Files or Software, and to permit persons to whom the
|
| 19 |
+
Data Files or Software are furnished to do so, provided that either (a)
|
| 20 |
+
this copyright and permission notice appear with all copies of the Data
|
| 21 |
+
Files or Software, or (b) this copyright and permission notice appear in
|
| 22 |
+
associated Documentation.
|
| 23 |
+
|
| 24 |
+
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
| 25 |
+
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 26 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
| 27 |
+
THIRD PARTY RIGHTS.
|
| 28 |
+
|
| 29 |
+
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
| 30 |
+
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
| 31 |
+
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
| 32 |
+
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
| 33 |
+
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
| 34 |
+
FILES OR SOFTWARE.
|
| 35 |
+
|
| 36 |
+
Except as contained in this notice, the name of a copyright holder shall
|
| 37 |
+
not be used in advertising or otherwise to promote the sale, use or other
|
| 38 |
+
dealings in these Data Files or Software without prior written
|
| 39 |
+
authorization of the copyright holder.
|
| 40 |
+
|
| 41 |
+
SPDX-License-Identifier: Unicode-3.0
|
rtme/lib/python3.10/site-packages/babel/locale-data/aa.dat
ADDED
|
Binary file (2.8 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/aa_DJ.dat
ADDED
|
Binary file (1.11 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/aa_ER.dat
ADDED
|
Binary file (637 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/aa_ET.dat
ADDED
|
Binary file (635 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ab.dat
ADDED
|
Binary file (95.3 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ab_GE.dat
ADDED
|
Binary file (635 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/af_NA.dat
ADDED
|
Binary file (1.45 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/af_ZA.dat
ADDED
|
Binary file (635 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/agq.dat
ADDED
|
Binary file (16.6 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/agq_CM.dat
ADDED
|
Binary file (636 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ak_GH.dat
ADDED
|
Binary file (616 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/am_ET.dat
ADDED
|
Binary file (635 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/an.dat
ADDED
|
Binary file (28.1 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/an_ES.dat
ADDED
|
Binary file (653 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ann.dat
ADDED
|
Binary file (737 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ann_NG.dat
ADDED
|
Binary file (617 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/apc.dat
ADDED
|
Binary file (1.56 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/apc_SY.dat
ADDED
|
Binary file (679 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ar_001.dat
ADDED
|
Binary file (1.71 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ar_AE.dat
ADDED
|
Binary file (982 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ar_BH.dat
ADDED
|
Binary file (720 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ar_DJ.dat
ADDED
|
Binary file (698 Bytes). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ar_DZ.dat
ADDED
|
Binary file (1.26 kB). View file
|
|
|
rtme/lib/python3.10/site-packages/babel/locale-data/ar_EH.dat
ADDED
|
Binary file (658 Bytes). View file
|
|
|