Repository: joke2k/faker
Pull request #1428 (2021-04-15 14:01:38)

Add city_name() and cities to support .city() for th_TH
### What does this changes

- Add list of cities and `city_name()` to support the `city()` in parent class.
- Add few more prefixes and suffixes for tambon

### What was wrong

`city()` was not get implemented before.

### How this fixes it

Implement `city()` using actual list of cities from 
https://th.wikipedia.org/wiki/%E0%B9%80%E0%B8%97%E0%B8%A8%E0%B8%9A%E0%B8%B2%E0%B8%A5%E0%B8%99%E0%B8%84%E0%B8%A3%E0%B9%83%E0%B8%99%E0%B8%9B%E0%B8%A3%E0%B8%B0%E0%B9%80%E0%B8%97%E0%B8%A8%E0%B9%84%E0%B8%97%E0%B8%A2



Patch:
diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 336b071b..5beaa135 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 8.1.0
+current_version = 8.1.4
 files = VERSION faker/__init__.py docs/conf.py
 commit = True
 tag = True
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf13eca7..59c8f483 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
 ## Changelog
 
+### [v8.1.4 - 2021-05-13](https://github.com/joke2k/faker/compare/v8.1.3...v8.1.4)
+
+* Support distant past dates on Windows. Thanks @prescod.
+
+### [v8.1.3 - 2021-05-10](https://github.com/joke2k/faker/compare/v8.1.2...v8.1.3)
+
+* Fixes #1439: Cast Path to a string for `pkgutil.iter_modules` (#1441). Thanks @fcurella.
+
+### [v8.1.2 - 2021-04-29](https://github.com/joke2k/faker/compare/8.1.1...v8.1.2)
+
+* Fix company suffix typo for ``th_TH``. Thanks @bact.
+
+### [v8.1.1 - 2021-04-23](https://github.com/joke2k/faker/compare/8.1.0...v8.1.1)
+
+* Remove ``gmail.co.uk`` from ``en_GB`` email Provider. Thanks @bact.
+
 ### [v8.1.0 - 2021-04-05](https://github.com/joke2k/faker/compare/8.0.0...v8.1.0)
 
 * Extend ``pl_PL`` Pesel To support greater range of dates. Thanks @IlfirinPL.
diff --git a/VERSION b/VERSION
index 8104cabd..ac2895c5 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-8.1.0
+8.1.4
diff --git a/docs/communityproviders.rst b/docs/communityproviders.rst
index e4cd0ff5..eecccb96 100644
--- a/docs/communityproviders.rst
+++ b/docs/communityproviders.rst
@@ -25,6 +25,13 @@ Here's a list of Providers written by the community:
 +---------------+--------------------------+----------------------------------+
 | Wi-Fi ESSID   | Fake Wi-Fi ESSIDs.       | `faker_wifi_essid`_              +
 +---------------+--------------------------+----------------------------------+
+| Airtravel     | Airport names, airport   | `faker_airtravel`_               |
+|               | codes, and flights.      |                                  |
++---------------+--------------------------+----------------------------------+
+| Optional      | Wrap over other          | `faker_optional`_                |
+|               | providers to return      |                                  |
+|               | their value or `None`.   |                                  |
++---------------+--------------------------+----------------------------------+
 
 If you want to add your own provider to this list, please submit a Pull Request to our `repo`_.
 
@@ -45,3 +52,5 @@ In order to be inlcuded, your provider must satisfy these requirement:
 .. _faker_microservice: https://pypi.org/project/faker-microservice/
 .. _faker_vehicle: https://pypi.org/project/faker-vehicle/
 .. _mdgen: https://pypi.org/project/mdgen/
+.. _faker_airtravel: https://pypi.org/project/faker_airtravel/
+.. _faker_optional: https://pypi.org/project/faker-optional
diff --git a/docs/conf.py b/docs/conf.py
index 57acbc2d..d44cde52 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,9 +51,9 @@ copyright = '2014, Daniele Faraglia'
 # built documents.
 #
 # The short X.Y version.
-version = '8.1.0'
+version = '8.1.4'
 # The full version, including alpha/beta/rc tags.
