ckadirt commited on
Commit
c99de0b
·
verified ·
1 Parent(s): a27d853

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. rtme/CACHEDIR.TAG +1 -0
  2. rtme/bin/accelerate +10 -0
  3. rtme/bin/accelerate-config +10 -0
  4. rtme/bin/accelerate-estimate-memory +10 -0
  5. rtme/bin/accelerate-launch +10 -0
  6. rtme/bin/activate +116 -0
  7. rtme/bin/activate.bat +71 -0
  8. rtme/bin/activate.csh +76 -0
  9. rtme/bin/activate.nu +117 -0
  10. rtme/bin/activate.ps1 +82 -0
  11. rtme/bin/activate_this.py +59 -0
  12. rtme/bin/convert-caffe2-to-onnx +10 -0
  13. rtme/bin/deactivate.bat +39 -0
  14. rtme/bin/debugpy +10 -0
  15. rtme/bin/diffusers-cli +10 -0
  16. rtme/bin/f2py +10 -0
  17. rtme/bin/fonttools +10 -0
  18. rtme/bin/huggingface-cli +10 -0
  19. rtme/bin/nib-roi +10 -0
  20. rtme/bin/nib-stats +10 -0
  21. rtme/bin/pybabel +10 -0
  22. rtme/bin/pydoc.bat +22 -0
  23. rtme/lib/python3.10/site-packages/babel/__init__.py +38 -0
  24. rtme/lib/python3.10/site-packages/babel/core.py +1337 -0
  25. rtme/lib/python3.10/site-packages/babel/dates.py +1999 -0
  26. rtme/lib/python3.10/site-packages/babel/locale-data/LICENSE.unicode +41 -0
  27. rtme/lib/python3.10/site-packages/babel/locale-data/aa.dat +0 -0
  28. rtme/lib/python3.10/site-packages/babel/locale-data/aa_DJ.dat +0 -0
  29. rtme/lib/python3.10/site-packages/babel/locale-data/aa_ER.dat +0 -0
  30. rtme/lib/python3.10/site-packages/babel/locale-data/aa_ET.dat +0 -0
  31. rtme/lib/python3.10/site-packages/babel/locale-data/ab.dat +0 -0
  32. rtme/lib/python3.10/site-packages/babel/locale-data/ab_GE.dat +0 -0
  33. rtme/lib/python3.10/site-packages/babel/locale-data/af_NA.dat +0 -0
  34. rtme/lib/python3.10/site-packages/babel/locale-data/af_ZA.dat +0 -0
  35. rtme/lib/python3.10/site-packages/babel/locale-data/agq.dat +0 -0
  36. rtme/lib/python3.10/site-packages/babel/locale-data/agq_CM.dat +0 -0
  37. rtme/lib/python3.10/site-packages/babel/locale-data/ak_GH.dat +0 -0
  38. rtme/lib/python3.10/site-packages/babel/locale-data/am_ET.dat +0 -0
  39. rtme/lib/python3.10/site-packages/babel/locale-data/an.dat +0 -0
  40. rtme/lib/python3.10/site-packages/babel/locale-data/an_ES.dat +0 -0
  41. rtme/lib/python3.10/site-packages/babel/locale-data/ann.dat +0 -0
  42. rtme/lib/python3.10/site-packages/babel/locale-data/ann_NG.dat +0 -0
  43. rtme/lib/python3.10/site-packages/babel/locale-data/apc.dat +0 -0
  44. rtme/lib/python3.10/site-packages/babel/locale-data/apc_SY.dat +0 -0
  45. rtme/lib/python3.10/site-packages/babel/locale-data/ar_001.dat +0 -0
  46. rtme/lib/python3.10/site-packages/babel/locale-data/ar_AE.dat +0 -0
  47. rtme/lib/python3.10/site-packages/babel/locale-data/ar_BH.dat +0 -0
  48. rtme/lib/python3.10/site-packages/babel/locale-data/ar_DJ.dat +0 -0
  49. rtme/lib/python3.10/site-packages/babel/locale-data/ar_DZ.dat +0 -0
  50. 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