繁体   English   中英

C/C++:如何获取构建时间的整数 unix 时间戳(不是字符串)

[英]C/C++: how to get integer unix timestamp of build time (not string)

我正在尝试实现非常简单的事情:我需要存储构建时间的整数 32 位 unix 时间戳,但是我发现的所有宏( __DATE____TIME____TIMESTAMP__ )都扩展为字符串,而不是整数。

看来,我们只是没有它(这对我来说很奇怪)。 我真的想要整数,而不是字符串。

获得它的最佳做法是什么?

更新:

附带说明:我做嵌入式的东西,所以我没有足够的资源(比如 128 KB 的闪存),因此解析字符串真的很糟糕。

为什么我需要它:我只需要每个 beta 版本都有唯一的版本号。 首先,十六进制文件将命名为my-firmware-v2-33-BETA-1397315745.hex ,其次,当我需要在设备屏幕上显示当前版本时,我可能想以各种格式回显它。

所以,我今晚玩得很开心,创建了一个带有宏的头文件来生成一个 UNIX 时间戳,没有任何外部程序或特殊的编译器功能! 只需包含标题并使用__TIME_UNIX__宏。

其实代码很简单:

  1. 字符串中的数字字符按照loreb 的建议转换为str[i]-'0'数字,并根据它们的位置进行加权。
  2. 月份字符串的处理类似于chux answer; 字符被单独检查并作为一个组评估? : ? :运营商。
  3. 计算过去几个月的天数是用可交换的? : ? :表达式。
  4. 闰年计算很简单,因为在 UNIX 时间范围内,每 4 年插入一个闰日
  5. 最后,所有单个值都与相应的秒数进行权衡以获得 UNIX 时间。 请注意, SEC_PER_DAY必须减去一次,因为JAN 01 1970, 00:00:00必须为0

该代码已在 ATMEL Studio 7 (Visual Studio 2015) 中使用默认编译器和设置(avr-gcc、-O1 优化)进行了测试,并通过检查生成的 .lss 文件确认了结果。

将下面的代码复制并粘贴到头文件中,并将其包含在您需要的任何位置。 享受!

/*
 * compile_time.h
 *
 * Created: 30.05.2017 20:57:58
 *  Author: Dennis (instructable.com/member/nqtronix)
 *
 * This code provides the macro __TIME_UNIX__ which returns the current time in UNIX format. It can
 * be used to identify a version of code on an embedded device, to initialize its RTC and much more.
 * Along that several more constants for seconds, minutes, etc. are provided
 *
 * The macro is based on __TIME__ and __DATE__, which are assumed to be formatted "HH:MM:SS" and
 * "MMM DD YYYY", respectively. The actual value can be calculated by the C compiler at compile time
 * as all inputs are literals. MAKE SURE TO ENABLE OPTIMISATION!
 */ 


#ifndef COMPILE_TIME_H_
#define COMPILE_TIME_H_

// extracts 1..4 characters from a string and interprets it as a decimal value
#define CONV_STR2DEC_1(str, i)  (str[i]>'0'?str[i]-'0':0)
#define CONV_STR2DEC_2(str, i)  (CONV_STR2DEC_1(str, i)*10 + str[i+1]-'0')
#define CONV_STR2DEC_3(str, i)  (CONV_STR2DEC_2(str, i)*10 + str[i+2]-'0')
#define CONV_STR2DEC_4(str, i)  (CONV_STR2DEC_3(str, i)*10 + str[i+3]-'0')

// Some definitions for calculation
#define SEC_PER_MIN             60UL
#define SEC_PER_HOUR            3600UL
#define SEC_PER_DAY             86400UL
#define SEC_PER_YEAR            (SEC_PER_DAY*365)
#define UNIX_START_YEAR         1970UL

