Skip to content

Implement Date.prototype.toISOString() #418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 3 additions & 111 deletions jerry-core/ecma/builtin-objects/ecma-builtin-date-prototype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,55 +42,6 @@
* @{
*/

/**
* Insert leading zeros to a string of a number if needed.
*/
static void
ecma_date_insert_leading_zeros (ecma_string_t **str_p, /**< input/output string */
ecma_number_t num, /**< input number */
uint32_t length) /**< length of string of number */
{
JERRY_ASSERT (length >= 1);

/* If the length is bigger than the number of digits in num, then insert leding zeros. */
uint32_t first_index = length - 1u;
ecma_number_t power_i = (ecma_number_t) pow (10, first_index);
for (uint32_t i = first_index; i > 0 && num < power_i; i--, power_i /= 10)
{
ecma_string_t *zero_str_p = ecma_new_ecma_string_from_uint32 (0);
ecma_string_t *concat_p = ecma_concat_ecma_strings (zero_str_p, *str_p);
ecma_deref_ecma_string (zero_str_p);
ecma_deref_ecma_string (*str_p);
*str_p = concat_p;
}
} /* ecma_date_insert_leading_zeros */

/**
* Insert a number to the start of a string with a specific separator character and
* fix length. If the length is bigger than the number of digits in num, then insert leding zeros.
*/
static void
ecma_date_insert_num_with_sep (ecma_string_t **str_p, /**< input/output string */
ecma_number_t num, /**< input number */
lit_magic_string_id_t magic_str_id, /**< separator character id */
uint32_t length) /**< length of string of number */
{
ecma_string_t *magic_string_p = ecma_get_magic_string (magic_str_id);

ecma_string_t *concat_p = ecma_concat_ecma_strings (magic_string_p, *str_p);
ecma_deref_ecma_string (magic_string_p);
ecma_deref_ecma_string (*str_p);
*str_p = concat_p;

ecma_string_t *num_str_p = ecma_new_ecma_string_from_number (num);
concat_p = ecma_concat_ecma_strings (num_str_p, *str_p);
ecma_deref_ecma_string (num_str_p);
ecma_deref_ecma_string (*str_p);
*str_p = concat_p;

ecma_date_insert_leading_zeros (str_p, num, length);
} /* ecma_date_insert_num_with_sep */

/**
* The Date.prototype object's 'toString' routine
*
Expand All @@ -103,66 +54,7 @@ ecma_date_insert_num_with_sep (ecma_string_t **str_p, /**< input/output string *
static ecma_completion_value_t
ecma_builtin_date_prototype_to_string (ecma_value_t this_arg) /**< this argument */
{
ecma_completion_value_t ret_value = ecma_make_empty_completion_value ();

if (!ecma_is_value_object (this_arg)
|| ecma_object_get_class_name (ecma_get_object_from_value (this_arg)) != LIT_MAGIC_STRING_DATE_UL)
{
ret_value = ecma_raise_type_error ("Incompatible type");
}
else
{
ECMA_TRY_CATCH (obj_this,
ecma_op_to_object (this_arg),
ret_value);

ecma_object_t *obj_p = ecma_get_object_from_value (obj_this);
ecma_property_t *prim_value_prop_p;
prim_value_prop_p = ecma_get_internal_property (obj_p, ECMA_INTERNAL_PROPERTY_PRIMITIVE_NUMBER_VALUE);
ecma_number_t *prim_value_num_p = ECMA_GET_NON_NULL_POINTER (ecma_number_t,
prim_value_prop_p->u.internal_property.value);

if (ecma_number_is_nan (*prim_value_num_p))
{
ecma_string_t *magic_str_p = ecma_get_magic_string (LIT_MAGIC_STRING_INVALID_DATE_UL);
ret_value = ecma_make_normal_completion_value (ecma_make_string_value (magic_str_p));
}
else
{
ecma_number_t milliseconds = ecma_date_ms_from_time (*prim_value_num_p);
ecma_string_t *output_str_p = ecma_new_ecma_string_from_number (milliseconds);
ecma_date_insert_leading_zeros (&output_str_p, milliseconds, 3);

ecma_number_t seconds = ecma_date_sec_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, seconds, LIT_MAGIC_STRING_DOT_CHAR, 2);

ecma_number_t minutes = ecma_date_min_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, minutes, LIT_MAGIC_STRING_COLON_CHAR, 2);

ecma_number_t hours = ecma_date_hour_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, hours, LIT_MAGIC_STRING_COLON_CHAR, 2);

ecma_number_t day = ecma_date_date_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, day, LIT_MAGIC_STRING_TIME_SEP_U, 2);

