ฉันจะรับ Cron เช่น scheduler ใน Python ได้อย่างไร [ปิด]


348

ฉันกำลังมองหาห้องสมุดใน Python ที่จะให้atและcronชอบฟังก์ชั่น

ฉันอยากได้โซลูชัน Python ที่บริสุทธิ์แทนที่จะใช้เครื่องมือที่ติดตั้งลงในกล่อง วิธีนี้ฉันทำงานบนเครื่องที่ไม่มี cron

สำหรับผู้ที่ไม่คุ้นเคยcron: คุณสามารถกำหนดเวลางานตามนิพจน์เช่น:

 0 2 * * 7 /usr/bin/run-backup # run the backups at 0200 on Every Sunday
 0 9-17/2 * * 1-5 /usr/bin/purge-temps # run the purge temps command, every 2 hours between 9am and 5pm on Mondays to Fridays.

ไวยากรณ์การแสดงออกเวลา cron มีความสำคัญน้อยกว่า แต่ฉันต้องการที่จะมีบางสิ่งบางอย่างที่มีความยืดหยุ่นเช่นนี้

หากไม่มีสิ่งใดที่ทำสิ่งนี้ให้ฉันนอกกรอบคำแนะนำใด ๆ สำหรับการสร้างบล็อคเพื่อทำบางสิ่งเช่นนี้จะได้รับสุดซึ้ง

แก้ไข ฉันไม่สนใจที่จะเปิดตัวกระบวนการเพียงแค่ "งาน" ยังเขียนในฟังก์ชั่น Python - python ตามความจำเป็นฉันคิดว่านี่จะเป็นเธรดอื่น แต่ไม่ใช่ในกระบวนการที่แตกต่างกัน

ด้วยเหตุนี้ฉันกำลังมองหาการแสดงออกของเวลา cron แต่ใน Python

Cron ได้รับรอบปี แต่ฉันพยายามที่จะเป็นแบบพกพาที่เป็นไปได้ ฉันไม่สามารถเชื่อถือได้


1
ฉันต้องการทราบวิธีการทำเช่นนี้ มันจะมีประโยชน์มากกว่าที่จะมีโซลูชันข้ามแพลตฟอร์มมากกว่าพึ่งพาส่วนประกอบเฉพาะของแพลตฟอร์ม
ฌอน

7
นี่ไม่ใช่นอกหัวข้อนี่เป็นคำถามที่สำคัญและมีประโยชน์มาก
Connor

คำตอบ:


571

หากคุณกำลังมองหาตารางการชำระเงินที่มีน้ำหนักเบา:

import schedule
import time

def job():
    print("I'm working...")

schedule.every(10).minutes.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("10:30").do(job)

while 1:
    schedule.run_pending()
    time.sleep(1)

การเปิดเผยข้อมูล : ฉันเป็นผู้แต่งห้องสมุดนั้น