-release = '8.1.0'
+release = '8.1.4'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
diff --git a/faker/__init__.py b/faker/__init__.py
index 21ed63b7..07c92ade 100644
--- a/faker/__init__.py
+++ b/faker/__init__.py
@@ -2,6 +2,6 @@ from faker.factory import Factory
 from faker.generator import Generator
 from faker.proxy import Faker
 
-VERSION = '8.1.0'
+VERSION = '8.1.4'
 
 __all__ = ('Factory', 'Generator', 'Faker')
diff --git a/faker/providers/address/th_TH/__init__.py b/faker/providers/address/th_TH/__init__.py
index 043abfd3..ba7514dd 100644
--- a/faker/providers/address/th_TH/__init__.py
+++ b/faker/providers/address/th_TH/__init__.py
@@ -20,13 +20,54 @@ class Provider(AddressProvider):
         ("{{street_address}} {{tambon}} จ.{{province}} {{postcode}}", 15),
         ("{{street_address}} อ.{{amphoe}} จ.{{province}} {{postcode}}", 15),
         ("{{street_address}} ต.{{tambon}} จ.{{province}} {{postcode}}", 15),
+        ("{{street_address}} อำเภอ{{amphoe}} จังหวัด{{province}} {{postcode}}", 15),
+        ("{{street_address}} ตำบล{{tambon}} อำเภอ{{amphoe}} จังหวัด{{province}} {{postcode}}", 10),
         ("{{street_address}} {{province}} {{postcode}}", 15),
         ("{{street_address}} ต.{{tambon}} อ.{{amphoe}} {{province}}", 15),
         ("{{street_address}} ต.{{tambon}} อ.{{amphoe}} จ.{{province}}", 15),
+        ("{{street_address}} ตำบล{{tambon}} จังหวัด{{province}} {{postcode}}", 10),
         ("{{building_number}} ต.{{tambon}} อ.{{amphoe}} {{province}} {{postcode}}", 10),
         ("{{building_number}} หมู่บ้าน{{first_name}} {{amphoe}} {{province}} {{postcode}}", 10),
     ))
 
+    # city names are actual city municipalities in Thailand
+    # source: Wikipedia: https://th.wikipedia.org/wiki/เทศบาลนครในประเทศไทย
+    city_formats = ("{{city_name}}",)
+    cities = (
+        "กรุงเทพมหานคร",
+        "นนทบุรี",
+        "ปากเกร็ด",
+        "หาดใหญ่",
+        "เจ้าพระยาสุรศักดิ์",
+        "สุราษฎร์ธานี",
+        "อุดรธานี",
+        "เชียงใหม่",
+        "นครราชสีมา",
+        "พัทยา",
+        "ขอนแก่น",
+        "นครศรีธรรมราช",
+        "แหลมฉบัง",
+        "รังสิต",
+        "นครสวรรค์",
+        "ภูเก็ต",
+        "เชียงราย",
+        "อุบลราชธานี",
+        "นครปฐม",
+        "เกาะสมุย",
+        "สมุทรสาคร",
+        "พิษณุโลก",
+        "ระยอง",
+        "สงขลา",
+        "ยะลา",
+        "ตรัง",
+        "อ้อมน้อย",
+        "สกลนคร",
+        "ลำปาง",
+        "สมุทรปราการ",
+        "พระนครศรีอยุธยา",
+        "แม่สอด",
+    )
+
     building_number_formats = (
         "###",
         "##",
@@ -37,6 +78,7 @@ class Provider(AddressProvider):
         "##/##",
         "#/#",
         "## หมู่ #",
+        "## หมู่ ##",
     )
 
     street_prefixes = OrderedDict((
@@ -202,7 +244,7 @@ class Provider(AddressProvider):
         "ดอนสมบูรณ์",
         "หัวงัว",
         "นาเชือก",
-        "วัดเทพศิรินทร์",
+        "เทพศิรินทร์",
         "อุ่มเม่า",
         "คลองขาม",
         "บัวบาน",
@@ -216,7 +258,6 @@ class Provider(AddressProvider):
         "หนองอิเฒ่า",
         "โนนศิลา",
         "หนองปลาหมอ",
-        "บ้านหัน",
         "เปือยใหญ่",
         "โนนแดง",
         "ก้อนแก้ว",
@@ -240,7 +281,6 @@ class Provider(AddressProvider):
         "บางกุ้ง",
         "นาวง",
         "เขากอบ",
-        "ห้วยนาง",
         "เขาขาว",
         "ในเตา",
         "เขาปูน",
@@ -255,6 +295,16 @@ class Provider(AddressProvider):
         "นาหินลาด",
     )
 
+    tambon_prefixes = OrderedDict((
+        ("", 40),
+        ("วัด", 2),
+        ("บ้าน", 2),
+        ("บ่อ", 2),
+        ("บึง", 2),
+        ("ป่า", 1),
+        ("ห้วย", 1),
+    ))
+
     tambon_suffixes = OrderedDict((
         ("", 30),
         ("เหนือ", 3),
@@ -262,8 +312,12 @@ class Provider(AddressProvider):
         ("ใหญ่", 2),
         ("กลาง", 1),
         ("เล็ก", 1),
+        ("ใหม่", 1),
+        ("เดิม", 0.1),
     ))
 
+    city_suffixes = ("นคร",)
+
     def street_prefix(self):
         """
         :example 'ถนน'
@@ -292,4 +346,11 @@ class Provider(AddressProvider):
         Currently it's total random and not necessarily matched with an amphoe or province.
         :example 'ห้วยนาง'
         """
-        return self.random_element(self.tambons) + self.random_element(self.tambon_suffixes)
+        return (
+            self.random_element(self.tambon_prefixes)
+            + self.random_element(self.tambons)
+            + self.random_element(self.tambon_suffixes)
+        )
+
+    def city_name(self):
+        return self.random_element(self.cities)
diff --git a/faker/providers/company/th_TH/__init__.py b/faker/providers/company/th_TH/__init__.py
index 85e14979..9065385a 100644
--- a/faker/providers/company/th_TH/__init__.py
+++ b/faker/providers/company/th_TH/__init__.py
@@ -7,12 +7,14 @@ class Provider(CompanyProvider):
     formats = OrderedDict((
         ("{{company_limited_prefix}}{{last_name}} {{company_limited_suffix}}", 0.2),
         ("{{company_limited_prefix}}{{last_name}}{{company_suffix}} {{company_limited_suffix}}", 0.2),
+        ("{{company_limited_prefix}}{{last_name}} {{company_limited_suffix}}", 0.2),
         ("{{company_prefix}}{{last_name}}", 0.2),
         ("{{company_prefix}}{{last_name}}{{company_suffix}}", 0.2),
         ("{{last_name}}{{company_suffix}}", 0.1),
         ("{{nonprofit_prefix}}{{last_name}}", 0.1),
         ("{{last_name}}-{{last_name}}", 0.05),
         ("{{last_name}}และ{{last_name}}", 0.05),
+        ("{{company_limited_prefix}}{{last_name}}", 0.01),
     ))
 
     company_prefixes = OrderedDict((
@@ -21,6 +23,7 @@ class Provider(CompanyProvider):
         ("บจก.", 0.1),
         ("บมจ.", 0.1),
         ("ห้างหุ้นส่วนสามัญ ", 0.1),
+        ("หสน.", 0.01),
     ))
 
     nonprofit_prefixes = OrderedDict((
@@ -45,7 +48,7 @@ class Provider(CompanyProvider):
         "พืชผล",
         "เอเยนซี",
         "เอ็นจิเนียริ่ง",
-        "คอนสครัคชั่น",
+        "คอนสตรัคชั่น",
         "วิศวกรรม",
         "วิศวการ",
         "คอมพิวเตอร์",
@@ -68,13 +71,16 @@ class Provider(CompanyProvider):
         "บรรจุภัณฑ์",
         "แพคกิ้ง",
         "มอเตอร์",
+        "โอสถ",
+        "การบัญชี",
+        "สโตร์",
     )
 
     company_limited_prefixes = OrderedDict((
         ("บริษัท ", 0.95),
         ("ธนาคาร", 0.03),
-        ("บริษัทหลักทรัพย์ ", 0.01),
-        ("กองทุนรวม", 0.01),
+        ("บริษัทหลักทรัพย์ ", 0.005),
+        ("กองทุนรวม", 0.005),
     ))
 
     company_limited_suffixes = OrderedDict((
diff --git a/faker/providers/date_time/__init__.py b/faker/providers/date_time/__init__.py
index f5bc08e4..bbd297b6 100644
--- a/faker/providers/date_time/__init__.py
+++ b/faker/providers/date_time/__init__.py
@@ -19,10 +19,10 @@ def datetime_to_timestamp(dt):
 
 def timestamp_to_datetime(timestamp, tzinfo):
     if tzinfo is None:
-        pick = datetime.fromtimestamp(timestamp, tzlocal())
+        pick = convert_timestamp_to_datetime(timestamp, tzlocal())
         pick = pick.astimezone(tzutc()).replace(tzinfo=None)
     else:
-        pick = datetime.fromtimestamp(timestamp, tzinfo)
+        pick = convert_timestamp_to_datetime(timestamp, tzinfo)
 
     return pick
 
@@ -1697,8 +1697,11 @@ class Provider(BaseProvider):
         )
         try:
             if tzinfo is None:
-                pick = datetime.fromtimestamp(timestamp, tzlocal())
-                pick = pick.astimezone(tzutc()).replace(tzinfo=None)
+                pick = convert_timestamp_to_datetime(timestamp, tzlocal())
+                try:
+                    pick = pick.astimezone(tzutc()).replace(tzinfo=None)
+                except OSError:
+                    pass
             else:
                 pick = datetime.fromtimestamp(timestamp, tzinfo)
         except OverflowError:
@@ -2045,3 +2048,11 @@ class Provider(BaseProvider):
         dob = self.date_time_ad(tzinfo=tzinfo, start_datetime=start_date, end_datetime=end_date).date()
 
         return dob if dob != start_date else dob + timedelta(days=1)
+
+
+def convert_timestamp_to_datetime(timestamp, tzinfo):
+    import datetime as dt
+    if timestamp >= 0:
+        return dt.datetime.fromtimestamp(timestamp, tzinfo)
+    else:
+        return dt.datetime(1970, 1, 1, tzinfo=tzinfo) + dt.timedelta(seconds=int(timestamp))
diff --git a/faker/providers/internet/en_GB/__init__.py b/faker/providers/internet/en_GB/__init__.py
index c2b795ed..cbacdde2 100644
--- a/faker/providers/internet/en_GB/__init__.py
+++ b/faker/providers/internet/en_GB/__init__.py
@@ -9,9 +9,9 @@ class Provider(InternetProvider):
         'gmail.com',
         'yahoo.com',
         'hotmail.com',
-        'gmail.co.uk',
         'yahoo.co.uk',
         'hotmail.co.uk',
+        'outlook.com',
     )
 
     tlds = ('com', 'com', 'com', 'com', 'com', 'com', 'biz', 'info', 'net', 'org', 'co.uk')
diff --git a/faker/sphinx/documentor.py b/faker/sphinx/documentor.py
index e4e3aaa7..91873f86 100644
--- a/faker/sphinx/documentor.py
+++ b/faker/sphinx/documentor.py
@@ -1,6 +1,7 @@
 # coding=utf-8
 import importlib
 import inspect
+import os
 
 from pathlib import Path
 
@@ -8,7 +9,12 @@ from faker.config import AVAILABLE_LOCALES
 from faker.config import PROVIDERS as STANDARD_PROVIDER_NAMES
 from faker.providers import BaseProvider
 
-DOCS_ROOT = Path(__file__).resolve().parents[1] / 'docs'
+if os.environ.get("READTHEDOCS", False):
+    version = os.environ["READTHEDOCS_VERSION"]
+    HOME = Path("/home/docs/checkouts/readthedocs.org/user_builds/faker/checkouts") / version
+    DOCS_ROOT = HOME / "docs"
+else:
+    DOCS_ROOT = Path(__file__).resolve().parents[2] / 'docs'
 
 SECTION_ADORNMENTS = '#*=-~'
 
@@ -91,6 +97,7 @@ def _write_standard_provider_index():
 
 
 def _write_base_provider_docs():
+    (DOCS_ROOT / 'providers').mkdir(parents=True, exist_ok=True)
     with (DOCS_ROOT / 'providers' / 'baseprovider.rst').open('wb') as fh:
         _hide_edit_on_github(fh)
         _write_title(fh, '``faker.providers``')
@@ -102,8 +109,9 @@ def _write_base_provider_docs():
 
 
 def _write_standard_provider_docs():
+    (DOCS_ROOT / 'providers').mkdir(parents=True, exist_ok=True)
     for provider_name in STANDARD_PROVIDER_NAMES:
-        with (DOCS_ROOT / 'providers' / f'{provider_name}.rst') as fh:
+        with (DOCS_ROOT / 'providers' / f'{provider_name}.rst').open('wb') as fh:
             provider_class = f'{provider_name}.Provider'
             provider_methods = _get_provider_methods(provider_class)
             _hide_edit_on_github(fh)
@@ -126,6 +134,7 @@ def _write_localized_provider_index():
 
 
 def _write_localized_provider_docs():
+    (DOCS_ROOT / 'locales').mkdir(parents=True, exist_ok=True)
     for locale in AVAILABLE_LOCALES:
         info = _get_localized_provider_info(locale)
         with (DOCS_ROOT / 'locales' / '{}.rst'.format(locale)).open('wb') as fh:
@@ -142,6 +151,7 @@ def _write_localized_provider_docs():
 
 
 def write_provider_docs():
+    DOCS_ROOT.mkdir(parents=True, exist_ok=True)
     _write_standard_provider_index()
     _write_base_provider_docs()
     _write_standard_provider_docs()
diff --git a/faker/utils/loading.py b/faker/utils/loading.py
index 757a0125..e60db12e 100644
--- a/faker/utils/loading.py
+++ b/faker/utils/loading.py
@@ -32,7 +32,7 @@ def list_module(module: ModuleType) -> List[str]:
         # PyInstaller
         return [file.parent.name for file in Path(path).glob('*/__init__.py')]
     else:
-        return [name for _, name, is_pkg in pkgutil.iter_modules([path]) if is_pkg]
+        return [name for _, name, is_pkg in pkgutil.iter_modules([str(path)]) if is_pkg]
 
 
 def find_available_locales(providers: List[str]) -> List[str]:


Test patch:
diff --git a/.github/workflows/test-ubuntu.yml b/.github/workflows/test-ubuntu.yml
index 72816969..2d2a786a 100644
--- a/.github/workflows/test-ubuntu.yml
+++ b/.github/workflows/test-ubuntu.yml
@@ -1,9 +1,6 @@
 name: Python Tests
 
-on:
-  push:
-    tags-ignore:
-      - "*"
+on: [push]
 
 jobs:
   test_ubuntu:
diff --git a/tests/providers/test_address.py b/tests/providers/test_address.py
index a94ab78c..cc1272a2 100644
--- a/tests/providers/test_address.py
+++ b/tests/providers/test_address.py
@@ -1514,6 +1514,12 @@ class TestThTh:
             assert isinstance(country, str)
             assert country in ThThAddressProvider.countries
 
+    def test_city_name(self, faker, num_samples):
+        for _ in range(num_samples):
+            city = faker.city_name()
+            assert isinstance(city, str)
+            assert city in ThThAddressProvider.cities
+
     def test_province(self, faker, num_samples):
         for _ in range(num_samples):
             province = faker.province()
diff --git a/tests/providers/test_date_time.py b/tests/providers/test_date_time.py
index 45ae331f..c48bcd56 100644
--- a/tests/providers/test_date_time.py
+++ b/tests/providers/test_date_time.py
@@ -229,6 +229,10 @@ class TestDateTime(unittest.TestCase):
         assert date_start <= random_date
         assert date_end >= random_date
 
+    def test_date_time_between_long_past_dates(self):
+        random_date = self.fake.date_between("-100y", "-50y")
+        assert random_date
+
     def _datetime_to_time(self, value):
         return int(time.mktime(value.timetuple()))