/*
* Note:
* 'ecma_date_month_from_time' (ECMA 262 v5, 15.9.1.4) returns a number from 0 to 11,
* but we have to print the month from 1 to 12 for ISO 8601 standard (ECMA 262 v5, 15.9.1.15).
*/
ecma_number_t month = ecma_date_month_from_time (*prim_value_num_p) + 1;
ecma_date_insert_num_with_sep (&output_str_p, month, LIT_MAGIC_STRING_MINUS_CHAR, 2);

ecma_number_t year = ecma_date_year_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, year, LIT_MAGIC_STRING_MINUS_CHAR, 4);

ret_value = ecma_make_normal_completion_value (ecma_make_string_value (output_str_p));
}

ECMA_FINALIZE (obj_this);
}

return ret_value;
return ecma_date_to_string (this_arg, ECMA_DATE_LOCAL);
} /* ecma_builtin_date_prototype_to_string */

/**
Expand Down Expand Up @@ -1192,7 +1084,7 @@ ecma_builtin_date_prototype_set_utc_full_year (ecma_value_t this_arg, /**< this
static ecma_completion_value_t
ecma_builtin_date_prototype_to_utc_string (ecma_value_t this_arg) /**< this argument */
{
ECMA_BUILTIN_CP_UNIMPLEMENTED (this_arg);
return ecma_date_to_string (this_arg, ECMA_DATE_UTC);
} /* ecma_builtin_date_prototype_to_utc_string */

/**
Expand All @@ -1207,7 +1099,7 @@ ecma_builtin_date_prototype_to_utc_string (ecma_value_t this_arg) /**< this argu
static ecma_completion_value_t
ecma_builtin_date_prototype_to_iso_string (ecma_value_t this_arg) /**< this argument */
{
ECMA_BUILTIN_CP_UNIMPLEMENTED (this_arg);
return ecma_date_to_string (this_arg, ECMA_DATE_UTC);
} /* ecma_builtin_date_prototype_to_iso_string */

/**
Expand Down
140 changes: 140 additions & 0 deletions jerry-core/ecma/builtin-objects/ecma-builtin-helpers-date.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

#include "ecma-alloc.h"
#include "ecma-builtin-helpers.h"
#include "ecma-exceptions.h"
#include "ecma-globals.h"
#include "ecma-helpers.h"
#include "ecma-objects.h"
#include "ecma-try-catch-macro.h"
#include "fdlibm-math.h"

#ifndef CONFIG_ECMA_COMPACT_PROFILE_DISABLE_DATE_BUILTIN
Expand Down Expand Up @@ -847,6 +850,143 @@ ecma_date_set_internal_property (ecma_value_t this_arg, /**< this argument */
return ecma_make_normal_completion_value (ecma_make_number_value (value_p));
} /* ecma_date_set_internal_property */

/**
* Insert leading zeros to a string of a number if needed.
*/
void
ecma_date_insert_leading_zeros (ecma_string_t **str_p, /**< input/output string */
ecma_number_t num, /**< input number */
uint32_t length) /**< length of string of number */
{
JERRY_ASSERT (length >= 1);

/* If the length is bigger than the number of digits in num, then insert leding zeros. */
uint32_t first_index = length - 1u;
ecma_number_t power_i = (ecma_number_t) pow (10, first_index);
for (uint32_t i = first_index; i > 0 && num < power_i; i--, power_i /= 10)
{
ecma_string_t *zero_str_p = ecma_new_ecma_string_from_uint32 (0);
ecma_string_t *concat_p = ecma_concat_ecma_strings (zero_str_p, *str_p);
ecma_deref_ecma_string (zero_str_p);
ecma_deref_ecma_string (*str_p);
*str_p = concat_p;
}
} /* ecma_date_insert_leading_zeros */

/**
* Insert a number to the start of a string with a specific separator character and
* fix length. If the length is bigger than the number of digits in num, then insert leding zeros.
*/
void
ecma_date_insert_num_with_sep (ecma_string_t **str_p, /**< input/output string */
ecma_number_t num, /**< input number */
lit_magic_string_id_t magic_str_id, /**< separator character id */
uint32_t length) /**< length of string of number */
{
ecma_string_t *magic_string_p = ecma_get_magic_string (magic_str_id);

ecma_string_t *concat_p = ecma_concat_ecma_strings (magic_string_p, *str_p);
ecma_deref_ecma_string (magic_string_p);
ecma_deref_ecma_string (*str_p);
*str_p = concat_p;

ecma_string_t *num_str_p = ecma_new_ecma_string_from_number (num);
concat_p = ecma_concat_ecma_strings (num_str_p, *str_p);
ecma_deref_ecma_string (num_str_p);
ecma_deref_ecma_string (*str_p);
*str_p = concat_p;

ecma_date_insert_leading_zeros (str_p, num, length);
} /* ecma_date_insert_num_with_sep */

/**
* Common function to create a time zone specific string.
*
* Used by:
* - The Date.prototype.toString routine.
* - The Date.prototype.toISOString routine.
* - The Date.prototype.toUTCString routine.
*
* @return completion value
* Returned value must be freed with ecma_free_completion_value.
*/
ecma_completion_value_t
ecma_date_to_string (ecma_value_t this_arg, /**< this argument */
ecma_date_timezone_t timezone) /**< timezone */
{
TODO ("Add support for local time zone output.");
ecma_completion_value_t ret_value = ecma_make_empty_completion_value ();

if (!ecma_is_value_object (this_arg)
|| ecma_object_get_class_name (ecma_get_object_from_value (this_arg)) != LIT_MAGIC_STRING_DATE_UL)
{
ret_value = ecma_raise_type_error ("Incompatible type");
}
else
{
ECMA_TRY_CATCH (obj_this,
ecma_op_to_object (this_arg),
ret_value);

ecma_object_t *obj_p = ecma_get_object_from_value (obj_this);
ecma_property_t *prim_value_prop_p;
prim_value_prop_p = ecma_get_internal_property (obj_p, ECMA_INTERNAL_PROPERTY_PRIMITIVE_NUMBER_VALUE);
ecma_number_t *prim_value_num_p = ECMA_GET_NON_NULL_POINTER (ecma_number_t,
prim_value_prop_p->u.internal_property.value);

if (ecma_number_is_nan (*prim_value_num_p))
{
ecma_string_t *magic_str_p = ecma_get_magic_string (LIT_MAGIC_STRING_INVALID_DATE_UL);
ret_value = ecma_make_normal_completion_value (ecma_make_string_value (magic_str_p));
}
else
{
ecma_string_t *output_str_p;
ecma_number_t milliseconds = ecma_date_ms_from_time (*prim_value_num_p);

if (timezone == ECMA_DATE_UTC)
{
output_str_p = ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY);
ecma_date_insert_num_with_sep (&output_str_p, milliseconds, LIT_MAGIC_STRING_Z_CHAR, 3);
}
else
{
output_str_p = ecma_new_ecma_string_from_number (milliseconds);
ecma_date_insert_leading_zeros (&output_str_p, milliseconds, 3);
}

ecma_number_t seconds = ecma_date_sec_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, seconds, LIT_MAGIC_STRING_DOT_CHAR, 2);

ecma_number_t minutes = ecma_date_min_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, minutes, LIT_MAGIC_STRING_COLON_CHAR, 2);

ecma_number_t hours = ecma_date_hour_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, hours, LIT_MAGIC_STRING_COLON_CHAR, 2);

ecma_number_t day = ecma_date_date_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, day, LIT_MAGIC_STRING_TIME_SEP_U, 2);

/*
* Note:
* 'ecma_date_month_from_time' (ECMA 262 v5, 15.9.1.4) returns a number from 0 to 11,
* but we have to print the month from 1 to 12 for ISO 8601 standard (ECMA 262 v5, 15.9.1.15).
*/
ecma_number_t month = ecma_date_month_from_time (*prim_value_num_p) + 1;
ecma_date_insert_num_with_sep (&output_str_p, month, LIT_MAGIC_STRING_MINUS_CHAR, 2);

ecma_number_t year = ecma_date_year_from_time (*prim_value_num_p);
ecma_date_insert_num_with_sep (&output_str_p, year, LIT_MAGIC_STRING_MINUS_CHAR, 4);

ret_value = ecma_make_normal_completion_value (ecma_make_string_value (output_str_p));
}

ECMA_FINALIZE (obj_this);
}

return ret_value;
} /* ecma_date_create_formatted_string */

/**
* @}
* @}
Expand Down
8 changes: 8 additions & 0 deletions jerry-core/ecma/builtin-objects/ecma-builtin-helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ extern ecma_completion_value_t ecma_date_set_internal_property (ecma_value_t thi
ecma_number_t day,
ecma_number_t time,
ecma_date_timezone_t is_utc);
extern void ecma_date_insert_leading_zeros (ecma_string_t **str_p,
ecma_number_t num,
uint32_t length);
extern void ecma_date_insert_num_with_sep (ecma_string_t **str_p,
ecma_number_t num,
lit_magic_string_id_t magic_str_id,
uint32_t length);
extern ecma_completion_value_t ecma_date_to_string (ecma_value_t this_arg, ecma_date_timezone_t timezone);
#endif /* !CONFIG_ECMA_COMPACT_PROFILE_DISABLE_DATE_BUILTIN */

typedef struct
Expand Down
1 change: 1 addition & 0 deletions jerry-core/lit/lit-magic-strings.inc.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_G_CHAR, "g")
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_I_CHAR, "i")
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_M_CHAR, "m")
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_TIME_SEP_U, "T")
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_Z_CHAR, "Z")
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_SLASH_CHAR, "/")
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_BACKSLASH_CHAR, "\\")
LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_EMPTY_NON_CAPTURE_GROUP, "(?:)")
Expand Down
30 changes: 30 additions & 0 deletions tests/jerry/date-tostring.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,33 @@ catch (e)
assert (e instanceof TypeError);
assert (e.message === "Incompatible type");
}

assert (new Date (NaN).toISOString () == "Invalid Date");
assert (new Date ("2015-07-16").toISOString () == "2015-07-16T00:00:00.000Z");
assert (new Date ("2015-07-16T11:29:05.023").toISOString () == "2015-07-16T11:29:05.023Z");

try
{
Date.prototype.toISOString.call(-1);
assert (false);
}
catch (e)
{
assert (e instanceof TypeError);
assert (e.message === "Incompatible type");
}

assert (new Date (NaN).toUTCString () == "Invalid Date");
assert (new Date ("2015-07-16").toUTCString () == "2015-07-16T00:00:00.000Z");
assert (new Date ("2015-07-16T11:29:05.023").toUTCString () == "2015-07-16T11:29:05.023Z");

try
{
Date.prototype.toUTCString.call(-1);
assert (false);
}
catch (e)
{
assert (e instanceof TypeError);
assert (e.message === "Incompatible type");
}