7
scheduleคุณควรจะพูดถึงว่าคุณดูแลของ มันทำงานได้ดีสำหรับฉัน มันจะดีกว่านี้ถ้ามันมี cron เช่นซินแท็กซ์และมัณฑนากรที่ได้รับการสนับสนุน (ดู crython แต่อย่าใช้ไลบรารี่นี้เพราะมันใช้งานไม่ได้;
Tim Ludwinski

23
มีวิธีส่งพารามิเตอร์ให้กับงานหรือไม่? ฉันต้องการทำอะไรเช่นนี้: schedule.every (). hour.do (job (myParam))
Zen Skunkworx

5
sched.every (). hour.do (งาน) นี้ทำงานทุกชั่วโมงหรือไม่ เช่นเดียวกับ 01:00, 02:00, 03:00 ฯลฯ แม้ว่าเวลาเริ่มต้นจะไม่เต็มชั่วโมงหรือไม่
swateek

1
สมมติว่ารหัสนี้อยู่ใน scheduler.py รหัสนี้จะทำงานโดยอัตโนมัติ?
Kishan

26
@ Darrel-โฮลท์และ @ เซน skunkworx: ในdo()ฟังก์ชั่นการส่งต่อข้อโต้แย้งพิเศษที่คุณส่งผ่านไปยังไปยังฟังก์ชั่นงาน: schedule.readthedocs.io/en/stable/api.html#schedule.Job.doตัวอย่างเช่นคุณสามารถทำเช่นนี้ : schedule.every().hour.do(job, param1, param2)ไม่จำเป็นต้องใช้แลมบ์ดา หวังว่านี่จะช่วยได้ :)
dbader

65

คุณสามารถใช้อาร์กิวเมนต์ Python ปกติผ่านไวยากรณ์เพื่อระบุ crontab ของคุณ ตัวอย่างเช่นสมมติว่าเรากำหนดคลาสกิจกรรมดังต่อไปนี้:

from datetime import datetime, timedelta
import time

# Some utility classes / functions first
class AllMatch(set):
    """Universal set - match everything"""
    def __contains__(self, item): return True

allMatch = AllMatch()

def conv_to_set(obj):  # Allow single integer to be provided
    if isinstance(obj, (int,long)):
        return set([obj])  # Single item
    if not isinstance(obj, set):
        obj = set(obj)
    return obj

# The actual Event class
class Event(object):
    def __init__(self, action, min=allMatch, hour=allMatch, 
                       day=allMatch, month=allMatch, dow=allMatch, 
                       args=(), kwargs={}):
        self.mins = conv_to_set(min)
        self.hours= conv_to_set(hour)
        self.days = conv_to_set(day)
        self.months = conv_to_set(month)
        self.dow = conv_to_set(dow)
        self.action = action
        self.args = args
        self.kwargs = kwargs

    def matchtime(self, t):
        """Return True if this event should trigger at the specified datetime"""
        return ((t.minute     in self.mins) and
                (t.hour       in self.hours) and
                (t.day        in self.days) and
                (t.month      in self.months) and
                (t.weekday()  in self.dow))

    def check(self, t):
        if self.matchtime(t):
            self.action(*self.args, **self.kwargs)

(หมายเหตุ: ไม่ผ่านการทดสอบอย่างละเอียด)

จากนั้น CronTab ของคุณสามารถระบุได้ในไวยากรณ์ python ปกติดังนี้:

c = CronTab(
  Event(perform_backup, 0, 2, dow=6 ),
  Event(purge_temps, 0, range(9,18,2), dow=range(0,5))
)

วิธีนี้ทำให้คุณได้รับพลังอย่างเต็มที่จากกลไกการโต้แย้งของ Python (การผสมเนื้อหาตำแหน่งและคำหลักและสามารถใช้ชื่อสัญลักษณ์สำหรับชื่อของสัปดาห์และเดือน)

คลาส CronTab จะถูกกำหนดเป็นเพียงแค่การนอนหลับเพิ่มขึ้นในนาทีและเรียกตรวจสอบ () ในแต่ละเหตุการณ์ (อาจมีรายละเอียดปลีกย่อยบางส่วนที่มีเวลา / เขตเวลากลางวันที่ต้องระวัง) นี่คือการดำเนินการอย่างรวดเร็ว:

class CronTab(object):
    def __init__(self, *events):
        self.events = events

    def run(self):
        t=datetime(*datetime.now().timetuple()[:5])
        while 1:
            for e in self.events:
                e.check(t)

            t += timedelta(minutes=1)
            while datetime.now() < t:
                time.sleep((t - datetime.now()).seconds)

บางสิ่งที่ควรทราบ: วันธรรมดา / เดือนของงูใหญ่เป็นศูนย์ดัชนี (ซึ่งแตกต่างจาก cron) และช่วงนั้นไม่รวมองค์ประกอบสุดท้ายดังนั้นไวยากรณ์เช่น "1-5" จะกลายเป็นช่วง (0,5) - เช่น [0,1,2, 3,4] ถ้าคุณชอบไวยากรณ์ของ cron การแยกมันไม่ควรยากเกินไป


คุณอาจต้องการเพิ่มข้อความสั่งการนำเข้าบางรายการสำหรับผู้ที่ไม่มีประสบการณ์ ฉันลงเอยด้วยการใส่คลาสทั้งหมดในไฟล์เดียวด้วยจากการนำเข้า datetime * จากเวลาพักการนำเข้าและเปลี่ยน time.sleep เป็น sleep นีซโซลูชั่นที่สง่างามเรียบง่าย ขอบคุณ
mavnn

1
แค่สงสัยว่าทำไมสิ่งนี้ถึงเป็นที่นิยมใน Kronos เป็นตารางที่รถ (ตั้งแต่ kronos ใช้ตารางเวลา)? หรือนี่มันล้าสมัยไปแล้วเหรอ?
cregox

ขอบคุณไบรอันฉันใช้โซลูชันของคุณในการผลิตและมันใช้งานได้ดี อย่างไรก็ตามอย่างที่คนอื่น ๆ ชี้ว่ามีข้อผิดพลาดเล็กน้อยในโค้ดการทำงานของคุณ ฉันก็พบว่ามันซับซ้อนเกินไปสำหรับความต้องการ
raph.amiard

1
นี่มันเจ๋ง แต่ก็ยังไม่สนับสนุนสัญกรณ์สแลชสำหรับการดำเนินการทุกชั่วโมงนาที ฯลฯ ..
Chris Koston

1
ความคิดที่ยอดเยี่ยมในการเขียนชั้นเรียนของคุณเองเช่นเมื่อฉันไม่สามารถเข้าถึง sudo บนเซิร์ฟเวอร์ได้จึงไม่สามารถทำได้pip install anything:)
Cometsong