// Custom "glue logic" to convert the month name to a usable number
#define GET_MONTH(str, i)      (str[i]=='J' && str[i+1]=='a' && str[i+2]=='n' ? 1 :     \
                                str[i]=='F' && str[i+1]=='e' && str[i+2]=='b' ? 2 :     \
                                str[i]=='M' && str[i+1]=='a' && str[i+2]=='r' ? 3 :     \
                                str[i]=='A' && str[i+1]=='p' && str[i+2]=='r' ? 4 :     \
                                str[i]=='M' && str[i+1]=='a' && str[i+2]=='y' ? 5 :     \
                                str[i]=='J' && str[i+1]=='u' && str[i+2]=='n' ? 6 :     \
                                str[i]=='J' && str[i+1]=='u' && str[i+2]=='l' ? 7 :     \
                                str[i]=='A' && str[i+1]=='u' && str[i+2]=='g' ? 8 :     \
                                str[i]=='S' && str[i+1]=='e' && str[i+2]=='p' ? 9 :     \
                                str[i]=='O' && str[i+1]=='c' && str[i+2]=='t' ? 10 :    \
                                str[i]=='N' && str[i+1]=='o' && str[i+2]=='v' ? 11 :    \
                                str[i]=='D' && str[i+1]=='e' && str[i+2]=='c' ? 12 : 0)

#define GET_MONTH2DAYS(month)  ((month == 1 ? 0 : 31 +                      \
                                (month == 2 ? 0 : 28 +                      \
                                (month == 3 ? 0 : 31 +                      \
                                (month == 4 ? 0 : 30 +                      \
                                (month == 5 ? 0 : 31 +                      \
                                (month == 6 ? 0 : 30 +                      \
                                (month == 7 ? 0 : 31 +                      \
                                (month == 8 ? 0 : 31 +                      \
                                (month == 9 ? 0 : 30 +                      \
                                (month == 10 ? 0 : 31 +                     \
                                (month == 11 ? 0 : 30))))))))))))           \


#define GET_LEAP_DAYS           ((__TIME_YEARS__-1968)/4 - (__TIME_MONTH__ <=2 ? 1 : 0))



#define __TIME_SECONDS__        CONV_STR2DEC_2(__TIME__, 6)
#define __TIME_MINUTES__        CONV_STR2DEC_2(__TIME__, 3)
#define __TIME_HOURS__          CONV_STR2DEC_2(__TIME__, 0)
#define __TIME_DAYS__           CONV_STR2DEC_2(__DATE__, 4)
#define __TIME_MONTH__          GET_MONTH(__DATE__, 0)
#define __TIME_YEARS__          CONV_STR2DEC_4(__DATE__, 7)

#define __TIME_UNIX__         ((__TIME_YEARS__-UNIX_START_YEAR)*SEC_PER_YEAR+       \
                                GET_LEAP_DAYS*SEC_PER_DAY+                          \
                                GET_MONTH2DAYS(__TIME_MONTH__)*SEC_PER_DAY+         \
                                __TIME_DAYS__*SEC_PER_DAY-SEC_PER_DAY+              \
                                __TIME_HOURS__*SEC_PER_HOUR+                        \
                                __TIME_MINUTES__*SEC_PER_MIN+                       \
                                __TIME_SECONDS__)

#endif /* COMPILE_TIME_H_ */

编辑:

初始版本不考虑 100 和 400 模年对二月天数的影响。 这应该不是 2001 到 2101 之间的问题,但这里有一个更通用的宏:

/* 
 *
 * Created: 29.03.2018
 *
 * Authors:
 * 
 * Assembled from the code released on Stackoverflow by:
 *   Dennis (instructable.com/member/nqtronix)    |   https://stackoverflow.com/questions/23032002/c-c-how-to-get-integer-unix-timestamp-of-build-time-not-string
 * and
 *   Alexis Wilke                                 |   https://stackoverflow.com/questions/10538444/do-you-know-of-a-c-macro-to-compute-unix-time-and-date
 *
 * Assembled by Jean Rabault
 * 
 * UNIX_TIMESTAMP gives the UNIX timestamp (unsigned long integer of seconds since 1st Jan 1970) of compilation from macros using the compiler defined __TIME__ macro.
 * This should include Gregorian calendar leap days, in particular the 29ths of February, 100 and 400 years modulo leaps.
 * 
 * Careful: __TIME__ is the local time of the computer, NOT the UTC time in general!
 * 
 */

#ifndef COMPILE_TIME_H_
#define COMPILE_TIME_H_

// Some definitions for calculation
#define SEC_PER_MIN             60UL
#define SEC_PER_HOUR            3600UL
#define SEC_PER_DAY             86400UL
#define SEC_PER_YEAR            (SEC_PER_DAY*365)

