module_time.c
1 #include <assert.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <unistd.h>
6 #include <time.h>
7 #include <sys/time.h>
8 
9 #include <kuroko/vm.h>
10 #include <kuroko/value.h>
11 #include <kuroko/object.h>
12 #include <kuroko/util.h>
13 
14 KRK_Function(sleep) {
15  FUNCTION_TAKES_EXACTLY(1);
16 
17  if (!IS_INTEGER(argv[0]) && !IS_FLOATING(argv[0])) {
18  return TYPE_ERROR(int or float,argv[0]);
19  }
20 
21  unsigned int usecs = (IS_INTEGER(argv[0]) ? AS_INTEGER(argv[0]) :
22  (IS_FLOATING(argv[0]) ? AS_FLOATING(argv[0]) : 0)) *
23  1000000;
24 
25  usleep(usecs);
26 
27  return BOOLEAN_VAL(1);
28 }
29 
30 KRK_Function(time) {
31  FUNCTION_TAKES_NONE();
32 
33  struct timeval tv;
34  gettimeofday(&tv,NULL);
35 
36  double out = (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
37 
38  return FLOATING_VAL(out);
39 }
40 
41 static KrkClass * struct_time;
42 
44  KrkInstance inst;
45  struct tm _value;
46 };
47 
48 #define IS_struct_time(o) (krk_isInstanceOf(o,struct_time))
49 #define AS_struct_time(o) ((struct struct_time_obj*)AS_OBJECT(o))
50 #define CURRENT_CTYPE struct struct_time_obj *
51 #define CURRENT_NAME self
52 
53 KRK_Method(struct_time,__init__) {
54  KrkValue seq;
55  if (!krk_parseArgs(".V:struct_time", (const char *[]){"iterable"}, &seq)) return NONE_VAL();
56  if (!IS_TUPLE(seq) || AS_TUPLE(seq)->values.count != 9) return krk_runtimeError(vm.exceptions->notImplementedError, "sequence other than 9-tuple unsupported");
57  for (int i = 0; i < 9; ++i) {
58  if (!IS_INTEGER(AS_TUPLE(seq)->values.values[i])) return krk_runtimeError(vm.exceptions->valueError, "expected int, not %T", AS_TUPLE(seq)->values.values[i]);
59  }
60 
61  self->_value.tm_year = AS_INTEGER(AS_TUPLE(seq)->values.values[0]) - 1900;
62  self->_value.tm_mon = AS_INTEGER(AS_TUPLE(seq)->values.values[1]) - 1;
63  self->_value.tm_mday = AS_INTEGER(AS_TUPLE(seq)->values.values[2]);
64  self->_value.tm_hour = AS_INTEGER(AS_TUPLE(seq)->values.values[3]);
65  self->_value.tm_min = AS_INTEGER(AS_TUPLE(seq)->values.values[4]);
66  self->_value.tm_sec = AS_INTEGER(AS_TUPLE(seq)->values.values[5]);
67  self->_value.tm_wday = (AS_INTEGER(AS_TUPLE(seq)->values.values[6])+6)%7;
68  self->_value.tm_yday = AS_INTEGER(AS_TUPLE(seq)->values.values[7]) - 1;
69  self->_value.tm_isdst = AS_INTEGER(AS_TUPLE(seq)->values.values[8]);
70 
71  return NONE_VAL();
72 }
73 
74 KRK_Method(struct_time,tm_year) { return INTEGER_VAL(self->_value.tm_year + 1900); } /* struct tm is 1900-indexed, snakes are not */
75 KRK_Method(struct_time,tm_mon) { return INTEGER_VAL(self->_value.tm_mon + 1); } /* struct tm is 0-indexed, snakes are not */
76 KRK_Method(struct_time,tm_mday) { return INTEGER_VAL(self->_value.tm_mday); }
77 KRK_Method(struct_time,tm_hour) { return INTEGER_VAL(self->_value.tm_hour); }
78 KRK_Method(struct_time,tm_min) { return INTEGER_VAL(self->_value.tm_min); }
79 KRK_Method(struct_time,tm_sec) { return INTEGER_VAL(self->_value.tm_sec); }
80 KRK_Method(struct_time,tm_wday) { return INTEGER_VAL((self->_value.tm_wday+1)%7); } /* struct tm has Sunday = 0, but snakes use Monday = 0 */
81 KRK_Method(struct_time,tm_yday) { return INTEGER_VAL(self->_value.tm_yday+1); } /* struct tm is from 0, but snakes start from 1 */
82 KRK_Method(struct_time,tm_isdst) { return INTEGER_VAL(self->_value.tm_isdst); }
83 
84 KRK_Method(struct_time,__repr__) {
85  return krk_stringFromFormat(
86  "time.struct_time(tm_year=%d, tm_mon=%d, tm_mday=%d, tm_hour=%d, tm_min=%d, "
87  "tm_sec=%d, tm_wday=%d, tm_yday=%d, tm_isdst=%d)",
88  self->_value.tm_year + 1900,
89  self->_value.tm_mon + 1,
90  self->_value.tm_mday,
91  self->_value.tm_hour,
92  self->_value.tm_min,
93  self->_value.tm_sec,
94  (self->_value.tm_wday + 1) % 7,
95  self->_value.tm_yday + 1,
96  self->_value.tm_isdst);
97 }
98 
99 static time_t time_or_now(int has_arg, long long secs) {
100  if (!has_arg) {
101  struct timeval tv;
102  gettimeofday(&tv,NULL);
103  return (time_t)tv.tv_sec;
104  } else {
105  return (time_t)secs;
106  }
107 }
108 
109 static void tm_or_now(const struct struct_time_obj * t, struct tm * _time) {
110  if (t) {
111  memcpy(_time,&t->_value,sizeof(struct tm));
112  } else {
113  struct timeval tv;
114  gettimeofday(&tv,NULL);
115  time_t time = tv.tv_sec;
116  localtime_r(&time,_time);
117  }
118 }
119 
120 KRK_Function(localtime) {
121  int gave_seconds;
122  long long seconds;
123  if (!krk_parseArgs("|L?",(const char*[]){"seconds"},&gave_seconds, &seconds)) return NONE_VAL();
124  time_t time = time_or_now(gave_seconds, seconds);
125 
126  /* Create a struct_time to store result in */
127  CURRENT_CTYPE out = (CURRENT_CTYPE)krk_newInstance(struct_time);
128  krk_push(OBJECT_VAL(out));
129 
130  if (!localtime_r(&time, &out->_value)) return krk_runtimeError(vm.exceptions->valueError, "?");
131 
132  return krk_pop();
133 }
134 
135 static KrkValue krk_asctime(const struct tm *_time) {
136  /* asctime is normally locale-aware, but the snake function is not, so we do this manually */
137  static const char * monNames[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
138  static const char * dayNames[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
139 
140  char buf[40] = {0};
141 
142  /* The normal strftime string for this is %a %b %d %T %Y
143  * Day Mon DD HH:MM:SS YYYY */
144  snprintf(buf,39, "%s %s%3d %.2d:%.2d:%.2d %d",
145  dayNames[_time->tm_wday % 7],
146  monNames[_time->tm_mon % 12],
147  _time->tm_mday,
148  _time->tm_hour,
149  _time->tm_min,
150  _time->tm_sec,
151  _time->tm_year + 1900);
152 
153  return OBJECT_VAL(krk_copyString(buf,strlen(buf)));
154 }
155 
156 KRK_Function(asctime) {
157  struct struct_time_obj * t = NULL;
158  if (!krk_parseArgs("|O!",(const char*[]){"t"},struct_time,&t)) return NONE_VAL();
159  struct tm _time;
160  tm_or_now(t,&_time);
161  return krk_asctime(&_time);
162 }
163 
164 KRK_Function(ctime) {
165  int has_arg;
166  long long secs;
167  if (!krk_parseArgs("|L?",(const char*[]){"secs"},&has_arg,&secs)) return NONE_VAL();
168  time_t time = time_or_now(has_arg, secs);
169  struct tm _time;
170  if (!localtime_r(&time, &_time)) return krk_runtimeError(vm.exceptions->valueError, "?");
171 
172  return krk_asctime(&_time);
173 }
174 
175 KRK_Function(gmtime) {
176  int gave_seconds;
177  long long seconds;
178  if (!krk_parseArgs("|L?",(const char*[]){"secs"},&gave_seconds, &seconds)) return NONE_VAL();
179  time_t time = time_or_now(gave_seconds, seconds);
180 
181  /* Create a struct_time to store result in */
182  CURRENT_CTYPE out = (CURRENT_CTYPE)krk_newInstance(struct_time);
183  krk_push(OBJECT_VAL(out));
184 
185  if (!gmtime_r(&time, &out->_value)) return krk_runtimeError(vm.exceptions->valueError, "?");
186 
187  return krk_pop();
188 }
189 
190 KRK_Function(mktime) {
191  struct struct_time_obj * t;
192  if (!krk_parseArgs("O!",(const char*[]){"t"},struct_time,&t)) return NONE_VAL();
193 
194  struct tm _time;
195  memcpy(&_time,&t->_value,sizeof(struct tm));
196  _time.tm_wday = -1;
197  time_t out = mktime(&_time);
198  if (out == -1 && _time.tm_wday == -1) return krk_runtimeError(vm.exceptions->valueError, "invalid argument to mktime");
199  return FLOATING_VAL(out);
200 }
201 
202 KRK_Function(strftime) {
203  const char * format;
204  struct struct_time_obj * t = NULL;
205  if (!krk_parseArgs("s|O!",(const char*[]){"format","t"},&format,struct_time,&t)) return NONE_VAL();
206  struct tm _time;
207  tm_or_now(t,&_time);
208 
209  /* strftime wants a buffer size, but we have no way of knowing. Following
210  * what CPython does, start from 1024 and try doubling until we reach
211  * the length of our format string * 256, and then give up. */
212  size_t fmt_len = strlen(format);
213  size_t size = 1024;
214  while (1) {
215  char * buf = malloc(size);
216  size_t ret = strftime(buf,size,format,&_time);
217  if (ret || size > fmt_len * 256) {
218  krk_push(OBJECT_VAL(krk_copyString(buf,ret)));
219  free(buf);
220  return krk_pop();
221  }
222  size *= 2;
223  free(buf);
224  }
225 }
226 
227 KRK_Module(time) {
228  KRK_DOC(module, "@brief Provides timekeeping functions.");
229  KRK_DOC(BIND_FUNC(module,sleep), "@brief Pause execution of the current thread.\n"
230  "@arguments secs\n\n"
231  "Uses the system @c usleep() function to sleep for @p secs seconds, which may be a @ref float or @ref int. "
232  "The available precision is platform-dependent.");
233  KRK_DOC(BIND_FUNC(module,time), "@brief Return the elapsed seconds since the system epoch.\n\n"
234  "Returns a @ref float representation of the number of seconds since the platform's epoch date. "
235  "On POSIX platforms, this is the number of seconds since 1 January 1970. "
236  "The precision of the return value is platform-dependent.");
237 
238  krk_makeClass(module, &struct_time, "struct_time", KRK_BASE_CLASS(object));
239  struct_time->allocSize = sizeof(struct struct_time_obj);
240  KRK_DOC(struct_time, "Time value returned by various functions.");
241  KRK_DOC(BIND_METHOD(struct_time,__init__), "@arguments iterable: tuple\n\n"
242  "Create a @ref struct_time from a 9-tuple of @ref int values.\n"
243  "The format of @p iterable is `(tm_year,tm_mon,tm_mday,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst)`.");
244  KRK_DOC(BIND_PROP(struct_time,tm_year), "Calendar year");
245  KRK_DOC(BIND_PROP(struct_time,tm_mon), "Month, [1, 12]");
246  KRK_DOC(BIND_PROP(struct_time,tm_mday), "Day of the month, [1, 31]");
247  KRK_DOC(BIND_PROP(struct_time,tm_hour), "Clock hour, [0, 23]");
248  KRK_DOC(BIND_PROP(struct_time,tm_min), "Clock minute, [0, 59]");
249  KRK_DOC(BIND_PROP(struct_time,tm_sec), "Clock seconds, [0, 61] (maybe, due to leap seconds, depends on platform)");
250  KRK_DOC(BIND_PROP(struct_time,tm_wday), "Day of week, [0, 6], 0 is Monday.");
251  KRK_DOC(BIND_PROP(struct_time,tm_yday), "Day of year [1, 366]");
252  KRK_DOC(BIND_PROP(struct_time,tm_isdst), "0, 1, -1 for unknown");
253  BIND_METHOD(struct_time,__repr__);
254  krk_finalizeClass(struct_time);
255 
256  KRK_DOC(BIND_FUNC(module,localtime), "@brief Convert seconds since epoch to local time.\n"
257  "@arguments seconds=time.time()\n\n"
258  "If @p seconds is not provided, the current @ref time is used.");
259  KRK_DOC(BIND_FUNC(module,asctime), "@brief Convert time to string.\n"
260  "@arguments t=time.localtime()\n\n"
261  "If @p t is not provided, the current @ref localtime is used.");
262  KRK_DOC(BIND_FUNC(module,ctime), "@brief Convert seconds since epoch to string.\n"
263  "@arguments secs=time.time()\n\n"
264  "If @p secs is not provided, the current @ref time is used.");
265  KRK_DOC(BIND_FUNC(module,gmtime), "@brief Convert seconds since epoch to UTC time.\n"
266  "@arguments secs=time.time()\n\n"
267  "If @p secs is not provided, the current @ref time is used.");
268  KRK_DOC(BIND_FUNC(module,mktime), "@brief Convert from local time to seconds since epoch.\n"
269  "@arguments t\n\n"
270  "For compatibility with @ref time a @ref float is returned.");
271  KRK_DOC(BIND_FUNC(module,strftime), "@brief Format time string with system function.\n"
272  "@arguments format,t=time.localtime()\n\n"
273  "Uses the system `strftime` C function to convert a @ref struct_time to a string.\n"
274  "If @p t is not provided, the current @ref localtime is used.");
275 }
276 
KrkValue krk_runtimeError(KrkClass *type, const char *fmt,...)
Produce and raise an exception with a formatted message.
Definition: exceptions.c:460
KRK_Module(socket)
Struct definitions for core object types.
Type object.
Definition: object.h:215
size_t allocSize
Size to allocate when creating instances of this class.
Definition: object.h:222
KrkClass * krk_makeClass(KrkInstance *module, KrkClass **_class, const char *name, KrkClass *base)
Convenience function for creating new types.
Definition: vm.c:164
void krk_finalizeClass(KrkClass *_class)
Finalize a class by collecting pointers to core methods.
Definition: vm.c:189
An object of a class.
Definition: object.h:281
KrkInstance * krk_newInstance(KrkClass *_class)
Create a new instance of the given class.
Definition: object.c:343
KrkString * krk_copyString(const char *chars, size_t length)
Obtain a string object representation of the given C string.
Definition: object.c:224
Stack reference or primative value.
Utilities for creating native bindings.
#define krk_parseArgs(f, n,...)
Parse arguments to a function while accepting keyword arguments.
Definition: util.h:360
#define KRK_DOC(thing, text)
Attach documentation to a thing of various types.
Definition: util.h:304
Definitions for primitive stack references.
Core API for the bytecode virtual machine.
#define vm
Convenience macro for namespacing.
Definition: vm.h:257
KrkValue krk_pop(void)
Pop the top of the stack.
Definition: vm.c:131
void krk_push(KrkValue value)
Push a stack value.
Definition: vm.c:118