27

สิ่งหนึ่งที่ในการค้นหาของฉันที่ฉันเห็นคือschedโมดูลของงูใหญ่ซึ่งอาจเป็นประเภทของสิ่งที่คุณกำลังมองหา


11
ตอนนี้กำหนดการมี enterabs () ซึ่งทำการตั้งเวลาแน่นอน
Jerther

5
ฉันคาดหวังว่านี่จะเป็นคำตอบที่ต้องการเนื่องจากการกำหนดเวลาเป็นส่วนหนึ่งของ python2 และ 3 stdlib ในขณะนี้และทำการตั้งเวลาแน่นอน
Michael

20

"... โมดูล Crontab สำหรับการอ่านและการเขียนไฟล์ crontab และการเข้าถึงระบบ cron โดยอัตโนมัติและใช้ API โดยตรง ... "

http://pypi.python.org/pypi/python-crontab

และ APScheduler ซึ่งเป็นแพ็คเกจไพ ธ อน เขียนแล้ว & ดีบั๊ก

http://packages.python.org/APScheduler/cronschedule.html


1
OP ขอเฉพาะบางสิ่งบางอย่างที่จะทำงานบนระบบที่ไม่มี cron
Hamy

11

มากหรือน้อยเหมือนข้างบน แต่ใช้ gevent พร้อมกัน :)

"""Gevent based crontab implementation"""

from datetime import datetime, timedelta
import gevent

# Some utility classes / functions first
def conv_to_set(obj):
    """Converts to set allowing single integer to be provided"""

    if isinstance(obj, (int, long)):
        return set([obj])  # Single item
    if not isinstance(obj, set):
        obj = set(obj)
    return obj

class AllMatch(set):
    """Universal set - match everything"""
    def __contains__(self, item): 
        return True

allMatch = AllMatch()

class Event(object):
    """The Actual Event Class"""

    def __init__(self, action, minute=allMatch, hour=allMatch, 
                       day=allMatch, month=allMatch, daysofweek=allMatch, 
                       args=(), kwargs={}):
        self.mins = conv_to_set(minute)
        self.hours = conv_to_set(hour)
        self.days = conv_to_set(day)
        self.months = conv_to_set(month)
        self.daysofweek = conv_to_set(daysofweek)
        self.action = action
        self.args = args
        self.kwargs = kwargs

    def matchtime(self, t1):
        """Return True if this event should trigger at the specified datetime"""
        return ((t1.minute     in self.mins) and
                (t1.hour       in self.hours) and
                (t1.day        in self.days) and
                (t1.month      in self.months) and
                (t1.weekday()  in self.daysofweek))

    def check(self, t):
        """Check and run action if needed"""

        if self.matchtime(t):
            self.action(*self.args, **self.kwargs)

class CronTab(object):
    """The crontab implementation"""

    def __init__(self, *events):
        self.events = events

    def _check(self):
        """Check all events in separate greenlets"""

        t1 = datetime(*datetime.now().timetuple()[:5])
        for event in self.events:
            gevent.spawn(event.check, t1)

        t1 += timedelta(minutes=1)
        s1 = (t1 - datetime.now()).seconds + 1
        print "Checking again in %s seconds" % s1
        job = gevent.spawn_later(s1, self._check)

    def run(self):
        """Run the cron forever"""

        self._check()
        while True:
            gevent.sleep(60)