// extracts 1..4 characters from a string and interprets it as a decimal value
#define CONV_STR2DEC_1(str, i)  (str[i]>'0'?str[i]-'0':0)
#define CONV_STR2DEC_2(str, i)  (CONV_STR2DEC_1(str, i)*10 + str[i+1]-'0')
#define CONV_STR2DEC_3(str, i)  (CONV_STR2DEC_2(str, i)*10 + str[i+2]-'0')
#define CONV_STR2DEC_4(str, i)  (CONV_STR2DEC_3(str, i)*10 + str[i+3]-'0')

// Custom "glue logic" to convert the month name to a usable number
#define GET_MONTH(str, i)      (str[i]=='J' && str[i+1]=='a' && str[i+2]=='n' ? 1 :     \
                                str[i]=='F' && str[i+1]=='e' && str[i+2]=='b' ? 2 :     \
                                str[i]=='M' && str[i+1]=='a' && str[i+2]=='r' ? 3 :     \
                                str[i]=='A' && str[i+1]=='p' && str[i+2]=='r' ? 4 :     \
                                str[i]=='M' && str[i+1]=='a' && str[i+2]=='y' ? 5 :     \
                                str[i]=='J' && str[i+1]=='u' && str[i+2]=='n' ? 6 :     \
                                str[i]=='J' && str[i+1]=='u' && str[i+2]=='l' ? 7 :     \
                                str[i]=='A' && str[i+1]=='u' && str[i+2]=='g' ? 8 :     \
                                str[i]=='S' && str[i+1]=='e' && str[i+2]=='p' ? 9 :     \
                                str[i]=='O' && str[i+1]=='c' && str[i+2]=='t' ? 10 :    \
                                str[i]=='N' && str[i+1]=='o' && str[i+2]=='v' ? 11 :    \
                                str[i]=='D' && str[i+1]=='e' && str[i+2]=='c' ? 12 : 0)

// extract the information from the time string given by __TIME__ and __DATE__
#define __TIME_SECONDS__        CONV_STR2DEC_2(__TIME__, 6)
#define __TIME_MINUTES__        CONV_STR2DEC_2(__TIME__, 3)
#define __TIME_HOURS__          CONV_STR2DEC_2(__TIME__, 0)
#define __TIME_DAYS__           CONV_STR2DEC_2(__DATE__, 4)
#define __TIME_MONTH__          GET_MONTH(__DATE__, 0)
#define __TIME_YEARS__          CONV_STR2DEC_4(__DATE__, 7)

// Days in February
#define _UNIX_TIMESTAMP_FDAY(year) \
    (((year) % 400) == 0UL ? 29UL : \
        (((year) % 100) == 0UL ? 28UL : \
            (((year) % 4) == 0UL ? 29UL : \
                28UL)))

// Days in the year
#define _UNIX_TIMESTAMP_YDAY(year, month, day) \
    ( \
        /* January */    day \
        /* February */ + (month >=  2 ? 31UL : 0UL) \
        /* March */    + (month >=  3 ? _UNIX_TIMESTAMP_FDAY(year) : 0UL) \
        /* April */    + (month >=  4 ? 31UL : 0UL) \
        /* May */      + (month >=  5 ? 30UL : 0UL) \
        /* June */     + (month >=  6 ? 31UL : 0UL) \
        /* July */     + (month >=  7 ? 30UL : 0UL) \
        /* August */   + (month >=  8 ? 31UL : 0UL) \
        /* September */+ (month >=  9 ? 31UL : 0UL) \
        /* October */  + (month >= 10 ? 30UL : 0UL) \
        /* November */ + (month >= 11 ? 31UL : 0UL) \
        /* December */ + (month >= 12 ? 30UL : 0UL) \
    )

// get the UNIX timestamp from a digits representation
#define _UNIX_TIMESTAMP(year, month, day, hour, minute, second) \
    ( /* time */ second \
                + minute * SEC_PER_MIN \
                + hour * SEC_PER_HOUR \
    + /* year day (month + day) */ (_UNIX_TIMESTAMP_YDAY(year, month, day) - 1) * SEC_PER_DAY \
    + /* year */ (year - 1970UL) * SEC_PER_YEAR \
                + ((year - 1969UL) / 4UL) * SEC_PER_DAY \
                - ((year - 1901UL) / 100UL) * SEC_PER_DAY \
                + ((year - 1601UL) / 400UL) * SEC_PER_DAY \
    )

