use crate::evaluator::value::{FnContext, Value};
use crate::{Error, Result};
use super::max_args;
pub fn fn_now<'a>(_context: FnContext<'a, '_>, _args: &[&'a Value<'a>]) -> Result<&'a Value<'a>> {
Err(Error::D3137Error(
"$now requires evaluator context (environment stub)".to_string(),
))
}
pub fn fn_millis<'a>(
_context: FnContext<'a, '_>,
_args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
Err(Error::D3137Error(
"$millis requires evaluator context (environment stub)".to_string(),
))
}
pub fn fn_uuid<'a>(_context: FnContext<'a, '_>, _args: &[&'a Value<'a>]) -> Result<&'a Value<'a>> {
Err(Error::D3137Error(
"$uuid requires evaluator context (environment stub)".to_string(),
))
}
pub fn fn_from_millis<'a>(
context: FnContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 3);
let millis = args
.first()
.copied()
.unwrap_or_else(|| Value::undefined(context.arena));
if millis.is_undefined() {
return Ok(Value::undefined(context.arena));
}
if !millis.is_number() {
return Err(Error::T0410ArgumentNotValid(
crate::Span::at(context.char_index),
1,
context.name.to_string(),
));
}
let picture_arg = args.get(1).copied();
if let Some(p) = picture_arg {
if !p.is_undefined() && !p.is_null() && !p.is_string() {
return Err(Error::T0410ArgumentNotValid(
crate::Span::at(context.char_index),
2,
context.name.to_string(),
));
}
}
let tz_arg = args.get(2).copied();
if let Some(tz) = tz_arg {
if !tz.is_undefined() && !tz.is_null() && !tz.is_string() {
return Err(Error::T0410ArgumentNotValid(
crate::Span::at(context.char_index),
3,
context.name.to_string(),
));
}
}
let ms = millis.as_f64() as i64;
use chrono::{FixedOffset, TimeZone, Utc};
let Some(utc_timestamp) = Utc.timestamp_millis_opt(ms).single() else {
return Err(Error::T0410ArgumentNotValid(
crate::Span::at(context.char_index),
1,
context.name.to_string(),
));
};
let tz_str = tz_arg.and_then(|v| {
if v.is_string() {
Some(v.as_str().to_string())
} else {
None
}
});
let offset: FixedOffset = if let Some(ref tz) = tz_str {
match crate::datetime::parse_timezone_offset(tz) {
Some(o) => o,
None => FixedOffset::east_opt(0).unwrap(),
}
} else {
FixedOffset::east_opt(0).unwrap()
};
let date_with_offset = utc_timestamp.with_timezone(&offset);
let picture_str = picture_arg.and_then(|v| {
if v.is_string() {
Some(v.as_str().to_string())
} else {
None
}
});
if let Some(ref pic) = picture_str {
let formatted = crate::datetime::format_custom_date(&date_with_offset, pic)?;
return Ok(Value::string(context.arena, &formatted));
}
let iso = date_with_offset.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
Ok(Value::string(context.arena, &iso))
}
pub fn fn_to_millis<'a>(
context: FnContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 2);
let timestamp = args
.first()
.copied()
.unwrap_or_else(|| Value::undefined(context.arena));
if timestamp.is_undefined() {
return Ok(Value::undefined(context.arena));
}
if !timestamp.is_string() {
return Err(Error::T0410ArgumentNotValid(
crate::Span::at(context.char_index),
1,
context.name.to_string(),
));
}
let picture_arg = args.get(1).copied();
if let Some(p) = picture_arg {
if !p.is_undefined() && !p.is_null() && !p.is_string() {
return Err(Error::T0410ArgumentNotValid(
crate::Span::at(context.char_index),
2,
context.name.to_string(),
));
}
}
let ts_str = timestamp.as_str();
if ts_str.is_empty() {
return Ok(Value::undefined(context.arena));
}
let picture_str = picture_arg.and_then(|v| {
if v.is_string() {
Some(v.as_str().to_string())
} else {
None
}
});
if let Some(ref pic) = picture_str {
match crate::datetime::parse_custom_format(&ts_str, pic) {
Ok(Some(ms)) => return Ok(Value::number(context.arena, ms as f64)),
Ok(None) => return Ok(Value::undefined(context.arena)),
Err(e) => return Err(e),
}
}
match crate::datetime::parse_custom_format(&ts_str, "") {
Ok(Some(ms)) => Ok(Value::number(context.arena, ms as f64)),
Ok(None) => {
Err(Error::D3110InvalidDateTimeString(ts_str.to_string()))
}
Err(e) => Err(e),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Span;
use bumpalo::Bump;
fn dummy_apply_fn<'a>(
_span: Span,
_input: &'a Value<'a>,
_proc: &'a Value<'a>,
_args: &[&'a Value<'a>],
) -> crate::Result<&'a Value<'a>> {
Err(crate::Error::D3137Error("dummy apply_fn".to_string()))
}
fn ctx(arena: &Bump) -> FnContext<'_, '_> {
FnContext {
name: "test",
char_index: 0,
input: Value::undefined(arena),
arena,
apply_fn: &dummy_apply_fn,
}
}
#[test]
fn test_from_millis_basic() {
let arena = Bump::new();
let c = ctx(&arena);
let ms = Value::number(&arena, 1_483_228_800_000.0);
let result = fn_from_millis(c, &[ms]).unwrap();
assert!(result.is_string());
let s = result.as_str();
assert!(s.contains("2017-01-01"));
}
#[test]
fn test_from_millis_with_picture() {
let arena = Bump::new();
let c = ctx(&arena);
let ms = Value::number(&arena, 1521801216617.0);
let pic = Value::string(&arena, "[Y0001]-[M01]-[D01]");
let result = fn_from_millis(c, &[ms, pic]).unwrap();
assert!(result.is_string());
assert_eq!(result.as_str().as_ref(), "2018-03-23");
}
#[test]
fn test_from_millis_with_timezone() {
let arena = Bump::new();
let c = ctx(&arena);
let ms = Value::number(&arena, 1521801216617.0);
let pic = Value::string(&arena, "[Y]-[M01]-[D01]T[H01]:[m]:[s].[f001][Z0101t]");
let tz = Value::string(&arena, "+0100");
let result = fn_from_millis(c, &[ms, pic, tz]).unwrap();
assert!(result.is_string());
assert_eq!(result.as_str().as_ref(), "2018-03-23T11:33:36.617+0100");
}
#[test]
fn test_to_millis_roundtrip() {
let arena = Bump::new();
let c = ctx(&arena);
let ts = Value::string(&arena, "2017-01-01T00:00:00.000Z");
let result = fn_to_millis(c, &[ts]).unwrap();
assert!(result.is_number());
assert_eq!(result.as_f64(), 1_483_228_800_000.0);
}
#[test]
fn test_to_millis_with_picture() {
let arena = Bump::new();
let c = ctx(&arena);
let ts = Value::string(&arena, "2018");
let pic = Value::string(&arena, "[Y1]");
let result = fn_to_millis(c, &[ts, pic]).unwrap();
assert!(result.is_number());
assert_eq!(result.as_f64(), 1514764800000.0);
}
#[test]
fn test_to_millis_invalid_iso() {
let arena = Bump::new();
let c = ctx(&arena);
let ts = Value::string(&arena, "foo");
let result = fn_to_millis(c, &[ts]);
assert!(result.is_err());
}
#[test]
fn test_now_stub() {
let arena = Bump::new();
let c = ctx(&arena);
let result = fn_now(c, &[]);
assert!(result.is_err());
}
#[test]
fn test_millis_stub() {
let arena = Bump::new();
let c = ctx(&arena);
let result = fn_millis(c, &[]);
assert!(result.is_err());
}
}