# Working with Dates and Times

Workflow rules often depend on dates and times. You can notify users before a due date, create follow-up dates, calculate how long an issue has been in progress, or stop an update when a work item is logged for the wrong day.

In JavaScript workflows, most date and time values are represented as Unix timestamps in milliseconds. This means that values returned by `Date.now()`, date and date-time custom fields, and issue properties such as `created`, `updated`, and `resolved` can be compared as numbers.

## Date and Time Values

The following values are commonly used in workflow rules:

| Value | Description |
| --- | --- |
| `Date.now()` | Returns the current Unix timestamp in milliseconds. |
| Issue properties | The `created`, `updated`, and `resolved` properties store timestamps. The `resolved` property is empty for unresolved issues. |
| Date custom fields | A custom field with the `date` type stores a timestamp for the selected calendar date. When a date is selected in YouTrack, the stored timestamp is normalized to 12:00 UTC for that date. |
| Date and time custom fields | A custom field with the `date and time` type stores the exact timestamp selected by the user or assigned by a workflow. |
| Period custom fields | A custom field with the `period` type stores a period value. Use the `date-time` module to create period values and the period object's methods to read its components. |
| Work item dates | Work item dates are also available as timestamps. You can compare them with `Date.now()` or normalize them with a JavaScript `Date` object. |

Use the corresponding field type in rule requirements when your rule reads or writes custom fields:

```JAVASCRIPT
requirements: {
  DueDate: {
    type: entities.Field.dateType,
    name: 'Due Date'
  },
  Reminder: {
    type: entities.Field.dateTimeType,
    name: 'Reminder'
  },
  Estimation: {
    type: entities.Field.periodType
  }
}
```

## Compare Dates

Because date and date-time values are timestamps, you can compare them with relational operators. Always check that optional values are present before you compare them.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');

exports.rule = entities.Issue.onSchedule({
    title: 'Find overdue issues',
    search: '#Unresolved has: {Due Date}',
    cron: '0 0 10 ? * MON-FRI',
    guard: (ctx) => {
        const dueDate = ctx.issue.fields.DueDate;
        return dueDate && dueDate < Date.now();
    },
    action: (ctx) => {
        ctx.issue.addComment('This issue is overdue.');
    },
    requirements: {
        DueDate: {
            type: entities.Field.dateType,
            name: 'Due Date'
        }
    }
});
```

> **Note: Date-only Fields and Time Zones**
> A date field does not store a user's local midnight. When a user selects a date in YouTrack, the value is stored as 12:00 UTC for that calendar date. This avoids most accidental date shifts across time zones, but it also means that comparisons with `Date.now()` become true only after that instant has passed.
>
>
>
> When you need calendar-day logic, normalize both values before comparing them or format the timestamp in the target time zone and compare the formatted date strings.

The following example checks whether a work item was added for a date more than one week in the past. Both values are normalized to the start of the UTC day before the comparison.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');
const workflow = require('@jetbrains/youtrack-scripting-api/workflow');

const WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000;

exports.rule = entities.Issue.onChange({
  title: 'Reject old work items',
  guard: (ctx) => {
    return ctx.issue.workItems.added.isNotEmpty();
  },
  action: (ctx) => {
    const today = new Date().setUTCHours(0, 0, 0, 0);
    ctx.issue.workItems.added.forEach((item) => {
      const itemDate = new Date(item.date).setUTCHours(0, 0, 0, 0);
      workflow.check(itemDate >= today - WEEK_IN_MS,
          'Adding work more than one week in the past is not allowed.');
    });
  },
  requirements: {}
});
```

## Parse and Format Values

The workflow API includes the `@jetbrains/youtrack-scripting-api/date-time` module. Use this module when you need to parse user-provided text, format timestamps for messages or search queries, or calculate dates by adding or subtracting periods.

Use `dateTime.parse()` instead of the standard JavaScript `Date.parse()` method. The workflow method lets you pass one or more expected formats and an optional time zone.

```JAVASCRIPT
const dateTime = require('@jetbrains/youtrack-scripting-api/date-time');

const start = dateTime.parse(
    '2026-05-15 09:00',
    'yyyy-MM-dd HH:mm',
    'Europe/Lisbon'
);
```

Use `dateTime.format()` to create readable dates for messages or dates in a specific format for search queries.

```JAVASCRIPT
const formatted = dateTime.format(ctx.issue.fields.DueDate, 'yyyy-MM-dd');
const withTimeZone = dateTime.format(Date.now(), 'yyyy-MM-dd HH:mm', 'America/New_York');
```

If you don't specify a format or time zone, YouTrack uses the values from the current user profile. For actions that are run by the workflow user, YouTrack uses the global date format and default time zone.

## Add and Subtract Periods

Do not write expressions like `Date.now() + 10 hours`. JavaScript doesn't support period literals. Use numbers for fixed millisecond offsets or the `date-time` module for period-aware calculations.

For simple fixed intervals, define the interval in milliseconds:

```JAVASCRIPT
const DAY_IN_MS = 24 * 60 * 60 * 1000;

ctx.issue.fields.DueDate = Date.now() + 2 * DAY_IN_MS;
```

For intervals that should be represented as periods, use `dateTime.after()` and `dateTime.before()`. Duration strings use a compact format with weeks, days, hours, and minutes.

```JAVASCRIPT
const dateTime = require('@jetbrains/youtrack-scripting-api/date-time');

const threeHoursFromNow = dateTime.after(Date.now(), '3h');
const twoDaysBeforeDueDate = dateTime.before(ctx.issue.fields.DueDate, '2d');
const fullPeriod = dateTime.after(Date.now(), '2w4d3h15m');
```

You can also pass a period value that was read from a period custom field or created with `dateTime.toPeriod()`.

## Work with Period Fields

Period fields store values as period objects. To assign a value to a period field, convert a number of milliseconds or a period string with `dateTime.toPeriod()`.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');
const dateTime = require('@jetbrains/youtrack-scripting-api/date-time');

exports.rule = entities.Issue.onChange({
    title: 'Set default estimation',
    guard: (ctx) => {
        return ctx.issue.isReported && !ctx.issue.fields.Estimation;
    },
    action: (ctx) => {
        ctx.issue.fields.Estimation = dateTime.toPeriod('1d4h');
    },
    requirements: {
        Estimation: {
            type: entities.Field.periodType
        }
    }
});
```

When you need to calculate with a period value, read its components with methods like `getWeeks()`, `getDays()`, `getHours()`, and `getMinutes()`. The following helper converts a period value to minutes using the common default of five working days per week and eight working hours per day:

```JAVASCRIPT
function periodToMinutes(period) {
  return period ? period.getMinutes() +
      60 * (period.getHours() +
          8 * (period.getDays() +
              5 * period.getWeeks())) : 0;
}
```

If your YouTrack instance uses different time tracking settings, adjust the conversion to match your working week and working day.

## Calculate Working Time

Use `Project.intervalToWorkingMinutes()` when you want to calculate the number of minutes that fall within the project's working hours. This is the method used by the default work timer workflows.

```JAVASCRIPT
const started = ctx.issue.fields.TimerTime;
const duration = ctx.issue.project.intervalToWorkingMinutes(started, Date.now());
```

Use `Issue.afterMinutes()` when you need to add SLA-style business minutes to a starting timestamp. This method uses a calendar, such as the project's SLA calendar or the 24x7 calendar.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');

const calendar = ctx.issue.project.slaCalendar;
ctx.issue.fields.FirstReply = ctx.issue.afterMinutes(
    ctx.issue.created,
    60,
    calendar,
    true
);

ctx.issue.fields.NextReply = ctx.issue.afterMinutes(
    Date.now(),
    30,
    entities.Calendar24x7.instance(),
    false
);
```

## Tips

* Use timestamps for direct comparisons and assignments to date or date-time fields.

* Use `dateTime.parse()` when a date comes from text input or an external service.

* Use `dateTime.format()` before adding dates to comments, notifications, or search queries.

* Use `dateTime.toPeriod()` before assigning values to period fields.

* Use `Project.intervalToWorkingMinutes()` for work timer calculations that must respect working hours.

* Use `Issue.afterMinutes()` for SLA-style calculations that must respect a business-hours calendar.