// the UNIX timestamp
#define UNIX_TIMESTAMP (_UNIX_TIMESTAMP(__TIME_YEARS__, __TIME_MONTH__, __TIME_DAYS__, __TIME_HOURS__, __TIME_MINUTES__, __TIME_SECONDS__))

#endif

您可以在每次构建时生成一个timestamp.h文件作为预构建步骤,并将该文件包含在您的源代码中。 我不知道你在使用什么构建工具(嵌入式世界非常广泛),到目前为止我看到的每个构建工具都允许用户定义自定义的预构建和后构建步骤(飞思卡尔 CodeWarrior、AVR 工作室、MSVS。 ...)。

例如,在 Windows 上的 AVR 工作室中,我使用了此预构建步骤(请注意, $(SolutionDir)特定于基于 MSVS 的 AVR stuio,您可以替换为您需要的任何文件系统路径):

FOR /F %%A IN ('C:\cygwin\bin\date +%s') DO SET BUILD_TIMESTAMP=%%A
echo #define BUILD_TIME %BUILD_TIMESTAMP% > "$(SolutionDir)timestamp.h"

在项目的其中一个 C 文件中,我通常包含这个生成的文件(该文件的路径可能不同...):

#include "../timestamp.h"

生成的文件如下所示:

#define BUILD_TIME 1397317498

因此,当我单击“构建项目”时,工作室首先运行我的命令,生成新的timestamp.h ,然后将其用作任何其他 C 文件中的包含。

请注意,上面的示例使用 Cygwin ( C:\\cygwin\\bin\\date +%s ) 来获取时间戳。 如果您不想使用 Cygwin,则必须为 Windows 找到其他方法来为您生成时间戳。 您可以编写自己的命令行实用程序(它应该是大约 10 行 C 代码:-)或在 Internet 上搜索其他方式。

为什么不在命令行上定义它?

gcc -DCOMPILE_TIME=`date '+%s'` mysources.c
/**
 * @brief  Macros to get integer build timestamp:
 *         __DATE_TIME_Y2K__  seconds since 2000-01-01,00:00:00
 *         __DATE_TIME_UNIX__ seconds since 1970-01-01 00:00:00
 *
 *           01234567890              01234567
 * __DATE__ "Jul 27 2019"   __TIME__ "12:34:56"
 */
#ifndef __DATE_TIME_H__
#define __DATE_TIME_H__

// For testing
//#define __DATE__ ("Jan 01 2000")
//#define __TIME__ ("00:00:00")

#define Y2K_UNIX_EPOCH_DIFF 946684800U
#define YEARS ((__DATE__[10] - '0' + (__DATE__[9] - '0') * 10))
#define DAY_OF_MONTH ((__DATE__[5] - '0') \
                  + (((__DATE__[4] > '0')? __DATE__[4] - '0': 0) * 10) - 1)
#define DAY_OF_YEAR ((DAY_OF_MONTH) + \
( /* Jan */ (__DATE__[0] == 'J' && __DATE__[1] == 'a')?   0: \
  /* Feb */ (__DATE__[0] == 'F'                      )?  31: \
  /* Mar */ (__DATE__[0] == 'M' && __DATE__[2] == 'r')?  59: \
  /* Apr */ (__DATE__[0] == 'A' && __DATE__[1] == 'p')?  90: \
  /* May */ (__DATE__[0] == 'M'                      )? 120: \
  /* Jun */ (__DATE__[0] == 'J' && __DATE__[2] == 'n')? 151: \
  /* Jul */ (__DATE__[0] == 'J'                      )? 181: \
  /* Aug */ (__DATE__[0] == 'A'                      )? 212: \
  /* Sep */ (__DATE__[0] == 'S'                      )? 243: \
  /* Oct */ (__DATE__[0] == 'O'                      )? 273: \
  /* Nov */ (__DATE__[0] == 'N'                      )? 304: \
  /* Dec */                                             334  ))
