مقدمه

لحظاتی مانده به بامداد ۲ فروردین ۱۴۰۲، در حال کار کردن با کامپیوتر یا گوشی‌هوشمند خودتان هستید و ناگهان شگفت‌زده می‌شوید. تلاش برای رفتن به سایت Time بی‌فایده است. عملکرد رمزسازهای OTP نیز با اختلال همراه شده. برعکس، شاید شما هم جزو چند درصد افراد خوش‌شانس بوده‌اید و متوجه تغییر یا اختلالی نمی‌شوید. اینها قسمتی از اتفاقات ناخوشایند بامداد روز گذشته بود. بنابراین تصمیم گرفتم راه‌حل مشکلات ناشی از لغو قانون ساعت تابستانی را تا حد امکان مستندسازی کنم.

لغو ساعت تابستانی و گوشی‌های هوشمند قدیمی

از زمان آخرین آپدیت گوشی اندرویدی‌تان بیشتر از ۵ سال گذشته است؟ خوشبختانه هنوز جای امیدواری وجود دارد! سیستم‌عامل اندروید اطلاعات منطقه‌های زمانی را در فایلی به نام tzdata در مسیر /system/usr/share/zoneinfo ذخیره می‌کند. نهاد IANA، پایگاه‌داده منطقه‌های زمانی را بر اساس تغییرات قوانین زمان، بروزرسانی و در اختیار همگان قرار می‌دهد. این تغییرات در نهایت توسط شرکت سازنده بر روی گوشی شما اعمال می‌شود، اما هنگامی که پشتیبانی از گوشی شما به پایان رسیده باشد اوضاع کمی متفاوت است و خودتان باید دست‌به‌کار شوید.

آموزش پیشِ‌رو بر روی نسخه‌های 6.0.1 و 4.4.4 اندروید امتحان شده است، اما از لحاظ تئوری و به دلیل یکسان بودن ساختار فایل tzdata در نسخه‌های 4.x تا 6.x اندروید نباید مشکلی در اجرای آن وجود داشته باشد. همچنین ساختار اطلاعات منطقه‌های زمانی در اندروید 7.x به بعد دچار تغییراتی شده است، بنابراین نمی‌توانم کارکرد درست این آموزش در نسخه‌های بعد از 7.x را تأیید یا رد کنم.

سلب مسئولیت

پیش از شروع، اگر تجربه قبلی از توسعه اندروید ندارید، توصیه می‌کنم از انجام آن خودداری کنید. زیرا احتمال Soft Brick یا حتی Hard Brick شدن گوشی شما وجود دارد. پس مسئولیت این‌کار کاملاً به عهده شماست.

آماده‌‌سازی محیط

برای شروع، به دسترسی روت نیاز داریم. در صورت استفاده از یک کاستوم ریکاوری مانند TWRP، با قابلیت خواندن و نوشتن بر روی فایل‌سیستم و عدم دسترسی روت در سیستم‌عامل اندروید، مانعی جهت انجام این روش وجود ندارد.

در ادامه از یک توزیع مبتنی بر Debian جهت اجرای دستورات استفاده شده است، اما استفاده از توزیع‌های دیگر نیز مانعی نداشته و روش انجام کار یکسان است.

ابتدا نصب بودن jdk بر روی کامپیوتر خود را با استفاده از دستور javac -version بررسی کنید.

sudo apt install openjdk-17-jdk # For Debian-based distros

سپس نصب بودن ابزار zic را با استفاده از دستور which zic بررسی کنید. اگر خروجی دستور چیزی مانند: /usr/sbin/zic بود می‌توانید قسمت بعدی را نادیده بگیرید، در غیراینصورت نیاز به کامپایل کردن ابزار zic داریم.

کامپایل کردن ابزار zic

نصب بودن gcc و make را بر روی کامپیوتر خود بررسی کنید.

sudo apt install make gcc # For Debian-based distros

سپس یک نسخه قدیمی از tzcode{version}.tar.gz را دانلود کنید. برای مثال در ادامه از نسخه 2016b استفاده کرده‌ایم. دلیل آن نیز سازگاری با نسخه‌های قدیمی اندروید است.

پس از دانلود نیاز به استخراج و در نهایت کامپایل zic داریم.

مراحل کامل را می‌توانید از طریق دستورات زیر انجام دهید:

mkdir tzcode && cd tzcode

wget https://data.iana.org/time-zones/releases/tzcode2016b.tar.gz

tar -xvf tzcode2016b.tar.gz

make zic

در صورت اجرای موفقیت‌آمیز دستور make، یک فایل اجرایی با نام zic خواهید داشت. سپس در Home Directory خود دایرکتوری جدیدی با نام newtz ساخته و فایل اجرایی zic را به آنجا منتقل کنید:

mkdir -p ~/newtz
mv -t ~/newtz zic

جهت فراخوانی ابزار zic در مرحله بعد باید به شکل زیر عمل کنید:

./zic 

کامپایل کردن پایگاه‌داده منطقه‌های زمانی

دریافت آخرین نسخه پایگاه‌داده منطقه‌های زمانی

ابتدا آخرین نسخه فایل tzdata{version}.tar.gz را از اینجا دانلود کنید. آخرین نسخه به هنگام نوشتن این پست، 2022g است.

در Home Directory خود یک دایرکتوری جدید با نام newtz ساخته و فایل دانلود شده را در آنجا استخراج کنید.

mkdir -p ~/newtz

cd ~/newtz

wget https://data.iana.org/time-zones/releases/tzdata2022g.tar.gz

tar -xvf tzdata2022g.tar.gz

rm -rf tzdata2022g.tar.gz

کامپایل کردن منطقه‌های زمانی

حال به قسمت کامپایل منطقه‌های زمانی با کمک ابزار zic می‌رسیم. فراموش نکنید که باید در دایرکتوری newtz باشید. سپس به شکل زیر عمل کنید:

# Be sure to be in the ~/newtz directory before running the command below

zic -d data africa antarctica asia australasia etcetera europe northamerica southamerica backward backzone

در صورت اجرای موفقیت‌آمیز دستور zic، یک دایرکتوری با نام ‍data حاوی منطقه‌های زمانی کامپایل شده به شکل زیر خواهید داشت:

Zic data directory

گردآوری فایل tzdata

دانلود و کامپایل ZoneCompactor

جهت تبدیل دایرکتوری data به یک فایل tzdata نیاز به برنامه‌ی جاوایی ‍ZoneCompactor.java داریم. به‌مانند قبل، پیش از اجرای دستورات باید در دایرکتوری newtz باشید.

نحوه دانلود و کامپایل ZoneCompactor.java به شکل زیر است:

# Be sure to be in the ~/newtz directory before running the commands below

# Getting ZoneCompactor.java
curl "https://android.googlesource.com/platform/bionic/+/refs/tags/android-6.0.1_r81/libc/tools/zoneinfo/ZoneCompactor.java?format=TEXT" | base64 -d > ZoneCompactor.java

# Compiling ZoneCompactor.java
javac ZoneCompactor.java

ساخت فایل setup

پیش از اجرای ZoneCompactor نیاز به مشخص کردن منطقه‌های زمانی موجود در فایل tzdata داریم. تایپ دستی این لیست کاری بیهوده است، پس اسکریپت پایتونی مربوط به این بخش را بازنویسی کرده‌ام تا با python3 سازگار شده و به صورت کاملاً خودکار یک لیست حاوی منطقه‌های زمانی برای شما بسازد. با یک ویرایشگر متنی مانند vim در دایرکتوری newtz فایلی با نام writeSetupFile.py ساخته و اسکریپت زیر را در آن جایگذاری کنید:

vim writeSetupFile.py
# Original credits:
# https://android.googlesource.com/platform/bionic/+/refs/tags/android-6.0.1_r81/libc/tools/zoneinfo/update-tzdata.py#49

from pathlib import Path

REGIONS = [
   "africa",
   "antarctica",
   "asia",
   "australasia",
   "etcetera",
   "europe",
   "northamerica",
   "southamerica",
   "backward",
   "backzone"
]

def write_setup_file():
   links, zones = [], []
   for region in REGIONS:
      file_path = Path.cwd() / f"{region}"
      with open(file_path, "r") as f:
         for line in f:
            fields = line.split()
            if fields:
               if fields[0] == "Link":
                  links.append(f"{fields[0]} {fields[1]} {fields[2]}")
                  zones.append(fields[2])
               elif fields[0] == "Zone":
                  zones.append(fields[1])
   setup_path = Path.cwd() / "setup"
   with open(setup_path, "w") as setup:
      [setup.write(f"{link}\n") for link in sorted(set(links))]
      [setup.write(f"{zone}\n") for zone in sorted(set(zones))]

def main():
   write_setup_file()

if __name__ == "__main__":
   main()

سپس آن را اجرا کنید:

python3 writeSetupFile.py

در صورت اجرای موفقیت آمیز اسکریپت، یک فایل خروجی حاوی اسم منطقه‌های زمانی با نام setup خواهید داشت.

مرحله نهایی و ساخت tzdata

در نهایت نوبت به اجرای ZoneCompactor و گردآوری فایل tzdata می‌رسد. نحوه فراخوانی ZoneCompactor به شکل زیر است:

java ZoneCompactor <setup file> <data directory> <zone.tab file> <output directory> <tzdata version>

و پارامترهای آن به این ترتیب هستند:

<setup file>: فایل خروجی مرحله ساخت فایل setup

<data directory>: آدرس دایرکتوری ساخته شده در مرحله کامپایل پایگاه‌داده منطقه‌های زمانی

<zone.tab file>: فایل zone.tab که در آرشیو tzdata قرار دارد

<output directory>: آدرس دایرکتوری خروجی فایل tzdata

<tzdata version>: نسخه فعلی پایگاه‌داده منطقه‌های زمانی که فرمت آن باید شبیه به tzdata{current_version} باشد. آخرین نسخه در زمان نوشتن این پست tzdata2022g است.

حال که کاربرد هر یک از پارامترها را متوجه شدیم آن را اجرا می‌کنیم، فراموش نکنید که پیش از اجرا، در دایرکتوری newtz یک دایرکتوری جدید به نام output نیز بسازید:

# Be sure to be in the ~/newtz directory before running the commands below

mkdir -p output

java ZoneCompactor setup data zone.tab output tzdata2022g

در صورت موفقیت‌آمیز بودن اجرا، فایل tzdata درون دایرکتوری output قابل دسترس است.

انتقال tzdata به گوشی

جهت انتقال و جایگزینی فایل جدید با فایل قدیمی راه‌های متعددی وجود دارد، در صورت داشتن دسترسی روت در سیستم‌عامل اندروید می‌توانید از یک Root Explorer استفاده کنید یا از طریق adb و دستور adb shell آن را جایگزین کنید. پیشنهاد می‌کنم این کار را از طریق بوت کردن یک کاستوم ریکاوری مانند TWRP و دستور adb shell انجام دهید. در ادامه نیز همین روش را پیش خواهیم برد.

برای شروع لازم است تا گوشی خودتان را در حالت ریکاوری بوت کنید. پس از آن گوشی را با کمک کابل به کامپیوتر خود متصل کنید.

در صورت نصب نبودن adb، می‌توانید با استفاده از دستور زیر آن را نصب کنید:

sudo apt install android-tools-adb

سپس با استفاده از دستور adb devices، درست شناسایی شدن گوشی خود توسط کامپیوتر را بررسی کرده و در ادامه با استفاده از دستور adb shell به شِل گوشی خود دسترسی پیدا کنید.

Mount بودن فایل‌سیستم را با استفاده از دستور df -h بررسی کرده و در صورت Mount نبودن دستور زیر را اجرا کنید:

mount /system

حال در یک تَب ترمینال دیگر با استفاده از دستور adb push، فایل tzdata ساخته شده را به حافظه داخلی گوشی خود انتقال دهید. توجه داشته باشید که ممکن است مسیر حافظه داخلی گوشی شما با مسیر استفاده شده در این آموزش، متفاوت باشد!

adb push ~/newtz/output/tzdata /sdcard/tzdata

به شِل گوشی برگشته و دستورات زیر را اجرا کنید:

cd /system/usr/share/zoneinfo # Change the working directory
mv tzdata tzdata.old # Backup the old tzdata in case something went wrong
mv /sdcard/tzdata tzdata # Move the new tzdata into the current directory
chmod 644 tzdata # Change the permissions

حال با وارد کردن دستور reboot در شِل گوشی، از حالت ریکاوری خارج شوید. پس از راه‌اندازی مجدد و بوت شدن، به تنظیمات زمان و تاریخ گوشی خود رفته و خودکار بودن منطقه زمانی و تاریخ‌و‌ساعت را بررسی کنید. اگر در حالت خودکار، منطقه زمانی دستگاه نمایانگر Iran Standard Time باشد، فرآیند جایگزینی و بروزرسانی با موفقیت انجام شده است.

اعمال موفقیت آمیز تغییرات منطقه زمانی

اشکال یابی

در صورتی که پس از اعمال تغییرات تازه، تمام منطقه‌های زمانی نمایانگر زمان +00:00 هستند و عملکرد گوشی شما در مواردی مانند شناسایی سیم‌کارت و شبکه دچار اختلال شده، می‌توانید راه‌های زیر را امتحان کنید:

دریافت فایل از پیش گردآوری شده tzdata

ابتدا فایل tzdata حاوی آخرین تغییرات منطقه‌های زمانی را از اینجا دریافت کنید. سپس آموزش را از قسمت انتقال tzdata به گوشی ادامه دهید.

چک کردن نسخه zic

آیا از ابزار zic درون سیستم‌عامل خود استفاده می‌کنید؟ این احتمال وجود دارد که ابزار zic سیستم‌عامل شما برای نسخه اندرویدتان بیش از اندازه جدید باشد. برای مثال نسخه 2.35 از ابزار zic که به صورت پیش‌فرض در Ubuntu 22.04 LTS وجود دارد، باعث ایجاد مشکل در برخی از گوشی‌های قدیمی شرکت Xiaomi که MIUI 10 را اجرا می‌کنند، می‌شود.

با اجرای دستور zic --version می‌توانید نسخه فعلی ابزار zic را مشاهده کنید. اگر نسخه نمایان شده بزرگ‌تر از 2.31 بود، می‌توانید آموزش را با یک نسخه قدیمی‌تر نیز امتحان کنید. برای دسترسی به نسخه‌های قدیمی‌تر به دو شکل می‌توان عمل کرد:

۱- کامپایل کردن یک نسخه قدیمی از ابزار zic

۲- استخراج ابزار zic از پکیج‌های قدیمی libc

در صورت استفاده از یک توزیع مبتنی بر Debian مانند Ubuntu، بهتر است از روش دوم استفاده کنید. ابتدا نسخه‌های قدیمی پکیج libc را از اینجا پیدا کرده و پکیج آن را مطابق با معماری پردازنده کامپیوتر خود دانلود کنید. برای مثال روند انجام این کار در یک کامپیوتر با پردازنده ۶۴بیتی و نسخه 2.31 از پکیج libc به شکل زیر است:

(پیش از شروع دایرکتوری خود را به newtz تغییر دهید)

# Be sure to be in the ~/newtz directory before running the commands below

wget http://security.ubuntu.com/ubuntu/pool/main/g/glibc/libc-bin_2.31-0ubuntu9.7_amd64.deb

dpkg-deb --fsys-tarfile libc-bin_2.31-0ubuntu9.7_amd64.deb | tar -x ./usr/sbin/zic && mv usr/sbin/zic zic

rm -rf usr

حال یک نسخه قابل‌اجرا از ابزار zic در دایرکتوری newtz خواهید داشت. پس از آن، آموزش را از قسمت کامپایل کردن منطقه‌های زمانی ادامه دهید و توجه کنید که باید دستور این قسمت را به شکل زیر فراخوانی کرده تا از ابزار zic موجود در دایرکتوری newtz جهت کامپایل منطقه‌های زمانی استفاده شود:

./zic -d data africa antarctica asia australasia etcetera europe northamerica southamerica backward backzone

پیش از اجرای دستور فوق بهتر است تا دایرکتوری data را حذف کنید.

بازگردانی تغییرات اعمال شده

در صورت موفقیت‌آمیز نبودن هیچ‌یک راه‌حل‌های مطرح شده، باید فایل قدیمی tzdata را بازگردانی کنید. این به معنای سازگار نبودن آموزش با نسخه اندروید گوشی شما یا استفاده شرکت سازنده دستگاه از روش‌های دیگر جهت ایجاد tzdata می‌باشد.

جهت بازگردانی فایل tzdata قدیمی، گوشی خود را مجدداً در حالت ریکاوری قرار داده و آن را با کمک کابل به کامپیوتر خود متصل کنید. با استفاده از دستور adb shell به شِل گوشی خود دسترسی پیدا کنید. سپس Mount بودن فایل‌سیستم را بررسی کرده و دستورات زیر را اجرا کنید:

cd /system/usr/share/zoneinfo
rm -rf tzdata
mv tzdata.old tzdata
reboot

سخن پایانی

این پست به‌مرور زمان و در مواجهه با چالش‌های تازه یا بهبود راه‌حل‌ها، به‌روزرسانی خواهد شد.