import os 
def test_task():
    """Just an example that sends a bell and asd to all terminals"""

    os.system('echo asd | wall')  

cron = CronTab(
  Event(test_task, 22, 1 ),
  Event(test_task, 0, range(9,18,2), daysofweek=range(0,5)),
)
cron.run()

เพียงแค่ทราบว่า datetime.timetuple () จะเริ่มต้นด้วยปีเดือนวัน ... ฯลฯ ...
Trey Stout

9

ไม่มีวิธีการแก้ปัญหาที่ระบุไว้แม้แต่ความพยายามที่จะแยกสตริงกำหนดการ cron ที่ซับซ้อน ดังนั้นนี่คือรุ่นของฉันโดยใช้croniter สรุปสาระสำคัญพื้นฐาน:

schedule = "*/5 * * * *" # Run every five minutes

nextRunTime = getNextCronRunTime(schedule)
while True:
     roundedDownTime = roundDownTime()
     if (roundedDownTime == nextRunTime):
         ####################################
         ### Do your periodic thing here. ###
         ####################################
         nextRunTime = getNextCronRunTime(schedule)
     elif (roundedDownTime > nextRunTime):
         # We missed an execution. Error. Re initialize.
         nextRunTime = getNextCronRunTime(schedule)
     sleepTillTopOfNextMinute()

ตัวช่วยประจำ:

from croniter import croniter
from datetime import datetime, timedelta

# Round time down to the top of the previous minute
def roundDownTime(dt=None, dateDelta=timedelta(minutes=1)):
    roundTo = dateDelta.total_seconds()
    if dt == None : dt = datetime.now()
    seconds = (dt - dt.min).seconds
    rounding = (seconds+roundTo/2) // roundTo * roundTo
    return dt + timedelta(0,rounding-seconds,-dt.microsecond)

# Get next run time from now, based on schedule specified by cron string
def getNextCronRunTime(schedule):
    return croniter(schedule, datetime.now()).get_next(datetime)

# Sleep till the top of the next minute
def sleepTillTopOfNextMinute():
    t = datetime.utcnow()
    sleeptime = 60 - (t.second + t.microsecond/1000000.0)
    time.sleep(sleeptime)

ใครบางคนสามารถเข้าไปใน "พลาดการประหารชีวิต" ได้elifอย่างไร? ตู้เอทีเอ็มฉันใช้ตารางเวลาเหมือน"* * * * *"แล้วเพิ่มบางอย่างtime.sleepมากกว่า 1 นาทีใน "ทำสิ่งที่เป็นระยะ" ifแต่ฉันมักจะเห็นเนื้อหาในนั้นถ้าคำสั่ง เมื่อใช้เวลามากกว่า 1 นาทีฉันก็เห็นว่าการวนซ้ำนั้นข้ามไป
TPPZ

@TPPZ กระบวนการอาจถูกระงับนาฬิกาอาจมีการเปลี่ยนแปลงด้วยตนเองหรือโดย ntp ฯลฯ เป็นต้น Croniter ใช้ในการไหลเวียนของอากาศและดูเหมือนว่าจะให้ความสำคัญมากกว่าโมดูล Crontab และอื่น ๆ
dlamblin

7

ฉันแก้ไขสคริปต์แล้ว

  1. ง่ายต่อการใช้:

    cron = Cron()
    cron.add('* * * * *'   , minute_task) # every minute
    cron.add('33 * * * *'  , day_task)    # every hour
    cron.add('34 18 * * *' , day_task)    # every day
    cron.run()
  2. ลองเริ่มภารกิจในวินาทีแรกของนาที

รหัสบน Github


6

ฉันมีการแก้ไขเล็กน้อยสำหรับวิธีการเรียกใช้คลาส CronTab ที่แนะนำโดย Brianวิธีการเรียนการทำงานการแนะนำโดยไบรอัน

เวลาออกไปหนึ่งวินาทีนำไปสู่หนึ่งวินาทีที่ยากลำบากในตอนท้ายของแต่ละนาที

class CronTab(object):
    def __init__(self, *events):
        self.events = events

    def run(self):
        t=datetime(*datetime.now().timetuple()[:5])
        while 1:
            for e in self.events:
                e.check(t)

            t += timedelta(minutes=1)
            n = datetime.now()
            while n < t:
                s = (t - n).seconds + 1
                time.sleep(s)
                n = datetime.now()

4

ไม่มีวิธี "pure python" ในการทำเช่นนี้เนื่องจากกระบวนการอื่นจะต้องเปิดใช้ python เพื่อเรียกใช้โซลูชันของคุณ ทุกแพลตฟอร์มจะมีหนึ่งหรือยี่สิบวิธีในการเริ่มกระบวนการและติดตามความคืบหน้าของพวกเขา บนแพลตฟอร์มยูนิกซ์ cron เป็นมาตรฐานเก่า ใน Mac OS X ยังมี launchd ซึ่งรวมการเรียกใช้ cron เหมือนการเปิดตัวกับฟังก์ชั่นการจ้องจับผิดที่สามารถทำให้กระบวนการของคุณอยู่ได้ถ้านั่นคือสิ่งที่คุณต้องการ เมื่อไพ ธ อนทำงานคุณสามารถใช้โมดูลกำหนดเวลาเพื่อกำหนดเวลางานได้


4

ฉันรู้ว่ามีจำนวนมากของคำตอบ แต่วิธีอื่นอาจจะไปกับตกแต่ง นี่คือตัวอย่างของการทำซ้ำฟังก์ชั่นทุกวันในเวลาที่กำหนด ความคิดที่ยอดเยี่ยมเกี่ยวกับการใช้วิธีนี้คือคุณเพียงแค่เพิ่มน้ำตาลซินแทคติกในฟังก์ชันที่คุณต้องการกำหนดเวลา:

@repeatEveryDay(hour=6, minutes=30)
def sayHello(name):
    print(f"Hello {name}")

sayHello("Bob") # Now this function will be invoked every day at 6.30 a.m

และมัณฑนากรจะมีลักษณะ:

def repeatEveryDay(hour, minutes=0, seconds=0):
    """
    Decorator that will run the decorated function everyday at that hour, minutes and seconds.
    :param hour: 0-24
    :param minutes: 0-60 (Optional)
    :param seconds: 0-60 (Optional)
    """
    def decoratorRepeat(func):

        @functools.wraps(func)
        def wrapperRepeat(*args, **kwargs):

            def getLocalTime():
                return datetime.datetime.fromtimestamp(time.mktime(time.localtime()))

            # Get the datetime of the first function call
            td = datetime.timedelta(seconds=15)
            if wrapperRepeat.nextSent == None:
                now = getLocalTime()
                wrapperRepeat.nextSent = datetime.datetime(now.year, now.month, now.day, hour, minutes, seconds)
                if wrapperRepeat.nextSent < now:
                    wrapperRepeat.nextSent += td

            # Waiting till next day
            while getLocalTime() < wrapperRepeat.nextSent:
                time.sleep(1)

            # Call the function
            func(*args, **kwargs)

            # Get the datetime of the next function call
            wrapperRepeat.nextSent += td
            wrapperRepeat(*args, **kwargs)

        wrapperRepeat.nextSent = None
        return wrapperRepeat

    return decoratorRepeat

1

ทางออกของ Brianทำงานได้ค่อนข้างดี อย่างไรก็ตามอย่างที่คนอื่น ๆ ชี้ไปมีข้อบกพร่องเล็กน้อยในการเรียกใช้รหัส ฉันก็พบว่ามันซับซ้อนเกินไปสำหรับความต้องการ

นี่เป็นทางเลือกที่ง่ายและใช้งานได้ของฉันสำหรับรหัสเรียกใช้ในกรณีที่ใครก็ได้ต้องการ:

def run(self):
    while 1:
        t = datetime.now()
        for e in self.events:
            e.check(t)

        time.sleep(60 - t.second - t.microsecond / 1000000.0)

1

อีกวิธีแก้ปัญหาเล็กน้อยจะ:

from aqcron import At
from time import sleep
from datetime import datetime

# Event scheduling
event_1 = At( second=5 )
event_2 = At( second=[0,20,40] )

while True:
    now = datetime.now()

    # Event check
    if now in event_1: print "event_1"
    if now in event_2: print "event_2"

    sleep(1)

และ class aqcron.At คือ:

# aqcron.py

class At(object):
    def __init__(self, year=None,    month=None,
                 day=None,     weekday=None,
                 hour=None,    minute=None,
                 second=None):
        loc = locals()
        loc.pop("self")
        self.at = dict((k, v) for k, v in loc.iteritems() if v != None)

    def __contains__(self, now):
        for k in self.at.keys():
            try:
                if not getattr(now, k) in self.at[k]: return False
            except TypeError:
                if self.at[k] != getattr(now, k): return False
        return True

1
ระวังเมื่อโพสต์คัดลอกและวาง boilerplate / คำต่อคำตอบสำหรับคำถามหลายข้อเหล่านี้มักจะถูกตั้งค่าสถานะเป็น "สแปม" โดยชุมชน หากคุณทำเช่นนี้ก็มักจะหมายถึงคำถามซ้ำกันดังนั้นให้ตั้งค่าสถานะเป็นเช่นนั้นแทน: stackoverflow.com/a/12360556/419
Kev

1

หากคุณกำลังมองหาตัวกำหนดตารางเวลาแบบกระจายคุณสามารถตรวจสอบhttps://github.com/sherinkurian/mani ได้ - ต้องใช้ redis แม้ว่าจะไม่ใช่สิ่งที่คุณต้องการ (โปรดทราบว่าฉันเป็นผู้เขียน) สิ่งนี้ถูกสร้างขึ้นเพื่อให้แน่ใจว่าสามารถรับข้อผิดพลาดได้โดยให้นาฬิกาทำงานบนโหนดมากกว่าหนึ่งโหนด


0

ฉันไม่รู้ว่ามีอะไรแบบนี้มีอยู่แล้ว มันจะง่ายในการเขียนของคุณเองด้วยโมดูลเวลาวันที่และ / หรือปฏิทินดูhttp://docs.python.org/library/time.html

กังวลเพียงสำหรับการแก้ปัญหาหลามคือความต้องการงานของคุณอยู่เสมอที่จะทำงานและอาจจะโดยอัตโนมัติ "ฟื้นคืนชีพ" หลังจากรีบูตบางสิ่งบางอย่างที่คุณทำจำเป็นต้องพึ่งพาการแก้ปัญหาขึ้นอยู่กับระบบ


3
หมุนตัวคุณเองเป็นตัวเลือก - แม้ว่ารหัสที่ดีที่สุดคือรหัสที่คุณไม่ต้องเขียน การฟื้นคืนชีพฉันคิดว่าเป็นสิ่งที่ฉันอาจต้องพิจารณา
jamesh

0

คุณสามารถตรวจสอบ PiCloud [1] Crons [2] แต่โปรดทราบว่างานของคุณจะไม่ทำงานบนเครื่องของคุณเอง นอกจากนี้ยังเป็นบริการที่คุณจะต้องจ่ายถ้าคุณใช้เวลาในการคำนวณมากกว่า 20 ชั่วโมงต่อเดือน

[1] http://www.picloud.com

[2] http://docs.picloud.com/cron.html


0

วิธีการของ Crontab บนเซิร์ฟเวอร์

Python ชื่อไฟล์ hello.py

ขั้นที่ 1: สร้างไฟล์ sh ให้ชื่อ s.sh

python3 /home/ubuntu/Shaurya/Folder/hello.py> /home/ubuntu/Shaurya/Folder/log.txt 2> & 1

ขั้นที่ 2: เปิดตัวแก้ไข Crontab

crontab -e

ขั้นตอนที่ 3: เพิ่มตารางเวลา

ใช้การจัดรูปแบบ Crontab

2 * * * * sudo sh /home/ubuntu/Shaurya/Folder/s.sh

cron นี้จะทำงาน“ ที่ 2 นาที”


0

ฉันชอบที่แพคเกจpycronแก้ปัญหานี้ได้อย่างไร

import pycron
import time

while True:
    if pycron.is_now('0 2 * * 0'):   # True Every Sunday at 02:00
        print('running backup')
    time.sleep(60)

1
นี่ไม่ใช่ความคิดที่ดีเนื่องจากรหัสของคุณ "print ('running backup')" จะเปิดตัวตลอดทั้งนาทีโดยมีช่วงเวลา 5s ดังนั้นในกรณีนี้ความล่าช้าควรเป็น 60 วินาที
n158

คุณถูก! ขอบคุณสำหรับการชี้ให้เห็นว่า
Duffau
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.