#define LEAP_DAYS (YEARS / 4 + ((YEARS % 4 == 0 && DAY_OF_YEAR > 58)? 1 : 0) )      
#define __DATE_TIME_Y2K__ ( (YEARS * 365 + LEAP_DAYS + DAY_OF_YEAR ) * 86400 \
                    + ((__TIME__[0] - '0') * 10 + __TIME__[1] - '0') * 3600 \
                    + ((__TIME__[3] - '0') * 10 + __TIME__[4] - '0') * 60 \
                    + ((__TIME__[6] - '0') * 10 + __TIME__[7] - '0') )
#define  __DATE_TIME_UNIX__ ( __DATE_TIME_Y2K__ + Y2K_UNIX_EPOCH_DIFF )
#endif /* __DATE_TIME_H__ */

这只是从上面@nqtronix 的版本开发的更紧凑、更轻的版本。 享受!

注意:此宏忽略世纪,因此仅在 2000-01-01,00:00:00 和 2099-12-31,23:59:59 之间有效。

看看下面的暴行:

#include <stdio.h>
#define dec(ch) ((ch)-'0')
#define t(index, multiplier)    (dec(__TIME__[index]) * (multiplier))
/* only minutes and seconds - you get the idea */
#define mmss()  (t(3,600) + t(4,60) + t(6,10) + t(7,1))
int main()
{
        /*
        int i;
        printf("time = %s\n", __TIME__);
        for(i=0; __TIME__[i]; i++)
                printf("t(%d) = %d\n", i, t(i,1));
        */
        printf("mmss = %d\n", mmss());
        return 0;
}

在我的电脑上 gcc -O 能够将其优化为一个恒定值; 要获得完整的time_t ,您需要__DATE__的堂兄宏并查看生成的程序集。

如果有人问,我没有写这个答案;)

编辑:为了清楚起见,您可能应该遵循 Roman Hocke 的回答并编写一个简短的 C 程序来为您生成值(当然,如果您进行交叉编译,您应该小心...)

编辑:根据@Lưu Vĩnh Phúc 的评论,关键思想似乎需要解释:所有计算都是在编译时完成的。 请参阅下面生成的 OP 代码。


下面不是生成time_t ,而是在编译器的时区生成struct tm 如果需要,调用mktime()将返回time_t

time_t CompileTime = mktime(CompileTimeTM());
printf("%lld\n", (long long) CompileTime);

在其他应用程序中,不是返回struct tm ,而是每个字段分配简单地打印值以显示版本。

else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='y')) Print2Dig(5);

下面冗长的代码在 PIC 编译器中生成的指令很少,因为它优化了很多。 类似的方法可以使用__TIMESTAMP__

// Dummy: used to detect a bad date parsing in Send_ID()
extern void BadDateM(void);

struct tm *CompileTimeTM(void) {
  static const char d[10] = __DATE__;
  static const char t[10] = __TIME__;
  static struct tm ct;

  ct.tm_year = (d[7]-'0')*10 + (d[8]-'0') + 2000 - 1900;

  #IGNORE_WARNINGS  204
  if (0) ;
  else if ((d[3]=='J') && (d[4]=='a') && (d[5]=='n')) ct.tm_mon = 1-1;
  else if ((d[3]=='F') && (d[4]=='e') && (d[5]=='b')) ct.tm_mon = 2-1;
  else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='r')) ct.tm_mon = 3-1;
  else if ((d[3]=='A') && (d[4]=='p') && (d[5]=='r')) ct.tm_mon = 4-1;
  else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='y')) ct.tm_mon = 5-1;
  else if ((d[3]=='J') && (d[4]=='u') && (d[5]=='n')) ct.tm_mon = 6-1;
  else if ((d[3]=='J') && (d[4]=='u') && (d[5]=='l')) ct.tm_mon = 7-1;
  else if ((d[3]=='A') && (d[4]=='u') && (d[5]=='g')) ct.tm_mon = 8-1;
  else if ((d[3]=='S') && (d[4]=='e') && (d[5]=='p')) ct.tm_mon = 9-1;
  else if ((d[3]=='O') && (d[4]=='c') && (d[5]=='t')) ct.tm_mon = 10-1;
  else if ((d[3]=='N') && (d[4]=='o') && (d[5]=='v')) ct.tm_mon = 11-1;
  else if ((d[3]=='D') && (d[4]=='e') && (d[5]=='c')) ct.tm_mon = 12-1;
  else BadDateM(); // compile this if no match above, and thus fail link.
  #IGNORE_WARNINGS  NONE

  ct.tm_mday = (d[0]-'0')*10 + (d[1]-'0');
  ct.tm_hour = (t[0]-'0')*10 + (t[1]-'0');
  ct.tm_min = (t[3]-'0')*10 + (t[4]-'0');
  ct.tm_sec = (t[6]-'0')*10 + (t[7]-'0');

  ct.tm_isdst = -1;  // information is not available.
  // ct.tm_yday = 0;
  // ct.tm_wday = 0;

  return &ct;
  }

清单

struct tm *CompileTimeTM(void) { 
static const char d[10] = __DATE__; 
static const char t[10] = __TIME__; 
static struct tm ct; 

ct.tm_year = (d[7]-'0')*10 + (d[8]-'0') + 2000 - 1900; 
0F78 200724         MOV     #72,W4         : W4 = 72
0F7A 8864D4         MOV     W4,C9A         : C9A = W4

#IGNORE_WARNINGS  204 
if (0) ; 
else if ((d[3]=='J') && (d[4]=='a') && (d[5]=='n')) ct.tm_mon = 1-1; 
else if ((d[3]=='F') && (d[4]=='e') && (d[5]=='b')) ct.tm_mon = 2-1; 
else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='r')) ct.tm_mon = 3-1; 
else if ((d[3]=='A') && (d[4]=='p') && (d[5]=='r')) ct.tm_mon = 4-1; 
0F7C 200034         MOV     #3,W4          : W4 = 3
0F7E 8864C4         MOV     W4,C98         : C98 = W4
else if ((d[3]=='M') && (d[4]=='a') && (d[5]=='y')) ct.tm_mon = 5-1; 
else if ((d[3]=='J') && (d[4]=='u') && (d[5]=='n')) ct.tm_mon = 6-1; 
else if ((d[3]=='J') && (d[4]=='u') && (d[5]=='l')) ct.tm_mon = 7-1; 
else if ((d[3]=='A') && (d[4]=='u') && (d[5]=='g')) ct.tm_mon = 8-1; 
else if ((d[3]=='S') && (d[4]=='e') && (d[5]=='p')) ct.tm_mon = 9-1; 
else if ((d[3]=='O') && (d[4]=='c') && (d[5]=='t')) ct.tm_mon = 10-1; 
else if ((d[3]=='N') && (d[4]=='o') && (d[5]=='v')) ct.tm_mon = 11-1; 
else if ((d[3]=='D') && (d[4]=='e') && (d[5]=='c')) ct.tm_mon = 12-1; 
else BadDateM(); // compile this if no match above, and thus fail link. 
#IGNORE_WARNINGS  NONE 

ct.tm_mday = (d[0]-'0')*10 + (d[1]-'0'); 
0F80 2000E4         MOV     #E,W4          : W4 = E
0F82 8864B4         MOV     W4,C96         : C96 = W4
ct.tm_hour = (t[0]-'0')*10 + (t[1]-'0'); 
0F84 2000B4         MOV     #B,W4          : W4 = B
0F86 8864A4         MOV     W4,C94         : C94 = W4
ct.tm_min = (t[3]-'0')*10 + (t[4]-'0'); 
0F88 200354         MOV     #35,W4         : W4 = 35
0F8A 886494         MOV     W4,C92         : C92 = W4
ct.tm_sec = (t[6]-'0')*10 + (t[7]-'0'); 
0F8C 2000D4         MOV     #D,W4          : W4 = D
0F8E 886484         MOV     W4,C90         : C90 = W4

我发现如果一个月中的某一天小于 10,我必须添加一个特殊的检查 - 否则它返回一个负数

 // special case to handle __DATE__ not inserting leading zero on day of month // if Day of month is less than 10 - it inserts a blank character // this results in a negative number for tm_mday if(d[4] == ' ') { ct.tm_mday = d[5]-'0'; } else { ct.tm_mday = (d[4]-'0')*10 + (d[5]-'0'); }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM